Commit 4a6b87ce authored by Paul Asmuth's avatar Paul Asmuth
Browse files

add the plot/vectors element

parent f74d723f
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -28,8 +28,9 @@
#include "elements/plot/grid.h"
#include "elements/plot/labels.h"
#include "elements/plot/lines.h"
#include "elements/chart/linechart.h"
#include "elements/plot/points.h"
#include "elements/plot/vectors.h"
#include "elements/chart/linechart.h"
#include "elements/chart/scatterplot.h"
#include "elements/layout/box.h"
#include "elements/legend.h"
@@ -61,6 +62,7 @@ fviz_t* fviz_init() {
  element_bind(elems, "plot/labels", bind(elements::plot::labels::build, _1, _2, _3));
  element_bind(elems, "plot/lines", bind(elements::plot::lines::build, _1, _2, _3));
  element_bind(elems, "plot/points", bind(elements::plot::points::build, _1, _2, _3));
  element_bind(elems, "plot/vectors", bind(elements::plot::vectors::build, _1, _2, _3));
  element_bind(elems, "legend", bind(elements::legend::build, _1, _2, _3));
  element_bind(elems, "legend/item", bind(elements::legend::item::build, _1, _2, _3));
  element_bind(elems, "layout/box", bind(elements::layout::box::build, _1, _2, _3));
+1 −0
Original line number Diff line number Diff line
@@ -352,6 +352,7 @@ ReturnCode build(
    {"lines", bind(&configure_geom, "plot/lines", _1, &geoms, &x, &y)},
    {"labels", bind(&configure_geom, "plot/labels", _1, &geoms, &x, &y)},
    {"points", bind(&configure_geom, "plot/points", _1, &geoms, &x, &y)},
    {"vectors", bind(&configure_geom, "plot/vectors", _1, &geoms, &x, &y)},

    /* grid & legend */
    {"grid", bind(&expr_to_copy, _1, &grid_opts)},
+263 −0
Original line number Diff line number Diff line
/**
 * This file is part of the "fviz" project
 *   Copyright (c) 2018 Paul Asmuth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "vectors.h"
#include "data.h"
#include "sexpr.h"
#include "sexpr_conv.h"
#include "sexpr_util.h"
#include "core/environment.h"
#include "core/color_reader.h"
#include "core/typographic_map.h"
#include "core/typographic_reader.h"
#include "core/layout.h"
#include "core/marker.h"
#include "core/scale.h"
#include "graphics/path.h"
#include "graphics/brush.h"
#include "graphics/text.h"
#include "graphics/layout.h"

#include <numeric>

using namespace std::placeholders;

namespace fviz::elements::plot::vectors {

static const double kDefaultPointSizePT = 4;
static const double kDefaultLabelPaddingEM = 0.4;

struct PlotPointsConfig {
  std::vector<Measure> x;
  std::vector<Measure> y;
  std::vector<Measure> dx;
  std::vector<Measure> dy;
  ScaleConfig scale_x;
  ScaleConfig scale_y;
  Color color;
  std::vector<Color> colors;
  Measure size;
  std::vector<Measure> sizes;
  Marker shape;
  std::vector<Marker> shapes;
  std::vector<std::string> labels;
  FontInfo label_font;
  Measure label_padding;
  Measure label_font_size;
  Color label_color;
  LayoutSettings layout;
};

ReturnCode draw(
    std::shared_ptr<PlotPointsConfig> config,
    const LayoutInfo& layout,
    Layer* layer) {
  const auto& clip = layout.content_box;

  /* convert units */
  convert_units(
      {
        bind(&convert_unit_typographic, layer->dpi, layer->font_size, _1),
        bind(&convert_unit_user, scale_translate_fn(config->scale_x), _1),
        bind(&convert_unit_relative, clip.w, _1)
      },
      &*config->x.begin(),
      &*config->x.end());

  convert_units(
      {
        bind(&convert_unit_typographic, layer->dpi, layer->font_size, _1),
        bind(&convert_unit_user, scale_translate_fn(config->scale_y), _1),
        bind(&convert_unit_relative, clip.h, _1)
      },
      &*config->y.begin(),
      &*config->y.end());

  convert_units(
      {
        bind(&convert_unit_typographic, layer->dpi, layer->font_size, _1)
      },
      &*config->sizes.begin(),
      &*config->sizes.end());

  convert_unit_typographic(
      layer->dpi,
      layer->font_size,
      &config->size);

  /* draw markers */
  for (size_t i = 0; i < config->x.size(); ++i) {
    auto sx = clip.x + config->x[i];
    auto sy = clip.y + clip.h - config->y[i];

    const auto& color = config->colors.empty()
        ? config->color
        : config->colors[i % config->colors.size()];

    auto size = config->sizes.empty()
        ? config->size
        : config->sizes[i % config->sizes.size()];

    auto shape = config->shapes.empty()
        ? config->shape
        : config->shapes[i % config->shapes.size()];

    if (auto rc = shape(Point(sx, sy), size, color, layer); !rc) {
      return rc;
    }
  }

  /* draw labels */
  for (size_t i = 0; i < config->labels.size(); ++i) {
    const auto& label_text = config->labels[i];

    auto size = config->sizes.empty()
        ? 0
        : config->sizes[i % config->sizes.size()].value;

    auto label_padding = size * 0.5 + measure_or(
        config->label_padding,
        from_em(kDefaultLabelPaddingEM, config->label_font_size));

    Point p(
        clip.x + config->x[i],
        clip.y + clip.h - config->y[i] - label_padding);

    TextStyle style;
    style.font = config->label_font;
    style.color = config->label_color;
    style.font_size = config->label_font_size;

    auto ax = HAlign::CENTER;
    auto ay = VAlign::BOTTOM;
    if (auto rc = drawTextLabel(label_text, p, ax, ay, style, layer); rc != OK) {
      return rc;
    }
  }

  return OK;
}

ReturnCode build(
    const Environment& env,
    const Expr* expr,
    ElementRef* elem) {
  /* set defaults from environment */
  auto c = std::make_shared<PlotPointsConfig>();
  c->color = env.foreground_color;
  c->size = from_pt(kDefaultPointSizePT);
  c->shape = marker_create_disk();
  c->label_font = env.font;
  c->label_font_size = env.font_size;

  /* parse properties */
  std::vector<std::string> data_x;
  std::vector<std::string> data_y;
  std::vector<std::string> data_dx;
  std::vector<std::string> data_dy;
  std::vector<std::string> data_colors;
  std::vector<std::string> data_sizes;
  ColorMap color_map;

  auto config_rc = expr_walk_map(expr_next(expr), {
    {"data-x", bind(&data_load_strings, _1, &data_x)},
    {"data-y", bind(&data_load_strings, _1, &data_y)},
    {"data-dx", bind(&data_load_strings, _1, &data_dx)},
    {"data-dy", bind(&data_load_strings, _1, &data_dy)},
    {"data-color", bind(&data_load_strings, _1, &data_colors)},
    {"data-shape", bind(&marker_configure_list, _1, &c->shapes)},
    {"limit-x", bind(&expr_to_float64_opt_pair, _1, &c->scale_x.min, &c->scale_x.max)},
    {"limit-x-min", bind(&expr_to_float64_opt, _1, &c->scale_x.min)},
    {"limit-x-max", bind(&expr_to_float64_opt, _1, &c->scale_x.max)},
    {"limit-y", bind(&expr_to_float64_opt_pair, _1, &c->scale_y.min, &c->scale_y.max)},
    {"limit-y-min", bind(&expr_to_float64_opt, _1, &c->scale_y.min)},
    {"limit-y-max", bind(&expr_to_float64_opt, _1, &c->scale_y.max)},
    {"scale-x", bind(&scale_configure_kind, _1, &c->scale_x)},
    {"scale-y", bind(&scale_configure_kind, _1, &c->scale_y)},
    {"scale-x-padding", bind(&expr_to_float64, _1, &c->scale_x.padding)},
    {"scale-y-padding", bind(&expr_to_float64, _1, &c->scale_y.padding)},
    {"shape", bind(&marker_configure, _1, &c->shape)},
    {"color", bind(&color_read, env, _1, &c->color)},
    {"color-map", bind(&color_map_read, env, _1, &color_map)},
  });

  if (!config_rc) {
    return config_rc;
  }

  /* scale configuration */
  if (auto rc = data_to_measures(data_x, c->scale_x, &c->x); !rc){
    return rc;
  }

  if (auto rc = data_to_measures(data_y, c->scale_y, &c->y); !rc){
    return rc;
  }

  if (auto rc = data_to_measures(data_dx, c->scale_x, &c->dx); !rc){
    return rc;
  }

  if (auto rc = data_to_measures(data_dy, c->scale_y, &c->dy); !rc){
    return rc;
  }

  for (const auto& v : c->x) {
    if (v.unit == Unit::USER) {
      scale_fit(v.value, &c->scale_x);
    }
  }

  for (const auto& v : c->y) {
    if (v.unit == Unit::USER) {
      scale_fit(v.value, &c->scale_y);
    }
  }

  /* check configuration */
  if (c->x.size() != c->y.size() ||
      c->x.size() != c->dx.size() ||
      c->x.size() != c->dy.size()) {
    return error(
        ERROR,
        "the length of the 'data-x' and 'data-y' properties must be equal");
  }

  /* convert color data */
  for (const auto& value : data_colors) {
    Color color;
    if (color_map) {
      if (auto rc = color_map(value, &color); !rc) {
        return rc;
      }
    } else {
      if (auto rc = color.parse(value); !rc) {
        return errorf(
            ERROR,
            "invalid data; can't parse '{}' as a color hex code; maybe you "
            "forgot to set the 'color-map' option?",
            value);
      }
    }

    c->colors.push_back(color);
  }

  /* return element */
  *elem = std::make_shared<Element>();
  (*elem)->draw = bind(&draw, c, _1, _2);
  return OK;
}

} // namespace fviz::elements::plot::vectors
+25 −0
Original line number Diff line number Diff line
/**
 * This file is part of the "fviz" project
 *   Copyright (c) 2018 Paul Asmuth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once
#include "element.h"

namespace fviz::elements::plot::vectors {

ReturnCode build(
    const Environment& env,
    const Expr* expr,
    ElementRef* elem);

} // namespace fviz::elements::plot::vectors
+119 −0
Original line number Diff line number Diff line
name: plot/vectors
desc: |
  The `plot/vectors` element is used to create vector "field" plots.

reference: |
  (plot/vectors <options>)

option_summary: true

properties:
  - title: "Vector Options"
    anchor: vector-options
    properties:
      - name: data-x
        desc: |
          Set the 'x' dataset for the plot. The 'x' dataset will be used to
          calculate the horizontal position of geometry.
        desc_code: |
          data-x (<values>...)
          data-x (csv <file> <column>)
        examples: |
          ;; list of static values
          data-x (10px 20px 30px)

          ;; load a csv file
          data-x (csv myfile.csv mycolumn)
      - name: data-y
        desc: |
          Set the 'y' dataset for the plot. The 'y' dataset will be used to
          calculate the vertical position of geometry.
        desc_code: |
          data-y (<values>...)
          data-y (csv <file> <column>)
        examples: |
          ;; list of static values
          data-y (10px 20px 30px)

          ;; load a csv file
          data-y (csv myfile.csv mycolumn)
      - name: data-dx
        desc: |
          Set the 'dx' dataset for the plot. The 'dx' dataset will be used to
          calculate the x component of the vector.
        desc_code: |
          data-dx (<values>...)
          data-dx (csv <file> <column>)
        examples: |
          ;; list of static values
          data-dx (3 1 5)

          ;; load a csv file
          data-dx (csv myfile.csv mycolumn)
      - name: data-dy
        desc: |
          Set the 'dy' dataset for the plot. The 'dy' dataset will be used to
          calculate the y component of the vector.
        desc_code: |
          data-dy (<values>...)
          data-dy (csv <file> <column>)
        examples: |
          ;; list of static values
          data-dy (1 4 2)

          ;; load a csv file
          data-dy (csv myfile.csv mycolumn)
      - name: data-color
        desc: |
          Set the 'color' dataset for the plot. The 'color' dataset will be used to
          calculate the color of vectors. The mapping of input values to colors
          is controlled by the `color-map` option. If no explicit `color-map`
          option is provided, the values `data-color` will be interpreted as
          hex color codes.
        desc_code: |
          data-color (<values>...)
          data-color (csv <file> <column>)
        examples: |
          ;; list of static values
          data-color (#06c #c06 #06c)

          ;; load a csv file
          data-color (csv myfile.csv mycolors)
      - name: data-shape
        desc: |
          Set the 'shape' dataset for the plot. The 'shape' dataset will be used to
          choose the marker shape of vectors.
        desc_code: |
          data-size (<values>...)
          data-size (csv <file> <column>)
        examples: |
          ;; list of static values
          data-size (hexagon circle square)

          ;; load a csv file
          data-size (csv myfile.csv myshapes)
      - name: color
        desc: |
          Set the point color. Note that this value is only used if no data-colors
          option is specified.
        desc_code: |
          color <color>
      - name: color-map
        desc: |
          Set the point color map. If no map is specified, the values in `data-color`
          will be interpreted as hex color values.
        desc_code: |
          color-map <color-map>
      - name: shape
        desc: |
          Set the marker shape for the plot. Note that this value is only used i
          no data-shape option is specified.
        desc_code: |
          marker-shape (<marker-shape>)
        examples: |
          ;; set the marker shape to 'hexagon'
          marker-shape (hexagon)

  - title: "Scale Options"
    anchor: scale-options
    inherit: ["elements/plot", "scale-options"]
Loading