Commit 82a5d1c1 authored by Paul Asmuth's avatar Paul Asmuth
Browse files

PointChart3D

parent 07d2b5ad
Loading
Loading
Loading
Loading
+2 −174
Original line number Diff line number Diff line
@@ -7,188 +7,16 @@
 * copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include "canvas.h"
#include "pointchart.h"
#include "rendertarget.h"
#include <fnordmetric/util/runtimeexception.h>

namespace fnordmetric {
namespace ui {

double PointChart::kDefaultPointSize = 3.0f;
char PointChart::kDefaultPointStyle[] = "circle";

PointChart::PointChart(
    Canvas* canvas,
    NumericalDomain* x_domain /* = nullptr */,
    NumericalDomain* y_domain /* = nullptr */) :
    canvas_(canvas),
    x_domain_(x_domain),
    y_domain_(y_domain),
    num_series_(0) {}

void PointChart::addSeries(
      Series2D<double, double>* series,
      const std::string& point_style /* = kDefaultPointStyle */,
      double point_size /* = kDefaultPointSize */) {
  const auto& color = seriesColor(series);

  for (const auto& spoint : series->getData()) {
    Point point;
    point.x = std::get<0>(spoint);
    point.y = std::get<1>(spoint);
    point.size = point_size;
    point.type = point_style; // FIXPAUL too many copies
    point.color = color; // FIXPAUL too many copies
    points_.emplace_back(point);
  }
}

void PointChart::addSeries(
    Series3D<double, double, double>* series,
    const std::string& point_style /* = kDefaultPointStyle */) {
  std::string color = seriesColor(series);

  for (const auto& spoint : series->getData()) {
    Point point;
    point.x = std::get<0>(spoint);
    point.y = std::get<1>(spoint);
    point.size = std::get<2>(spoint);
    point.type = point_style; // FIXPAUL too many copies
    point.color = color; // FIXPAUL too many copies
    points_.emplace_back(point);
  }
}

AxisDefinition* PointChart::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* PointChart::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& point : points_) {
      if (point.x > x_max) {
        x_max = point.x;
      }
      if (point.x < x_min) {
        x_min = point.x;
      }
    }

    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* PointChart::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& point : points_) {
      if (point.y > y_max) {
        y_max = point.y;
      }
      if (point.y < y_min) {
        y_min = point.y;
      }
    }

    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 PointChart::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("points");

  for (const auto& point : points_) {
    auto draw_x = padding_left + x_domain->scale(point.x) * inner_width;
    auto draw_y = padding_top + (1.0 - y_domain->scale(point.y)) * inner_height;

    target->drawPoint(
      draw_x,
      draw_y,
      point.type,
      point.size,
      point.color,
      "point");
  }

  target->finishGroup();
}
PointChart::PointChart(ui::Canvas* canvas) : Drawable(canvas) {}

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

#ifndef _FNORDMETRIC_POINTCHART_H
#define _FNORDMETRIC_POINTCHART_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 = {dot,square}, default: horizontal
 *   point_size = default: 5
 *
 */
class PointChart : public Drawable {
public:
  static double kDefaultPointSize;
  static char kDefaultPointStyle[];
  PointChart(ui::Canvas* canvas);
};

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

  /**
   * Create a new point chart
   * Create a new line 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
   */
  PointChart(
      Canvas* canvas,
      NumericalDomain* x_domain = nullptr,
      NumericalDomain* y_domain = nullptr);
  PointChart3D(Canvas* canvas);

  /**
   * Add a (x: string, y: double) series. This will draw one point for each
   * point in the series.
   * Create a new line chart
   *
   * @param series the series to add. does not transfer ownership
   * @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
   */
  void addSeries(
      Series2D<double, double>* series,
      const std::string& point_style = kDefaultPointStyle,
      double point_size = kDefaultPointSize);
  PointChart3D(Canvas* canvas, AnyDomain* x_domain, AnyDomain* y_domain);

  /**
   * Add a (x: string, y: double, z: double) series. This will draw one point
   * for each point in the series with size Z.
   *
   * @param series the series to add. does not transfer ownership
   */
  void addSeries(
      Series3D<double, double, double>* series,
      const std::string& point_style = kDefaultPointStyle);
  void addSeries(Series3D<TX, TY, TZ>* series);

  /**
   * Add an axis to the chart. This method should only be called after all
@@ -80,34 +71,152 @@ public:
   */
   AxisDefinition* addAxis(AxisDefinition::kPosition position);

protected:
  /**
   * 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;

  NumericalDomain* getXDomain() const;
  NumericalDomain* getYDomain() const;
protected:

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

  struct Point {
    double x;
    double y;
    double size;
    std::string type;
    std::string color;
  };
      Viewport* viewport) const override;

  Canvas* canvas_;
  NumericalDomain* x_domain_;
  NumericalDomain* y_domain_;
  int num_series_;
  std::vector<Point> points_;
  mutable std::unique_ptr<NumericalDomain> x_domain_auto_;
  mutable std::unique_ptr<NumericalDomain> y_domain_auto_;
  DomainAdapter x_domain_;
  DomainAdapter y_domain_;
  std::vector<Series3D<TX, TY, TZ>*> series_;
  ColorPalette color_palette_;
};

template <typename TX, typename TY, typename TZ>
PointChart3D<TX, TY, TZ>::PointChart3D(Canvas* canvas) : PointChart(canvas) {}

template <typename TX, typename TY, typename TZ>
PointChart3D<TX, TY, TZ>::PointChart3D(
    Canvas* canvas,
    AnyDomain* x_domain,
    AnyDomain* y_domain) :
    PointChart(canvas) {
  x_domain_.reset(x_domain);
  y_domain_.reset(y_domain);
}

template <typename TX, typename TY, typename TZ>
void PointChart3D<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);
  } else {
    y_domain = y_domain_.getAs<Domain<TY>>();
  }

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

  series_.push_back(series);

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

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

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

  for (const auto& series : series_) {
    std::vector<std::pair<double, double>> coords;

    // FIXPAUL catch conversion excpetion
    auto point_style = series->getProperty(Series::P_POINT_STYLE);
    auto color = series->getProperty(Series::P_COLOR);

    for (const auto& point : series->getData()) {
      auto x = x_domain_.getAs<Domain<TX>>()->scale(point.x());
      auto y = y_domain_.getAs<Domain<TY>>()->scale(point.y());
      auto ss_x = viewport->paddingLeft() + x * viewport->innerWidth();
      auto ss_y = viewport->paddingTop() + (1.0 - y) * viewport->innerHeight();

      if (point_style != "none") {
        target->drawPoint(
            ss_x,
            ss_y,
            point_style,
            point.z(),
            color,
            "point");
      }
    }
  }

  target->finishGroup();
}

template <typename TX, typename TY, typename TZ>
AxisDefinition* PointChart3D<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* PointChart3D<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, "PointChart3D does not have a Z domain");
      return nullptr;
  }
}

}
}
#endif
+7 −5
Original line number Diff line number Diff line
@@ -10,9 +10,10 @@
#include <fnordmetric/base/series.h>
#include <fnordmetric/ui/axisdefinition.h>
#include <fnordmetric/ui/barchart.h>
#include <fnordmetric/ui/linechart.h>
#include <fnordmetric/ui/canvas.h>
#include <fnordmetric/ui/domain.h>
#include <fnordmetric/ui/linechart.h>
#include <fnordmetric/ui/pointchart.h>
#include <fnordmetric/ui/svgtarget.h>
#include <fnordmetric/util/unittest.h>

@@ -634,7 +635,7 @@ static fnordmetric::util::UnitTest::TestCase __test_simple_point_chart_(
  series2.addDatum(-50, -33);

  Canvas canvas;
  auto point_chart = canvas.addChart<PointChart>();
  auto point_chart = canvas.addChart<PointChart2D<double, double>>();
  point_chart->addSeries(&series1);
  point_chart->addSeries(&series2);
  point_chart->addAxis(AxisDefinition::TOP);
@@ -646,7 +647,7 @@ static fnordmetric::util::UnitTest::TestCase __test_simple_point_chart_(
      &canvas,
      "UITest_TestSimplePointChart_out.svg.html");
});

*/
static fnordmetric::util::UnitTest::TestCase __test_variablesize_point_chart_(
    &UITest, "TestVariableSizePointChart", [] () {
  Series3D<double, double, double> series1("myseries1");
@@ -666,7 +667,7 @@ static fnordmetric::util::UnitTest::TestCase __test_variablesize_point_chart_(
  series2.addDatum(-50, -23, 17);

  Canvas canvas;
  auto point_chart = canvas.addChart<PointChart>();
  auto point_chart = canvas.addChart<PointChart3D<double, double, double>>();
  point_chart->addSeries(&series1);
  point_chart->addSeries(&series2);
  point_chart->addAxis(AxisDefinition::TOP);
@@ -678,7 +679,8 @@ static fnordmetric::util::UnitTest::TestCase __test_variablesize_point_chart_(
      &canvas,
      "UITest_TestVariableSizePointChart_out.svg.html");
});
*/

/*
static fnordmetric::util::UnitTest::TestCase __test_simple_line_chart_(
    &UITest, "TestSimpleLineChart", [] () {
  Series2D<double, double> series1("myseries1");