Commit 9b6cc8d0 authored by Paul Asmuth's avatar Paul Asmuth
Browse files

update 'bars' element

parent 125080d1
Loading
Loading
Loading
Loading
+15 −6
Original line number Diff line number Diff line
@@ -29,8 +29,10 @@
 */
#include "element_factory.h"
#include "elements/axis.h"
#include "elements/points.h"
#include "elements/bars.h"
#include "elements/lines.h"
#include "elements/points.h"

#include <unordered_map>

namespace plotfx {
@@ -40,11 +42,11 @@ using ElementConfigureFn = std::function<ReturnCode (const Document&, const Prop
static std::unordered_map<std::string, ElementBuilder> elems = {
  {"axis", elem_builder<AxisDefinition>(&axis::configure, &axis::layout, &axis::draw)},
  {
    "points",
    elem_builder<plot::points::PlotPointsConfig>(
        &plot::points::configure,
        &plot::points::layout,
        &plot::points::draw)
    "bars",
    elem_builder<plot::bars::PlotBarsConfig>(
        &plot::bars::configure,
        &plot::bars::layout,
        &plot::bars::draw)
  },
  {
    "lines",
@@ -53,6 +55,13 @@ static std::unordered_map<std::string, ElementBuilder> elems = {
        &plot::lines::layout,
        &plot::lines::draw)
  },
  {
    "points",
    elem_builder<plot::points::PlotPointsConfig>(
        &plot::points::configure,
        &plot::points::layout,
        &plot::points::draw)
  },
};

ReturnCode buildElement(
+311 −0
Original line number Diff line number Diff line
@@ -28,8 +28,8 @@
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "bars.h"
#include <numeric>
#include "plot_bars.h"
#include <plotfx.h>
#include <graphics/path.h>
#include <graphics/brush.h>
@@ -44,8 +44,7 @@ namespace plotfx {
namespace plot {
namespace bars {

static const double kDefaultBarPadding = 0.44;
static const double kDefaultBarGroupPadding = 0.24;
static const double kDefaultBarSizePT = 10;
static const double kDefaultLabelPaddingHorizEM = 0.4;
static const double kDefaultLabelPaddingVertEM = 0.4;

@@ -53,57 +52,66 @@ PlotBarsConfig::PlotBarsConfig() :
    direction(Direction::VERTICAL) {}

ReturnCode draw_horizontal(
    const PlotBarsConfig& config,
    const Rectangle& clip,
    PlotBarsConfig config,
    const LayoutInfo& layout,
    Layer* layer) {
  assert(config.x.size() == config.xoffset.size());
  assert(config.x.size() == config.y.size());
  assert(config.y.size() == config.yoffset.size());

  const double group_cnt = config.groups.size();
  const double groups_cnt = (config.x.size() / group_cnt);

  const double slot_width =
      (clip.h / groups_cnt) *
      (1.0 - kDefaultBarPadding);

  const double bar_width =
      (slot_width / group_cnt) *
      (group_cnt > 1 ? (1.0 - kDefaultBarGroupPadding) : 1.0);
  const auto& clip = layout.content_box;

  /* convert units */
  convert_units(
      {
        bind(&convert_unit_typographic, layer->dpi, layer->font_size.value, _1),
        bind(&convert_unit_user, domain_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.value, _1),
        bind(&convert_unit_user, domain_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.value, _1)
      },
      &*config.sizes.begin(),
      &*config.sizes.end());

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

  for (size_t group_idx = 0; group_idx < config.groups.size(); ++group_idx) {
    const auto group = config.groups[group_idx];

    for (auto i : group.index) {
      auto sx1 = clip.x + config.x[i] * clip.w;
      auto sx2 = clip.x + config.xoffset[i] * clip.w;
      const double sy =
          clip.y + (1.0 - config.y[i]) * clip.h +
          slot_width * -.5 +
          (slot_width / group_cnt) * (group_idx + .5);
    auto size = config.sizes.empty()
        ? from_pt(kDefaultBarSizePT, layer->dpi)
        : config.sizes[i % config.sizes.size()];

    const auto& color = config.colors.empty()
        ? Color{}
        : config.colors[i % config.colors.size()];

      auto size = config.sizes.empty()
          ? Measure{}
          : config.sizes[i % config.sizes.size()];

    FillStyle style;
    style.color = color;

    Path path;
      path.moveTo(sx1, sy - bar_width * 0.5);
      path.lineTo(sx2, sy - bar_width * 0.5);
      path.lineTo(sx2, sy + bar_width * 0.5);
      path.lineTo(sx1, sy + bar_width * 0.5);
    path.moveTo(sx1, sy - size * 0.5);
    path.lineTo(sx2, sy - size * 0.5);
    path.lineTo(sx2, sy + size * 0.5);
    path.lineTo(sx1, sy + size * 0.5);
    path.closePath();


    fillPath(layer, clip, path, style);
  }
  }

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

@@ -116,8 +124,8 @@ ReturnCode draw_horizontal(
        from_em(kDefaultLabelPaddingHorizEM, config.label_font_size));

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

    TextStyle style;
    style.font = config.label_font;
@@ -135,58 +143,65 @@ ReturnCode draw_horizontal(
}

ReturnCode draw_vertical(
    const PlotBarsConfig& config,
    const Rectangle& clip,
    PlotBarsConfig config,
    const LayoutInfo& layout,
    Layer* layer) {
  assert(config.x.size() == config.xoffset.size());
  assert(config.x.size() == config.y.size());
  assert(config.y.size() == config.yoffset.size());

  const double group_cnt = config.groups.size();
  const double groups_cnt = (config.x.size() / group_cnt);
  const auto& clip = layout.content_box;

  /* convert units */
  convert_units(
      {
        bind(&convert_unit_typographic, layer->dpi, layer->font_size.value, _1),
        bind(&convert_unit_user, domain_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.value, _1),
        bind(&convert_unit_user, domain_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.value, _1)
      },
      &*config.sizes.begin(),
      &*config.sizes.end());

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

  // TODO: make configurable
  const double slot_width =
      (clip.w / groups_cnt) *
      (1.0 - kDefaultBarPadding);

  const double bar_width =
      (slot_width / group_cnt) *
      (group_cnt > 1 ? (1.0 - kDefaultBarGroupPadding) : 1.0);

  for (size_t group_idx = 0; group_idx < config.groups.size(); ++group_idx) {
    const auto group = config.groups[group_idx];

    for (auto i : group.index) {
      const double sy1 = clip.y + (1.0 - config.y[i]) * clip.h;
      const double sy2 = clip.y + (1.0 - config.yoffset[i]) * clip.h;
      const double sx =
          clip.x + config.x[i] * clip.w +
          slot_width * -.5 +
          (slot_width / group_cnt) * (group_idx + .5);
    auto size = config.sizes.empty()
        ? from_pt(kDefaultBarSizePT, layer->dpi)
        : config.sizes[i % config.sizes.size()];

    const auto& color = config.colors.empty()
        ? Color{}
        : config.colors[i % config.colors.size()];

      auto size = config.sizes.empty()
          ? Measure{}
          : config.sizes[i % config.sizes.size()];

    FillStyle style;
    style.color = color;

    Path path;
      path.moveTo(sx - bar_width * 0.5, sy1);
      path.lineTo(sx - bar_width * 0.5, sy2);
      path.lineTo(sx + bar_width * 0.5, sy2);
      path.lineTo(sx + bar_width * 0.5, sy1);
    path.moveTo(sx - size * 0.5, sy1);
    path.lineTo(sx - size * 0.5, sy2);
    path.lineTo(sx + size * 0.5, sy2);
    path.lineTo(sx + size * 0.5, sy1);
    path.closePath();

    fillPath(layer, clip, path, style);
  }
  }

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

@@ -199,8 +214,8 @@ ReturnCode draw_vertical(
        from_em(kDefaultLabelPaddingVertEM, config.label_font_size));

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

    TextStyle style;
    style.font = config.label_font;
@@ -219,36 +234,23 @@ ReturnCode draw_vertical(

ReturnCode draw(
    const PlotBarsConfig& config,
    const Rectangle& clip,
    const LayoutInfo& layout,
    Layer* layer) {
  switch (config.direction) {
    case Direction::HORIZONTAL:
      return draw_horizontal(config, clip, layer);
      return draw_horizontal(config, layout, layer);
    case Direction::VERTICAL:
      return draw_vertical(config, clip, layer);
      return draw_vertical(config, layout, layer);
    default:
      return ERROR;
  }
}

ReturnCode build_legend(
ReturnCode layout(
    const PlotBarsConfig& config,
    const std::string& title,
    const std::string& legend_key,
    LegendItemMap* legend) {
  LegendItemGroup legend_items;

  for (const auto& g : config.groups) {
    LegendItem li;
    li.title = g.key.empty() ? title : g.key;
    li.color = config.colors.empty()
        ? Color{}
        : config.colors[g.index[0] % config.colors.size()];

    legend_items.items.emplace_back(li);
  }

  legend_items_add(legend_key, legend_items, legend);
    const Layer& layer,
    LayoutInfo* layout) {
  /* nothing to do */
  return OK;
}

@@ -256,125 +258,48 @@ ReturnCode configure(
    const plist::PropertyList& plist,
    const DataContext& data,
    const Document& doc,
    const DomainMap& scales,
    LegendItemMap* legend,
    const Environment& env,
    PlotBarsConfig* config) {
  SeriesRef data_x = find_maybe(data.defaults, "x");
  SeriesRef data_xoffset;
  SeriesRef data_y = find_maybe(data.defaults, "y");
  SeriesRef data_yoffset;
  SeriesRef data_group = find_maybe(data.defaults, "group");
  SeriesRef data_labels;

  std::string scale_x = SCALE_DEFAULT_X;
  std::string scale_y = SCALE_DEFAULT_Y;

  std::string title;

  std::string legend_key = LEGEND_DEFAULT;

  Direction direction = Direction::VERTICAL;

  std::optional<Color> color;
  SeriesRef colors = find_maybe(data.defaults, "colors");
  DomainConfig color_domain;
  ColorScheme color_palette;
  /* set defaults from environment */
  config->scale_x = env.scale_x;
  config->scale_y = env.scale_y;
  config->label_font = doc.font_sans;
  config->label_font_size = doc.font_size;

  static const ParserDefinitions pdefs = {
    {"x", configure_series_fn(data, &data_x)},
    {"x-offset", configure_series_fn(data, &data_xoffset)},
    {"scale-x", bind(&configure_string, _1, &scale_x)},
    {"y", configure_series_fn(data, &data_y)},
    {"y-offset", configure_series_fn(data, &data_yoffset)},
    {"scale-y", bind(&configure_string, _1, &scale_y)},
    {"direction", bind(&configure_direction, _1, &direction)},
    {"group", configure_series_fn(data, &data_group)},
    {"color", configure_color_opt(&color)},
    {"colors", configure_series_fn(data, &colors)},
    {"labels", configure_series_fn(data, &data_labels)},
    {"title", bind(&configure_string, _1, &title)},
    {"xs", bind(&configure_measures, _1, &config->x)},
    {"ys", bind(&configure_measures, _1, &config->y)},
    {"scale-x", bind(&domain_configure, _1, &config->scale_x)},
    {"scale-x-min", bind(&configure_float_opt, _1, &config->scale_x.min)},
    {"scale-x-max", bind(&configure_float_opt, _1, &config->scale_x.max)},
    {"scale-x-padding", bind(&configure_float, _1, &config->scale_x.padding)},
    {"scale-y", bind(&domain_configure, _1, &config->scale_y)},
    {"scale-y-min", bind(&configure_float_opt, _1, &config->scale_y.min)},
    {"scale-y-max", bind(&configure_float_opt, _1, &config->scale_y.max)},
    {"scale-y-padding", bind(&configure_float, _1, &config->scale_y.padding)},
    {"size", bind(&configure_measures, _1, &config->sizes)},
    {"sizes", bind(&configure_measures, _1, &config->sizes)},
    {"direction", bind(&configure_direction, _1, &config->direction)},
    {"color", configure_vec<Color>(bind(&configure_color, _1, _2), &config->colors)},
    {"colors", configure_vec<Color>(bind(&configure_color, _1, _2), &config->colors)},
    {"labels", bind(&configure_strings, _1, &config->labels)},
  };

  if (auto rc = parseAll(plist, pdefs); !rc) {
    return rc;
  }

  /* check dataset */
  if (!data_x || !data_y) {
    return ReturnCode::error("EARG", "the following properties are required: x, y");
  /* scale autoconfig */
  for (const auto& v : config->x) {
    if (v.unit == Unit::USER) {
      domain_fit(v.value, &config->scale_x);
    }

  if ((data_x->size() != data_y->size()) ||
      (data_xoffset && data_x->size() != data_xoffset->size()) ||
      (data_yoffset && data_x->size() != data_yoffset->size()) ||
      (data_group && data_x->size() != data_group->size())) {
    return ReturnCode::error(
        "EARG",
        "the length of the 'x', 'y', 'y', 'yoffset' and 'group' properties must be equal");
  }

  if (data_labels && (data_x->size() != data_labels->size())) {
    return ReturnCode::error(
        "EARG",
        "the length of the 'x', 'y' and 'labels' properties must be equal");
  }

  /* fetch domains */
  auto domain_x = find_ptr(scales, scale_x);
  if (!domain_x) {
    return ReturnCode::errorf("EARG", "scale not found: $0", scale_x);
  for (const auto& v : config->y) {
    if (v.unit == Unit::USER) {
      domain_fit(v.value, &config->scale_y);
    }

  auto domain_y = find_ptr(scales, scale_y);
  if (!domain_y) {
    return ReturnCode::errorf("EARG", "scale not found: $0", scale_y);
  }

  /* group data */
  if (data_group) {
    if (data_x->size() != data_group->size()) {
      return ERROR;
    }

    config->groups = plotfx::series_group(*data_group);
  } else {
    DataGroup g;
    g.index = std::vector<size_t>(data_x->size());
    std::iota(g.index.begin(), g.index.end(), 0);
    config->groups.emplace_back(g);
  }

  /* return element */
  config->direction = direction;

  //config->x = domain_translate(*domain_x, *data_x);
  //config->xoffset = domain_translate(
  //    *domain_x,
  //    data_xoffset
  //        ? *data_xoffset
  //        : std::vector<Value>(data_x->size(), "0.0"));

  //config->y = domain_translate(*domain_y, *data_y);
  //config->yoffset = domain_translate(
  //    *domain_y,
  //    data_yoffset
  //        ? *data_yoffset
  //        : std::vector<Value>(data_y->size(), "0.0"));

  config->colors = fallback(
      color,
      series_to_colors(colors, color_domain, color_palette),
      groups_to_colors(data_x->size(), config->groups, color_palette));

  config->label_font = doc.font_sans;
  config->label_font_size = doc.font_size;
  if (data_labels) {
    config->labels = *data_labels;
  }

  /* build legend items */
  if (auto rc = build_legend(*config, title, legend_key, legend); !rc) {
    return rc;
  }

  return OK;
+13 −8
Original line number Diff line number Diff line
@@ -46,11 +46,12 @@ namespace bars {
struct PlotBarsConfig {
  PlotBarsConfig();
  Direction direction;
  std::vector<double> x;
  std::vector<double> xoffset;
  std::vector<double> y;
  std::vector<double> yoffset;
  std::vector<DataGroup> groups;
  std::vector<Measure> x;
  std::vector<Measure> xoffset;
  std::vector<Measure> y;
  std::vector<Measure> yoffset;
  DomainConfig scale_x;
  DomainConfig scale_y;
  std::vector<Color> colors;
  std::vector<Measure> sizes;
  std::vector<std::string> labels;
@@ -62,15 +63,19 @@ struct PlotBarsConfig {

ReturnCode draw(
    const PlotBarsConfig& config,
    const Rectangle& clip,
    const LayoutInfo& clip,
    Layer* layer);

ReturnCode layout(
    const PlotBarsConfig& config,
    const Layer& layer,
    LayoutInfo* layout);

ReturnCode configure(
    const plist::PropertyList& plist,
    const DataContext& data,
    const Document& doc,
    const DomainMap& scales,
    LegendItemMap* legend,
    const Environment& env,
    PlotBarsConfig* config);

} // namespace bars
+0 −1
Original line number Diff line number Diff line
@@ -38,7 +38,6 @@
#include "source/element_factory.h"
#include "source/utils/algo.h"
#include "plot_area.h"
#include "plot_bars.h"
#include "plot_labels.h"
#include "legend.h"

+15 −14
Original line number Diff line number Diff line
width: 1200px;
height: 480px;

data: csv(tests/testdata/histogram.csv);
y: var1;
x: var0;

axis-top: off;
width: 400px;
height: 280px;

scale-x-padding: 0.5;
scale-y: log;
scale-x: categorical;

scale-y-max: 80000;
bars {
  xs: csv(tests/testdata/histogram.csv, var0);
  ys: csv(tests/testdata/histogram.csv, var1);
  color: #888;
  size: 14pt;
}

grid-x: geom;
axis {
  position: bottom;
  layout: linear(1, align 1);
}

layer {
  type: bars;
  labels: var1;
axis {
  position: left;
}
Loading