Commit ed801e53 authored by Paul Asmuth's avatar Paul Asmuth
Browse files

split Canvas into PlotLayout and Legend

parent aca8c691
Loading
Loading
Loading
Loading

core/layout/legend.cc

0 → 100644
+311 −0
Original line number Diff line number Diff line
/**
 * This file is part of the "libstx" project
 *   Copyright (c) 2014 Paul Asmuth, Google Inc.
 *
 * libstx is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License v3.0. You should have received a
 * copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
#include <stdlib.h>
#include "../graphics/rendertarget.h"
#include "../graphics/svgtarget.h"
#include "canvas.h"
#include "domain.h"

namespace signaltk {
namespace chart {

Legend::Legend() :
    width_(800),
    height_(320) {}

void Legend::renderOutsideLegends(
    RenderTarget* target,
    Viewport* viewport) const {
  for (const auto& legend : legends_) {
    if (legend->placement() != LegendDefinition::LEGEND_OUTSIDE) {
      continue;
    }

    target->beginGroup("legend");

    switch (legend->verticalPosition()) {
      case LegendDefinition::LEGEND_TOP: {
        switch (legend->horizontalPosition()) {
          case LegendDefinition::LEGEND_LEFT:
            renderLeftLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                false,
                true);
            break;
          case LegendDefinition::LEGEND_RIGHT:
            renderRightLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                false,
                true);
            break;
          }

        viewport->setPaddingTop(
            viewport->paddingTop() + kLegendOutsideVertPadding);
        break;
      }

      case LegendDefinition::LEGEND_BOTTOM: {
        switch (legend->horizontalPosition()) {
          case LegendDefinition::LEGEND_LEFT:
            renderLeftLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                true,
                true);
            break;
          case LegendDefinition::LEGEND_RIGHT:
            renderRightLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                true,
                true);
            break;

          }

        viewport->setPaddingBottom(
            viewport->paddingBottom() + kLegendOutsideVertPadding);
        break;
      }
    }

    target->finishGroup();
  }
}

void Legend::renderInsideLegends(
    RenderTarget* target,
    Viewport* viewport) const {
  auto orig_padding = viewport->padding();

  for (const auto& legend : legends_) {
    if (legend->placement() != LegendDefinition::LEGEND_INSIDE) {
      continue;
    }

    target->beginGroup("legend");

    viewport->setPaddingTop(viewport->paddingTop() + kLegendInsideVertPadding);
    viewport->setPaddingBottom(
        viewport->paddingBottom() + kLegendInsideVertPadding);

    switch (legend->horizontalPosition()) {
      case LegendDefinition::LEGEND_LEFT:
        renderLeftLegend(
            target,
            viewport,
            legend.get(),
            kLegendOutsideHorizPadding,
            legend->verticalPosition() == LegendDefinition::LEGEND_BOTTOM,
            false);
        break;
      case LegendDefinition::LEGEND_RIGHT:
        renderRightLegend(
            target,
            viewport,
            legend.get(),
            kLegendOutsideHorizPadding,
            legend->verticalPosition() == LegendDefinition::LEGEND_BOTTOM,
            false);
        break;
      }

    target->finishGroup();
  }

  viewport->setPadding(orig_padding);
}

void Legend::renderRightLegend(
    RenderTarget* target,
    Viewport* viewport,
    LegendDefinition* legend,
    double horiz_padding,
    bool bottom,
    bool outside) const {
  std::string title = legend->title();

  double height;
  if (bottom) {
    height = viewport->paddingTop() + viewport->innerHeight()  -
      kLegendLineHeight * 0.5f;
  } else {
    height = viewport->paddingTop();
  }

  target->drawText(
    title,
    viewport->paddingLeft() + horiz_padding,
    height,
    "start",
    bottom ? "text-after-edge" : "text-before-edge",
    "title");

  auto lx = viewport->paddingLeft() + viewport->innerWidth() - horiz_padding;
  auto lx_boundary = viewport->paddingLeft() + horiz_padding +
    estimateTextLength(title) + kLegendLabelPadding;

  for (const auto& entry : legend->entries()) {
    auto this_len = estimateTextLength(std::get<0>(entry)) +
        kLegendLabelPadding;

    /* line wrap */
    if (lx - this_len < lx_boundary) {
      lx = viewport->paddingLeft() + viewport->innerWidth() - horiz_padding;
      height += bottom ? -1 * kLegendLineHeight : kLegendLineHeight;
      lx_boundary = viewport->paddingLeft() + horiz_padding;
    }

    auto ly = bottom ?
        height - kLegendPointSize * 0.4f :
        height + kLegendPointSize * 2.0f;

    target->drawPoint(
        lx,
        ly,
        std::get<2>(entry),
        kLegendPointSize,
        std::get<1>(entry),
        "point");

    target->drawText(
      std::get<0>(entry),
      lx - kLegendPointWidth,
      ly,
      "end",
      "central",
      "label");

    lx -= this_len;
  }

  if (bottom) {
    if (outside) {
      height -= kLegendLineHeight;
    }

    viewport->setPaddingBottom(
        viewport->innerHeight() + viewport->paddingTop() +
        viewport->paddingBottom() - height);
  } else {
    height += kLegendLineHeight;
    viewport->setPaddingTop(height);
  }
}

void Legend::renderLeftLegend(
    RenderTarget* target,
    Viewport* viewport,
    LegendDefinition* legend,
    double horiz_padding,
    bool bottom,
    bool outside) const {
  std::string title = legend->title();

  double height;
  if (bottom) {
    height = viewport->paddingTop() + viewport->innerHeight()  -
      kLegendLineHeight * 0.5f;
  } else {
    height = viewport->paddingTop();
  }

  target->drawText(
    title,
    viewport->paddingLeft() + viewport->innerWidth() - horiz_padding,
    height,
    "end",
    bottom ? "text-after-edge" : "text-before-edge",
    "title");

  auto lx = viewport->paddingLeft() + horiz_padding;
  auto lx_boundary = viewport->paddingLeft() + viewport->innerWidth() -
      horiz_padding - estimateTextLength(title) - kLegendLabelPadding;

  for (const auto& entry : legend->entries()) {
    auto this_len = estimateTextLength(std::get<0>(entry)) + 
        kLegendLabelPadding;

    /* line wrap */
    if (lx + this_len > lx_boundary) {
      lx = viewport->paddingLeft() + horiz_padding;
      lx_boundary = viewport->paddingLeft() + viewport->innerWidth() -
          horiz_padding;
      height += bottom ? -1 * kLegendLineHeight : kLegendLineHeight;
    }

    auto ly = bottom ?
        height - kLegendPointSize * 0.4f :
        height + kLegendPointSize * 2.0f;

    target->drawPoint(
        lx,
        ly,
        std::get<2>(entry),
        kLegendPointSize,
        std::get<1>(entry),
        "point");

    target->drawText(
      std::get<0>(entry),
      lx + kLegendPointWidth,
      ly,
      "start",
      "central",
      "label");

    lx += this_len;
  }

  if (bottom) {
    if (outside) {
      height -= kLegendLineHeight;
    }

    viewport->setPaddingBottom(
        viewport->innerHeight() + viewport->paddingTop() +
        viewport->paddingBottom() - height);
  } else {
    height += kLegendLineHeight;
    viewport->setPaddingTop(height);
  }
}

LegendDefinition* Legend::addLegend(
    LegendDefinition::kVerticalPosition vert_pos,
    LegendDefinition::kHorizontalPosition horiz_pos,
    LegendDefinition::kPlacement placement,
    const std::string& title) {
  legends_.emplace_back(
      new LegendDefinition(vert_pos, horiz_pos, placement, title));
  return legends_.back().get();
}

LegendDefinition* Legend::legend() const {
  if (legends_.size() == 0) {
    return nullptr;
  } else {
    return legends_.back().get();
  }
}

}
}

core/layout/legend.h

0 → 100644
+96 −0
Original line number Diff line number Diff line
/**
 * This file is part of the "libstx" project
 *   Copyright (c) 2011-2014 Paul Asmuth, Google Inc.
 *
 * libstx is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License v3.0. You should have received a
 * copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
#ifndef _libstx_CANVAS_H
#define _libstx_CANVAS_H
#include <memory>
#include <vector>
#include <string>
#include <tuple>
#include "axisdefinition.h"
#include "drawable.h"
#include "legenddefinition.h"

namespace signaltk {
namespace chart {
class RenderTarget;

class Legend {
public:
  static const int kLegendLabelPadding = 20; // FIXPAUL make configurable
  static const int kLegendLineHeight = 20; // FIXPAUL make configurable
  static const int kLegendInsideVertPadding = 10;
  static const int kLegendInsideHorizPadding = 15;
  static const int kLegendOutsideVertPadding = 10;
  static const int kLegendOutsideHorizPadding = 25;
  static const int kLegendPointY = 6;
  static const int kLegendPointWidth = 8;
  static const int kLegendPointSize = 3;

  Legend();

  /**
   * Add a leged to this canvas. Usually legends are not specified manually via
   * this method but through one of the Chart subclasses. However it is safe
   * to call this method to explicitly define a custom legend.
   *
   * The returned pointer is owned by the canvas instance and must not be freed
   * by the caller.
   */
  LegendDefinition* addLegend(
      LegendDefinition::kVerticalPosition vert_pos,
      LegendDefinition::kHorizontalPosition horiz_pos,
      LegendDefinition::kPlacement placement,
      const std::string& title);
  /**
   * Return the legend or nullptr
   */
  LegendDefinition* legend() const;

  /**
   * Render the contents of this canvas to the provided render target
   *
   * @param target a RenderTarget subclass instance. Does not transfer ownership
   */
  void render(RenderTarget* target) const;

protected:

  /**
   * Render the legends
   */
  void renderOutsideLegends(RenderTarget* target, Viewport* viewport) const;

  /**
   * Render the legends
   */
  void renderInsideLegends(RenderTarget* target, Viewport* viewport) const;

  void renderRightLegend(
      RenderTarget* target,
      Viewport* viewport,
      LegendDefinition* legend,
      double horiz_padding,
      bool bottom,
      bool outside) const;

  void renderLeftLegend(
      RenderTarget* target,
      Viewport* viewport,
      LegendDefinition* legend,
      double horiz_padding,
      bool bottom,
      bool outside) const;

  std::vector<std::unique_ptr<LegendDefinition>> legends_;
};

}
}
#endif
+1 −6
Original line number Diff line number Diff line
@@ -8,15 +8,10 @@

# chart
add_library(signaltk-plot STATIC
    canvas.cc
    plot_layout.cc
    axisdefinition.cc
    areachart.cc
    barchart.cc
    linechart.cc
    pointchart.cc
    domain.cc
    domainprovider.cc
    drawable.cc
    griddefinition.cc
    legenddefinition.cc
    series.cc
+12 −343
Original line number Diff line number Diff line
@@ -10,25 +10,17 @@
#include <stdlib.h>
#include "../graphics/rendertarget.h"
#include "../graphics/svgtarget.h"
#include "canvas.h"
#include "plot_layout.h"
#include "domain.h"

namespace signaltk {
namespace chart {

Canvas::Canvas() :
PlotLayout::PlotLayout() :
    width_(800),
    height_(320) {}

void Canvas::setTitle(const std::string& title) {
  title_ = title;
}

void Canvas::setSubtitle(const std::string& subtitle) {
  subtitle_ = subtitle;
}

void Canvas::setDimensions(int width, int height) {
void PlotLayout::setDimensions(int width, int height) {
  if (width > 0) {
    width_ = width;
  }
@@ -38,53 +30,17 @@ void Canvas::setDimensions(int width, int height) {
  }
}

void Canvas::render(RenderTarget* target) const {
void PlotLayout::render(RenderTarget* target) const {
  // FIXPAUL: initialize from rendertarget
  Viewport viewport(width_, height_);

  target->beginChart(width_, height_, "fm-chart");
  renderTitle(target, &viewport);
  renderOutsideLegends(target, &viewport);
  renderAxes(target, &viewport);
  renderGrids(target, &viewport);
  renderInsideLegends(target, &viewport);
  renderCharts(target, &viewport);
  target->finishChart();
}

void Canvas::renderTitle(RenderTarget* target, Viewport* viewport) const {
  if (title_.size() > 0) {
    target->drawText(
        title_,
        viewport->paddingLeft() + viewport->innerWidth() * 0.5f,
        viewport->paddingTop(),
        "middle",
        "text-before-edge",
        "chart-title");

    viewport->setPaddingTop(viewport->paddingTop() + kTitleLineHeight);
  }

  if (subtitle_.size() > 0) {
    target->drawText(
        subtitle_,
        viewport->paddingLeft() + viewport->innerWidth() * 0.5f,
        viewport->paddingTop(),
        "middle",
        "text-before-edge",
        "chart-subtitle");

    viewport->setPaddingTop(viewport->paddingTop() + kSubtitleLineHeight);
  }
}

void Canvas::renderCharts(RenderTarget* target, Viewport* viewport) const {
  for (const auto& drawable : drawables_) {
    drawable->render(target, viewport);
  }
}

void Canvas::renderAxes(RenderTarget* target, Viewport* viewport) const {
void PlotLayout::renderAxes(RenderTarget* target, Viewport* viewport) const {
  std::tuple<int, int, int, int> padding = viewport->padding();
  std::vector<std::pair<int, AxisDefinition*>> top;
  std::vector<std::pair<int, AxisDefinition*>> right;
@@ -164,7 +120,7 @@ void Canvas::renderAxes(RenderTarget* target, Viewport* viewport) const {
  }
}

void Canvas::renderTopAxis(
void PlotLayout::renderTopAxis(
    RenderTarget* target,
    Viewport* viewport,
    AxisDefinition* axis,
@@ -229,7 +185,7 @@ void Canvas::renderTopAxis(
}


void Canvas::renderRightAxis(
void PlotLayout::renderRightAxis(
    RenderTarget* target,
    Viewport* viewport,
    AxisDefinition* axis,
@@ -293,7 +249,7 @@ void Canvas::renderRightAxis(
  target->finishGroup();
}

void Canvas::renderBottomAxis(
void PlotLayout::renderBottomAxis(
    RenderTarget* target,
    Viewport* viewport,
    AxisDefinition* axis,
@@ -357,7 +313,7 @@ void Canvas::renderBottomAxis(
  target->finishGroup();
}

void Canvas::renderLeftAxis(
void PlotLayout::renderLeftAxis(
    RenderTarget* target,
    Viewport* viewport,
    AxisDefinition* axis,
@@ -422,276 +378,7 @@ void Canvas::renderLeftAxis(
  target->finishGroup();
}

void Canvas::renderOutsideLegends(
    RenderTarget* target,
    Viewport* viewport) const {
  for (const auto& legend : legends_) {
    if (legend->placement() != LegendDefinition::LEGEND_OUTSIDE) {
      continue;
    }

    target->beginGroup("legend");

    switch (legend->verticalPosition()) {
      case LegendDefinition::LEGEND_TOP: {
        switch (legend->horizontalPosition()) {
          case LegendDefinition::LEGEND_LEFT:
            renderLeftLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                false,
                true);
            break;
          case LegendDefinition::LEGEND_RIGHT:
            renderRightLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                false,
                true);
            break;
          }

        viewport->setPaddingTop(
            viewport->paddingTop() + kLegendOutsideVertPadding);
        break;
      }

      case LegendDefinition::LEGEND_BOTTOM: {
        switch (legend->horizontalPosition()) {
          case LegendDefinition::LEGEND_LEFT:
            renderLeftLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                true,
                true);
            break;
          case LegendDefinition::LEGEND_RIGHT:
            renderRightLegend(
                target,
                viewport,
                legend.get(),
                kLegendOutsideHorizPadding,
                true,
                true);
            break;

          }

        viewport->setPaddingBottom(
            viewport->paddingBottom() + kLegendOutsideVertPadding);
        break;
      }
    }

    target->finishGroup();
  }
}

void Canvas::renderInsideLegends(
    RenderTarget* target,
    Viewport* viewport) const {
  auto orig_padding = viewport->padding();

  for (const auto& legend : legends_) {
    if (legend->placement() != LegendDefinition::LEGEND_INSIDE) {
      continue;
    }

    target->beginGroup("legend");

    viewport->setPaddingTop(viewport->paddingTop() + kLegendInsideVertPadding);
    viewport->setPaddingBottom(
        viewport->paddingBottom() + kLegendInsideVertPadding);

    switch (legend->horizontalPosition()) {
      case LegendDefinition::LEGEND_LEFT:
        renderLeftLegend(
            target,
            viewport,
            legend.get(),
            kLegendOutsideHorizPadding,
            legend->verticalPosition() == LegendDefinition::LEGEND_BOTTOM,
            false);
        break;
      case LegendDefinition::LEGEND_RIGHT:
        renderRightLegend(
            target,
            viewport,
            legend.get(),
            kLegendOutsideHorizPadding,
            legend->verticalPosition() == LegendDefinition::LEGEND_BOTTOM,
            false);
        break;
      }

    target->finishGroup();
  }

  viewport->setPadding(orig_padding);
}

void Canvas::renderRightLegend(
    RenderTarget* target,
    Viewport* viewport,
    LegendDefinition* legend,
    double horiz_padding,
    bool bottom,
    bool outside) const {
  std::string title = legend->title();

  double height;
  if (bottom) {
    height = viewport->paddingTop() + viewport->innerHeight()  -
      kLegendLineHeight * 0.5f;
  } else {
    height = viewport->paddingTop();
  }

  target->drawText(
    title,
    viewport->paddingLeft() + horiz_padding,
    height,
    "start",
    bottom ? "text-after-edge" : "text-before-edge",
    "title");

  auto lx = viewport->paddingLeft() + viewport->innerWidth() - horiz_padding;
  auto lx_boundary = viewport->paddingLeft() + horiz_padding +
    estimateTextLength(title) + kLegendLabelPadding;

  for (const auto& entry : legend->entries()) {
    auto this_len = estimateTextLength(std::get<0>(entry)) +
        kLegendLabelPadding;

    /* line wrap */
    if (lx - this_len < lx_boundary) {
      lx = viewport->paddingLeft() + viewport->innerWidth() - horiz_padding;
      height += bottom ? -1 * kLegendLineHeight : kLegendLineHeight;
      lx_boundary = viewport->paddingLeft() + horiz_padding;
    }

    auto ly = bottom ?
        height - kLegendPointSize * 0.4f :
        height + kLegendPointSize * 2.0f;

    target->drawPoint(
        lx,
        ly,
        std::get<2>(entry),
        kLegendPointSize,
        std::get<1>(entry),
        "point");

    target->drawText(
      std::get<0>(entry),
      lx - kLegendPointWidth,
      ly,
      "end",
      "central",
      "label");

    lx -= this_len;
  }

  if (bottom) {
    if (outside) {
      height -= kLegendLineHeight;
    }

    viewport->setPaddingBottom(
        viewport->innerHeight() + viewport->paddingTop() +
        viewport->paddingBottom() - height);
  } else {
    height += kLegendLineHeight;
    viewport->setPaddingTop(height);
  }
}

void Canvas::renderLeftLegend(
    RenderTarget* target,
    Viewport* viewport,
    LegendDefinition* legend,
    double horiz_padding,
    bool bottom,
    bool outside) const {
  std::string title = legend->title();

  double height;
  if (bottom) {
    height = viewport->paddingTop() + viewport->innerHeight()  -
      kLegendLineHeight * 0.5f;
  } else {
    height = viewport->paddingTop();
  }

  target->drawText(
    title,
    viewport->paddingLeft() + viewport->innerWidth() - horiz_padding,
    height,
    "end",
    bottom ? "text-after-edge" : "text-before-edge",
    "title");

  auto lx = viewport->paddingLeft() + horiz_padding;
  auto lx_boundary = viewport->paddingLeft() + viewport->innerWidth() -
      horiz_padding - estimateTextLength(title) - kLegendLabelPadding;

  for (const auto& entry : legend->entries()) {
    auto this_len = estimateTextLength(std::get<0>(entry)) + 
        kLegendLabelPadding;

    /* line wrap */
    if (lx + this_len > lx_boundary) {
      lx = viewport->paddingLeft() + horiz_padding;
      lx_boundary = viewport->paddingLeft() + viewport->innerWidth() -
          horiz_padding;
      height += bottom ? -1 * kLegendLineHeight : kLegendLineHeight;
    }

    auto ly = bottom ?
        height - kLegendPointSize * 0.4f :
        height + kLegendPointSize * 2.0f;

    target->drawPoint(
        lx,
        ly,
        std::get<2>(entry),
        kLegendPointSize,
        std::get<1>(entry),
        "point");

    target->drawText(
      std::get<0>(entry),
      lx + kLegendPointWidth,
      ly,
      "start",
      "central",
      "label");

    lx += this_len;
  }

  if (bottom) {
    if (outside) {
      height -= kLegendLineHeight;
    }

    viewport->setPaddingBottom(
        viewport->innerHeight() + viewport->paddingTop() +
        viewport->paddingBottom() - height);
  } else {
    height += kLegendLineHeight;
    viewport->setPaddingTop(height);
  }
}

void Canvas::renderGrids(RenderTarget* target, Viewport* viewport) const {
void PlotLayout::renderGrids(RenderTarget* target, Viewport* viewport) const {
  for (const auto& grid : grids_) {
    switch (grid->placement()) {

@@ -730,33 +417,15 @@ void Canvas::renderGrids(RenderTarget* target, Viewport* viewport) const {
  }
}

AxisDefinition* Canvas::addAxis(AxisDefinition::kPosition position) {
AxisDefinition* PlotLayout::addAxis(AxisDefinition::kPosition position) {
  axes_.emplace_back(new AxisDefinition(position));
  return axes_.back().get();
}

GridDefinition* Canvas::addGrid(GridDefinition::kPlacement placement) {
GridDefinition* PlotLayout::addGrid(GridDefinition::kPlacement placement) {
  grids_.emplace_back(new GridDefinition(placement));
  return grids_.back().get();
}

LegendDefinition* Canvas::addLegend(
    LegendDefinition::kVerticalPosition vert_pos,
    LegendDefinition::kHorizontalPosition horiz_pos,
    LegendDefinition::kPlacement placement,
    const std::string& title) {
  legends_.emplace_back(
      new LegendDefinition(vert_pos, horiz_pos, placement, title));
  return legends_.back().get();
}

LegendDefinition* Canvas::legend() const {
  if (legends_.size() == 0) {
    return nullptr;
  } else {
    return legends_.back().get();
  }
}

}
}
+3 −95

File changed and moved.

Preview size limit exceeded, changes collapsed.

Loading