Commit 07217a99 authored by Paul Asmuth's avatar Paul Asmuth
Browse files

add the 'categorical' scale type and axis layout

parent 7cc4aa38
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -118,6 +118,12 @@ Formatter format_string() {
  };
}

Formatter format_noop() {
  return [] (size_t idx, const std::string& v) -> std::string {
    return {};
  };
}

ReturnCode format_configure_string(
    const Expr* expr,
    Formatter* formatter) {
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ Formatter format_decimal_scientific(size_t precision);
Formatter format_datetime(const std::string& fmt);
Formatter format_string();
Formatter format_custom(const std::vector<std::string>& values);
Formatter format_noop();

ReturnCode format_configure(
    const Expr* expr,
+47 −3
Original line number Diff line number Diff line
@@ -195,12 +195,24 @@ ReturnCode scale_configure_kind(
      continue;
    }

    if (expr_is_value(expr, "categorical")) {
      domain->kind = ScaleKind::CATEGORICAL;

      expr = expr_next(expr);
      if (auto rc = expr_to_strings(expr, &domain->categories); !rc) {
        return rc;
      }

      continue;
    }

    return err_invalid_value(expr_inspect(expr), {
      "linear",
      "log",
      "logarithmic",
      "invert",
      "inverted"
      "inverted",
      "categorical"
    });
  }

@@ -209,12 +221,14 @@ ReturnCode scale_configure_kind(

ReturnCode scale_layout_linear(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout,
    double step,
    std::optional<double> o_begin,
    std::optional<double> o_end) {
  layout->ticks.clear();
  layout->labels.clear();
  layout->label_text.clear();

  auto begin = std::max(o_begin.value_or(scale_min(domain)), scale_min(domain));
  auto end = std::min(o_end.value_or(scale_max(domain)), scale_max(domain));
@@ -223,11 +237,12 @@ ReturnCode scale_layout_linear(
    return {ERROR, "too many ticks"};
  }

  size_t label_idx = 0;
  size_t idx = 0;
  for (auto v = begin; v <= end; v += step) {
    auto vp = scale_translate(domain, v);
    layout->ticks.emplace_back(vp);
    layout->labels.emplace_back(vp);
    layout->label_text.emplace_back(label_format(idx++, std::to_string(v))); // FIXME
  }

  return OK;
@@ -235,15 +250,19 @@ ReturnCode scale_layout_linear(

ReturnCode scale_layout_subdivide(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout,
    uint32_t divisions) {
  layout->ticks.clear();
  layout->labels.clear();
  layout->label_text.clear();

  for (size_t i = 0; i <= divisions; ++i) {
    auto o = (1.0f / divisions) * i;
    layout->ticks.emplace_back(o);
    layout->labels.emplace_back(o);
    layout->label_text.emplace_back(
        label_format(i, std::to_string(scale_untranslate(domain, o)))); // FIXME
  }

  return OK;
@@ -251,11 +270,13 @@ ReturnCode scale_layout_subdivide(

ReturnCode scale_layout_discrete(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout) {
  uint32_t step = 1;
  uint32_t range = scale_max(domain) - scale_min(domain);

  layout->labels.clear();
  layout->label_text.clear();
  layout->ticks.clear();

  for (size_t i = 0; i <= range; i += step) {
@@ -267,6 +288,8 @@ ReturnCode scale_layout_discrete(

    if (o1 >= 0 && o2 <= 1) {
      layout->labels.emplace_back(o);
      layout->label_text.emplace_back(
          label_format(i - 1, std::to_string(scale_untranslate(domain, o)))); // FIXME
    }

    if (o1 >= 0 && o1 <= 1) {
@@ -281,6 +304,25 @@ ReturnCode scale_layout_discrete(
  return OK;
}

ReturnCode scale_layout_categorical(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout) {
  layout->labels.clear();
  layout->label_text.clear();
  layout->ticks.clear();

  auto n = domain.categories.size();
  for (size_t i = 0; i < n; ++i) {
    auto o =  double(i + 1) / (n + 1);
    layout->labels.emplace_back(o);
    layout->label_text.emplace_back(label_format(i, domain.categories[i]));
    layout->ticks.push_back(o);
  }

  return OK;
}

ReturnCode scale_configure_layout_linear(
    const Expr* expr,
    ScaleLayoutFn* layout) {
@@ -318,6 +360,7 @@ ReturnCode scale_configure_layout_linear(
      &scale_layout_linear,
      _1,
      _2,
      _3,
      step,
      begin,
      end);
@@ -350,6 +393,7 @@ ReturnCode scale_configure_layout_subdivide(
      &scale_layout_subdivide,
      _1,
      _2,
      _3,
      subdivisions);

  return OK;
@@ -376,7 +420,7 @@ ReturnCode scale_configure_layout(
  }

  if (expr_is_value(expr, "discrete")) {
    *layout = bind(&scale_layout_discrete, _1, _2);
    *layout = bind(&scale_layout_discrete, _1, _2, _3);
    return OK;
  }

+17 −2
Original line number Diff line number Diff line
@@ -22,11 +22,12 @@
#include "return_code.h"
#include "sexpr.h"
#include "color_scheme.h"
#include "format.h"

namespace fviz {

enum class ScaleKind {
  LINEAR, LOGARITHMIC
  LINEAR, LOGARITHMIC, CATEGORICAL
};

struct ScaleLimitHints {
@@ -43,14 +44,20 @@ struct ScaleConfig {
  bool inverted;
  double padding;
  std::shared_ptr<ScaleLimitHints> limit_hints;
  std::vector<std::string> categories;
};

struct ScaleLayout {
  std::vector<double> ticks;
  std::vector<double> labels;
  std::vector<std::string> label_text;
};

using ScaleLayoutFn = std::function<void (const ScaleConfig&, ScaleLayout*)>;
using ScaleLayoutFn = std::function<
    void (
        const ScaleConfig&,
        const Formatter&,
        ScaleLayout*)>;

void scale_fit(double value, ScaleConfig* domain);

@@ -77,6 +84,7 @@ ReturnCode scale_configure_kind(

ReturnCode scale_layout_linear(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout,
    double step,
    std::optional<double> begin,
@@ -84,11 +92,18 @@ ReturnCode scale_layout_linear(

ReturnCode scale_layout_subdivide(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout,
    uint32_t divisions);

ReturnCode scale_layout_discrete(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout);

ReturnCode scale_layout_categorical(
    const ScaleConfig& domain,
    const Formatter& label_format,
    ScaleLayout* layout);

ReturnCode scale_configure_layout(
+22 −17
Original line number Diff line number Diff line
@@ -131,15 +131,6 @@ void axis_convert_units(AxisDefinition* config, const Layer& layer) {
      &config->tick_length);
}

std::string axis_get_label(
    const AxisDefinition& axis,
    size_t idx,
    double offset) {
  const auto& domain = axis.scale;
  auto value = scale_untranslate(domain, offset);
  return axis.label_formatter(idx, std::to_string(value));
}

ReturnCode axis_layout_labels(
    const AxisDefinition& axis,
    const AxisPosition& axis_position,
@@ -147,13 +138,13 @@ ReturnCode axis_layout_labels(
    double* margin) {
  /* compute scale layout */
  ScaleLayout slayout;
  axis.scale_layout(axis.scale, &slayout);
  axis.scale_layout(axis.scale, axis.label_formatter, &slayout);

  /* compute label margin */
  double label_margin = 0;
  for (size_t i = 0; i < slayout.labels.size(); ++i) {
    auto tick = slayout.labels[i];
    auto label_text = axis_get_label(axis, i, tick);
    auto label_text = slayout.label_text[i];

    TextStyle style;
    style.font = axis.label_font;
@@ -348,7 +339,7 @@ static ReturnCode axis_draw_vertical(
    Layer* target) {
  /* compute layout */
  ScaleLayout slayout;
  axis_config.scale_layout(axis_config.scale, &slayout);
  axis_config.scale_layout(axis_config.scale, axis_config.label_formatter, &slayout);

  /* draw axis line */
  strokeLine(target, {x, y0}, {x, y1}, axis_config.border_style);
@@ -409,7 +400,7 @@ static ReturnCode axis_draw_vertical(

  for (size_t i = 0; i < slayout.labels.size(); ++i) {
    auto tick = slayout.labels[i];
    auto label_text = axis_get_label(axis_config, i, tick);
    auto label_text = slayout.label_text[i];

    Point p;
    p.x = x + label_padding * label_position;
@@ -529,7 +520,10 @@ static ReturnCode axis_draw_horizontal(
    Layer* target) {
  /* compute layout */
  ScaleLayout slayout;
  axis_config.scale_layout(axis_config.scale, &slayout);
  axis_config.scale_layout(
      axis_config.scale,
      axis_config.label_formatter,
      &slayout);

  /* draw axis line */
  strokeLine(target, {x0, y}, {x1, y}, axis_config.border_style);
@@ -590,7 +584,7 @@ static ReturnCode axis_draw_horizontal(

  for (size_t i = 0; i < slayout.labels.size(); ++i) {
    auto tick = slayout.labels[i];
    auto label_text = axis_get_label(axis_config, i, tick);
    auto label_text = slayout.label_text[i];

    Point p;
    p.x = x0 + (x1 - x0) * tick;
@@ -760,7 +754,6 @@ ReturnCode build(const Environment& env, const Expr* expr, ElementRef* elem) {
  config->title_font = env.font;
  config->title_font_size = env.font_size;
  config->title_color = env.text_color;
  config->scale_layout = bind(&scale_layout_subdivide, _1, _2, 10);
  config->border_style.line_width = from_pt(1);
  config->border_style.color = env.border_color;

@@ -838,9 +831,21 @@ ReturnCode build(const Environment& env, const Expr* expr, ElementRef* elem) {
    }
  }

  if (!config->scale_layout) {
    if (config->scale.kind == ScaleKind::CATEGORICAL) {
      config->scale_layout = bind(&scale_layout_categorical, _1, _2, _3);
    } else {
      config->scale_layout = bind(&scale_layout_subdivide, _1, _2, _3, 10);
    }
  }

  if (!config->label_formatter) {
    if (config->scale.kind == ScaleKind::CATEGORICAL) {
      config->label_formatter = format_string();
    } else {
      config->label_formatter = format_decimal_fixed(1);
    }
  }

  switch (config->tick_position) {
    case AxisLabelPosition::OUTSIDE:
Loading