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

areachart WIP

parent 0012d0c9
Loading
Loading
Loading
Loading
+7 −255
Original line number Diff line number Diff line
@@ -16,264 +16,16 @@
namespace fnordmetric {
namespace ui {

char AreaChart::kDefaultBorderStyle[] = "none";
double AreaChart::kDefaultBorderWidth = 2.0f;
char AreaChart::kDefaultLineStyle[] = "none";
char AreaChart::kDefaultLineWidth[] = "2";
char AreaChart::kDefaultPointStyle[] = "none";
double AreaChart::kDefaultPointSize = 3.0f;
char AreaChart::kDefaultPointSize[] = "3";

AreaChart::AreaChart(
    Canvas* canvas,
    NumericalDomain* x_domain /* = nullptr */,
    NumericalDomain* y_domain /* = nullptr */,
    bool stacked /* = false */) :
    canvas_(canvas),
    x_domain_(x_domain),
    y_domain_(y_domain),
    stacked_(stacked),
    num_series_(0) {}

void AreaChart::addSeries(
    Series2D<double, double>* series,
    const std::string& border_style /* = kDefaultBorderStyle */,
    double border_width /* = kDefaultBorderWidth */,
    const std::string& point_style /* = kDefaultPointStyle */,
    double point_size /* = kDefaultPointSize */,
    bool smooth /* = false */) {
  Area area;
  area.color = seriesColor(series);
  area.border_style = border_style;
  area.border_width = border_width;
  area.point_style = point_style;
  area.point_size = point_size;
  area.smooth = smooth;

  // FIXPAUL: stacked
  for (const auto& spoint : series->getData()) {
    area.points.emplace_back(std::get<0>(spoint), 0, std::get<1>(spoint));
  }

  areas_.emplace_back(area);
}

void AreaChart::addSeries(
    Series3D<double, double, double>* series,
    const std::string& border_style /* = kDefaultBorderStyle */,
    double border_width /* = kDefaultBorderWidth */,
    const std::string& point_style /* = kDefaultPointStyle */,
    double point_size /* = kDefaultPointSize */,
    bool smooth /* = false */) {
  Area area;
  area.color = seriesColor(series);
  area.border_style = border_style;
  area.border_width = border_width;
  area.point_style = point_style;
  area.point_size = point_size;
  area.smooth = smooth;

  // FIXPAUL: stacked
  for (const auto& spoint : series->getData()) {
    area.points.emplace_back(
        std::get<0>(spoint),
        std::get<1>(spoint),
        std::get<2>(spoint));
  }

  areas_.emplace_back(area);
}

AxisDefinition* AreaChart::addAxis(AxisDefinition::kPosition position) {
  switch (position) {
    case AxisDefinition::TOP:
      return canvas_->addAxis(position, getXDomain());

    case AxisDefinition::RIGHT:
      return canvas_->addAxis(position, getYDomain());

    case AxisDefinition::BOTTOM:
      return canvas_->addAxis(position, getXDomain());

    case AxisDefinition::LEFT:
      return canvas_->addAxis(position, getYDomain());
  }
}

NumericalDomain* AreaChart::getXDomain() const {
  if (x_domain_ != nullptr) {
    return x_domain_;
  }

  if (x_domain_auto_.get() == nullptr) {
    double x_min = 0.0f;
    double x_max = 0.0f;

    for (const auto& area : areas_) {
      for (const auto& point : area.points) {
        if (std::get<0>(point) > x_max) {
          x_max = std::get<0>(point);
        }
        if (std::get<0>(point) < x_min) {
          x_min = std::get<0>(point) ;
        }
      }
    }

    if (x_max > 0) {
      x_max *= 1.1;
    }

    if (x_max < 0) {
      x_max *= 0.9;
    }

    if (x_min > 0) {
      x_min *= 0.9;
    }

    if (x_min < 0) {
      x_min *= 1.1;
    }

    x_domain_auto_.reset(new NumericalDomain(x_min, x_max, false));
  }

  return x_domain_auto_.get();
}

NumericalDomain* AreaChart::getYDomain() const {
  if (y_domain_ != nullptr) {
    return y_domain_;
  }

  if (y_domain_auto_.get() == nullptr) {
    double y_min = 0.0f;
    double y_max = 0.0f;

    for (const auto& area : areas_) {
      for (const auto& point : area.points) {
        if (std::get<2>(point) > y_max) {
          y_max = std::get<2>(point);
        }
        if (std::get<2>(point) < y_min) {
          y_min = std::get<2>(point) ;
        }
      }
    }

    if (y_max > 0) {
      y_max *= 1.1;
    }

    if (y_max < 0) {
      y_max *= 0.9;
    }

    if (y_min > 0) {
      y_min *= 0.9;
    }

    if (y_min < 0) {
      y_min *= 1.1;
    }

    y_domain_auto_.reset(new NumericalDomain(y_min, y_max, false));
  }


  return y_domain_auto_.get();
}

void AreaChart::render(
    RenderTarget* target,
    int width,
    int height,
    std::tuple<int, int, int, int>* padding) const {
  auto padding_top = std::get<0>(*padding);
  auto padding_right = std::get<1>(*padding);
  auto padding_bottom = std::get<2>(*padding);
  auto padding_left = std::get<3>(*padding);
  auto inner_width = width - padding_right - padding_left;
  auto inner_height = height - padding_top - padding_bottom;
  auto x_domain = getXDomain();
  auto y_domain = getYDomain();

  target->beginGroup("areas");

  for (const auto& area : areas_) {
    std::vector<std::pair<double, double>> area_coords;
    std::vector<std::pair<double, double>> border_top_coords;
    std::vector<std::pair<double, double>> border_bottom_coords;
    std::vector<std::pair<double, double>> point_coords;

    for (int i = 0; i < area.points.size(); ++i) {
      const auto& point = area.points[i];
      auto scaled_x = x_domain->scale(std::get<0>(point));
      auto scaled_y2 = 1.0 - y_domain->scale(std::get<2>(point));
      auto draw_x = padding_left + scaled_x * inner_width;
      auto draw_y2 = padding_top + scaled_y2 * inner_height;

      area_coords.emplace_back(draw_x, draw_y2);
      border_top_coords.emplace_back(draw_x, draw_y2);
      point_coords.emplace_back(draw_x, draw_y2);
    }

    for (int i = area.points.size() - 1; i >= 0; --i) {
      const auto& point = area.points[i];
      auto scaled_x = x_domain->scale(std::get<0>(point));
      auto scaled_y1 = 1.0 - y_domain->scale(std::get<1>(point));
      auto draw_x = padding_left + scaled_x * inner_width;
      auto draw_y1 = padding_top + scaled_y1 * inner_height;

      area_coords.emplace_back(draw_x, draw_y1);

      if (std::get<1>(point) != 0) {
        border_bottom_coords.emplace_back(draw_x, draw_y1);
        point_coords.emplace_back(draw_x, draw_y1);
      }
    }

    target->drawPath(
        area_coords,
        "fill",
        area.border_style == "none" ? 0 : area.border_width,
        area.smooth,
        area.color,
        "area");

    if (area.border_style != "none") {
      target->drawPath(
          border_top_coords,
          area.border_style,
          area.border_width,
          area.smooth,
          area.color,
          "line");

      if (border_bottom_coords.size() > 0) {
        target->drawPath(
            border_bottom_coords,
            area.border_style,
            area.border_width,
            area.smooth,
            area.color,
            "line");
      }
    }

    if (area.point_style != "none") {
      for (const auto &point : point_coords) {
        target->drawPoint(
            point.first,
            point.second,
            area.point_style,
            area.point_size,
            area.color,
            "point");
      }
    }
  }

  target->finishGroup();
}
    ui::Canvas* canvas,
    bool stacked) :
    Drawable(canvas),
    stacked_(stacked) {}

}
}
+277 −77
Original line number Diff line number Diff line
@@ -7,96 +7,81 @@
 * copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */

#ifndef _FNORDMETRIC_AREACHART_H
#define _FNORDMETRIC_AREACHART_H
#include <stdlib.h>
#include <assert.h>
#include "../base/series.h"
#include "axisdefinition.h"
#include "domain.h"
#include "drawable.h"
#include <fnordmetric/base/series.h>
#include <fnordmetric/ui/axisdefinition.h>
#include <fnordmetric/ui/domain.h>
#include <fnordmetric/ui/drawable.h>
#include <fnordmetric/ui/canvas.h>
#include <fnordmetric/ui/colorpalette.h>
#include <fnordmetric/ui/rendertarget.h>

namespace fnordmetric {
namespace ui {
class Canvas;
class Domain;

/**
 * This draws the series data as points.
 *
 * OPTIONS
 *
 *   point_style = {none,circle}; default: none
 *   point_size = default: 2
 *   border_style = {none,solid,dashed}, default: none
 *   border_width = default: 2
 *
 */
class AreaChart : public Drawable {
public:
  static char kDefaultPointStyle[];
  static double kDefaultPointSize;
  static char kDefaultBorderStyle[];
  static double kDefaultBorderWidth;
  static char kDefaultPointSize[];
  static char kDefaultLineStyle[];
  static char kDefaultLineWidth[];

  /**
   * Create a new area chart
   *
   * @param canvas the canvas to draw this chart on. does not transfer ownership
   * @param stacked stack areas?
   * @param x_domain the x domain. does not transfer ownership
   * @param y_domain the y domain. does not transfer ownership
   */
  AreaChart(
      Canvas* canvas,
      NumericalDomain* x_domain = nullptr,
      NumericalDomain* y_domain = nullptr,
      bool stacked = false);

protected:
  bool stacked_;
};

template <typename TX_, typename TY_, typename TZ_>
class AreaChart3D : public AreaChart {
public:
  typedef TX_ TX;
  typedef TY_ TY;
  typedef TZ_ TZ;

  /**
   * Add a (x: string, y: double) series. This will draw an area that covers
   * the surface between height 0 and y for each x.
   * Create a new area chart
   *
   * @param series the series to add. does not transfer ownership
   * @param border_style the border style
   * @param border_width the line width
   * @param point_style the point style
   * @param point_width the point size
   * @param smooth smooth this area?
   * @param line_style the line style ({solid,dashed})
   * @param canvas the canvas to draw this chart on. does not transfer ownership
   * @param stacked stack areas?
   */
  AreaChart3D(Canvas* canvas, bool stacked = false);

  /**
   * Create a new area chart
   *
   * @param canvas the canvas to draw this chart on. does not transfer ownership
   * @param x_domain the x domain. does not transfer ownership
   * @param y_domain the y domain. does not transfer ownership
   * @param stacked stack areas?
   */
  void addSeries(
      Series2D<double, double>* series,
      const std::string& border_style = kDefaultBorderStyle,
      double border_width = kDefaultBorderWidth,
      const std::string& point_style = kDefaultPointStyle,
      double point_size = kDefaultPointSize,
      bool smooth = false);
  AreaChart3D(
      Canvas* canvas,
      AnyDomain* x_domain,
      AnyDomain* y_domain,
      bool stacked = false);

  /**
   * Add a (x: string, y: double, z: double) series. This will draw an area that
   * covers the surface between height y and z for each x.
   *
   * @param series the series to add. does not transfer ownership
   * @param border_style the border style
   * @param border_width the line width
   * @param point_style the point style
   * @param point_width the point size
   * @param smooth smooth this area?
   * @param line_style the line style ({solid,dashed})
   */
  void addSeries(
      Series3D<double, double, double>* series,
      const std::string& border_style = kDefaultBorderStyle,
      double border_width = kDefaultBorderWidth,
      const std::string& point_style = kDefaultPointStyle,
      double point_size = kDefaultPointSize,
      bool smooth = false);
  void addSeries(Series3D<TX, TY, TZ>* series);

  /**
   * Add an axis to the chart. This method should only be called after all
   * series have been added to the chart.
   * Add an axis to the chart.
   *
   * The returned pointer is owned by the canvas object and must not be freed
   * by the caller!
@@ -105,37 +90,252 @@ public:
   */
   AxisDefinition* addAxis(AxisDefinition::kPosition position);

protected:

  NumericalDomain* getXDomain() const;
  NumericalDomain* getYDomain() const;
  /**
   * Get the {x,y} domain of this chart. Will raise an exception if z domain
   * is requested.
   *
   * The returned pointer is owned by the chart object and must not be freed
   * by the caller!
   *
   * @param dimension the dimension (x,y)
   */
  AnyDomain* getDomain(AnyDomain::kDimension dimension) override;

  void render(
      RenderTarget* target,
      int width,
      int height,
      std::tuple<int, int, int, int>* padding) const override;
protected:

  struct Area {
    std::vector<std::tuple<double, double, double>> points;
    std::string border_style;
    double border_width;
    std::string color;
    std::string line_style;
    double line_width;
    std::string point_style;
    double point_size;
    std::string color;
    bool smooth;
    std::vector<std::tuple<TX, TY, TZ>> points;
  };

  Canvas* canvas_;
  NumericalDomain* x_domain_;
  NumericalDomain* y_domain_;
  bool stacked_;
  int num_series_;
  void render(
      RenderTarget* target,
      Viewport* viewport) const override;

  DomainAdapter x_domain_;
  DomainAdapter y_domain_;
  std::vector<Area> areas_;
  mutable std::unique_ptr<NumericalDomain> x_domain_auto_;
  mutable std::unique_ptr<NumericalDomain> y_domain_auto_;
  ColorPalette color_palette_;
};

template <typename TX_, typename TY_>
class AreaChart2D : public AreaChart3D<TX_, TY_, double> {
public:
  typedef TX_ TX;
  typedef TY_ TY;

  /**
   * Create a new area chart
   *
   * @param canvas the canvas to draw this chart on. does not transfer ownership
   * @param stacked stack areas?
   */
  AreaChart2D(Canvas* canvas, bool stacked = false);

  /**
   * Create a new area chart
   *
   * @param canvas the canvas to draw this chart on. does not transfer ownership
   * @param x_domain the x domain. does not transfer ownership
   * @param y_domain the y domain. does not transfer ownership
   * @param stacked stack areas?
   */
  AreaChart2D(
      Canvas* canvas,
      AnyDomain* x_domain,
      AnyDomain* y_domain,
      bool stacked = false);

  /**
   * Add a (x: string, y: double) series. This will draw an area that covers
   * the surface between 0 and y for each x.
   *
   * @param series the series to add. does not transfer ownership
   */
  void addSeries(Series2D<TX, TY>* series);

};

template <typename TX, typename TY, typename TZ>
AreaChart3D<TX, TY, TZ>::AreaChart3D(
    Canvas* canvas,
    bool stacked /* = false */) : 
    AreaChart(canvas, stacked) {}

template <typename TX, typename TY, typename TZ>
AreaChart3D<TX, TY, TZ>::AreaChart3D(
    Canvas* canvas,
    AnyDomain* x_domain,
    AnyDomain* y_domain,
    bool stacked /* = false */) :
    AreaChart(canvas, stacked) {
  x_domain_.reset(x_domain);
  y_domain_.reset(y_domain);
}

template <typename TX, typename TY, typename TZ>
void AreaChart3D<TX, TY, TZ>::addSeries(Series3D<TX, TY, TZ>* series) {
  Domain<TX>* x_domain;
  if (x_domain_.empty()) {
    x_domain = Domain<TX>::mkDomain();
    x_domain_.reset(x_domain, true);
  } else {
    x_domain = x_domain_.getAs<Domain<TX>>();
  }

  Domain<TY>* y_domain;
  if (y_domain_.empty()) {
    y_domain = Domain<TY>::mkDomain();
    y_domain_.reset(y_domain, true);

    auto cont = dynamic_cast<AnyContinuousDomain*>(y_domain);
    if (cont != nullptr) {
      cont->setPadding(
          AnyDomain::kDefaultDomainPadding,
          AnyDomain::kDefaultDomainPadding);
    }
  } else {
    y_domain = y_domain_.getAs<Domain<TY>>();
  }

  if (!series->hasProperty(Series::P_COLOR)) {
    color_palette_.setNextColor(series);
  }

  series->setDefaultProperty(
      Series::P_LINE_STYLE,
      AreaChart::kDefaultLineStyle);

  series->setDefaultProperty(
      Series::P_LINE_WIDTH,
      AreaChart::kDefaultLineWidth);

  series->setDefaultProperty(
      Series::P_POINT_STYLE,
      AreaChart::kDefaultPointStyle);

  series->setDefaultProperty(
      Series::P_POINT_SIZE,
      AreaChart::kDefaultPointSize);

  // FIXPAUL catch conversion errors
  Area area;
  area.color = series->getProperty(Series::P_COLOR);
  area.line_style = series->getProperty(Series::P_LINE_STYLE);
  area.line_width = std::stod(series->getProperty(Series::P_LINE_WIDTH));
  area.point_style = series->getProperty(Series::P_POINT_STYLE);
  area.point_size = std::stod(series->getProperty(Series::P_POINT_SIZE));

  for (const auto& point : series->getData()) {
    x_domain->addValue(point.x());
    y_domain->addValue(point.y());
    y_domain->addValue(point.z());
    area.points.emplace_back(
        point.x(),
        point.y(),
        point.z());
  }

  // FIXPAUL: stacked areas, missing data
  areas_.emplace_back(area);
}

template <typename TX, typename TY, typename TZ>
void AreaChart3D<TX, TY, TZ>::render(
    RenderTarget* target,
    Viewport* viewport) const {
  target->beginGroup("areas");
  target->finishGroup();
}

template <typename TX, typename TY, typename TZ>
AxisDefinition* AreaChart3D<TX, TY, TZ>::addAxis(
    AxisDefinition::kPosition position) {
  auto axis = canvas_->addAxis(position);

  switch (position) {

    case AxisDefinition::TOP:
      axis->setDomain(&x_domain_);
      return axis;

    case AxisDefinition::RIGHT:
      axis->setDomain(&y_domain_);
      return axis;

    case AxisDefinition::BOTTOM:
      axis->setDomain(&x_domain_);
      return axis;

    case AxisDefinition::LEFT:
      axis->setDomain(&y_domain_);
      return axis;

  }
}

template <typename TX, typename TY, typename TZ>
AnyDomain* AreaChart3D<TX, TY, TZ>::getDomain(
    AnyDomain::kDimension dimension) {
  switch (dimension) {
    case AnyDomain::DIM_X:
      return x_domain_.get();

    case AnyDomain::DIM_Y:
      return y_domain_.get();

    case AnyDomain::DIM_Z:
      RAISE(util::RuntimeException, "AreaChart3D does not have a Z domain");
      return nullptr;
  }
}

template <typename TX, typename TY>
AreaChart2D<TX, TY>::AreaChart2D(
    Canvas* canvas,
    bool stacked /* = false */) :
    AreaChart3D<TX, TY, TY>(canvas, stacked) {}

template <typename TX, typename TY>
AreaChart2D<TX, TY>::AreaChart2D(
    Canvas* canvas,
    AnyDomain* x_domain,
    AnyDomain* y_domain,
    bool stacked /* = false */) :
    AreaChart3D<TX, TY, TY>(canvas, x_domain, y_domain, stacked) {}

template <typename TX, typename TY>
void AreaChart2D<TX, TY>::addSeries(Series2D<TX, TY>* series) {
  auto series3d = new Series3D<TX, TY, TY>(); // FIXPAUL: never free'd!
  auto copy_labels = series->hasProperty(Series::P_LABEL);

  for (const auto& point : series->getData()) {
    series3d->addDatum(
        Series::Coord<TX>(point.x()),
        Series::Coord<TY>(nullptr),
        Series::Coord<TY>(point.y()));

    if (copy_labels) {
      series3d->setProperty(
          Series::P_LABEL,
          &series3d->getData().back(),
          series->getProperty(Series::P_LABEL, &point));
    }
  }

  if (series->hasProperty(Series::P_COLOR)) {
    series3d->setDefaultProperty(
        Series::P_COLOR,
        series->getProperty(Series::P_COLOR));
  }

  AreaChart3D<TX, TY, TY>::addSeries(series3d);
}

}
}
#endif
+9 −7
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
#include <string.h>
#include <fnordmetric/base/series.h>
#include <fnordmetric/ui/axisdefinition.h>
#include <fnordmetric/ui/areachart.h>
#include <fnordmetric/ui/barchart.h>
#include <fnordmetric/ui/canvas.h>
#include <fnordmetric/ui/domain.h>
@@ -824,7 +825,6 @@ static fnordmetric::util::UnitTest::TestCase __test_multi_chart_(
      "UITest_TestMultiChart_out.svg.html");
});

/*
static fnordmetric::util::UnitTest::TestCase __test_simple_area_chart_(
    &UITest, "TestSimpleAreaChart", [] () {
  Series2D<double, double> series1("myseries1");
@@ -839,17 +839,19 @@ static fnordmetric::util::UnitTest::TestCase __test_simple_area_chart_(
  ui::ContinuousDomain<double> y_domain(0, 50, false);

  Canvas canvas;
  auto aread_chart = canvas.addChart<AreaChart>(&x_domain, &y_domain);
  aread_chart->addSeries(&series1, "solid", 2, "circle", 4);
  aread_chart->addAxis(AxisDefinition::TOP);
  aread_chart->addAxis(AxisDefinition::RIGHT);
  aread_chart->addAxis(AxisDefinition::BOTTOM);
  aread_chart->addAxis(AxisDefinition::LEFT);
  auto area_chart = canvas.addChart<AreaChart2D<double, double>>(
      &x_domain, &y_domain);
  area_chart->addSeries(&series1);
  area_chart->addAxis(AxisDefinition::TOP);
  area_chart->addAxis(AxisDefinition::RIGHT);
  area_chart->addAxis(AxisDefinition::BOTTOM);
  area_chart->addAxis(AxisDefinition::LEFT);

  compareChart(
      &canvas,
      "UITest_TestSimpleAreaChart_out.svg.html");
});
/*

static fnordmetric::util::UnitTest::TestCase __test_range_area_chart_(
    &UITest, "TestRangeAreaChart", [] () {