Skip to content

Commit

Permalink
Merge pull request #618 from vizzuhq/axis_refactor_v11c2
Browse files Browse the repository at this point in the history
Axis refactor v11f - Add split style parameter for plot.axes
  • Loading branch information
schaumb authored Nov 26, 2024
2 parents 3c6b916 + 0b09de3 commit 717d4ef
Show file tree
Hide file tree
Showing 29 changed files with 209 additions and 154 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
- Add meaning to crossing interlacing.
- Do not draw dimension axis labels when the middle of the text is off the plot.

### Added

- Add spacing property for plot axis style structure.

## [0.15.0] - 2024-10-28

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions src/apps/weblib/typeschema-api/styles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ definitions:
interlacing:
$ref: Interlacing
nullable: true
spacing:
type: string
mask: /:number:%/
nullable: true

Plot:
$extends: [Padding, Box]
Expand Down
2 changes: 1 addition & 1 deletion src/base/conv/auto_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct JSON
else if constexpr (std::is_arithmetic_v<T>) {
json += toString(val);
}
else if constexpr (std::is_enum_v<T>
else if constexpr (Refl::is_enum<T>
|| std::is_same_v<T, bool>) {
json += '\"';
json += toString(val);
Expand Down
4 changes: 2 additions & 2 deletions src/base/conv/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concept Parsable = !std::is_void_v<decltype(T::fromString(

template <class To>
constexpr inline static bool IsParsable =
std::is_enum_v<To> || Parsable<To>
Refl::is_enum<To> || Parsable<To>
|| (Type::is_optional_v<To> && IsParsable<Type::optional_t<To>>)
|| std::is_constructible_v<To, std::string>
|| std::is_same_v<To, bool> || std::is_floating_point_v<To>
Expand All @@ -28,7 +28,7 @@ template <typename To>
requires IsParsable<To>
[[nodiscard]] decltype(auto) parse(const std::string &string)
{
if constexpr (std::is_enum_v<To>)
if constexpr (Refl::is_enum<To>)
return Refl::get_enum<To>(string);
else if constexpr (Parsable<To>)
return To::fromString(string);
Expand Down
4 changes: 2 additions & 2 deletions src/base/conv/tostring.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ concept ToStringMember =

template <class From>
constexpr inline static bool IsStringifiable =
ToStringMember<From> || std::is_enum_v<From>
ToStringMember<From> || Refl::is_enum<From>
|| (Type::is_optional_v<From>
&& IsStringifiable<Type::optional_t<From>>)
|| std::is_constructible_v<std::string, From>
Expand All @@ -32,7 +32,7 @@ template <typename From>
requires IsStringifiable<From>
[[nodiscard]] decltype(auto) toString(const From &value)
{
if constexpr (std::is_enum_v<From>)
if constexpr (Refl::is_enum<From>)
return Refl::enum_name(value);
else if constexpr (Type::is_optional_v<From>) {
using T = std::remove_cvref_t<decltype(toString(*value))>;
Expand Down
9 changes: 6 additions & 3 deletions src/base/refl/auto_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,16 @@ template <class E> constexpr E get_enum(const std::string_view &data)
return static_cast<E>(ix + first);
}

template <class E>
concept is_enum = std::is_enum_v<E> && Detail::count<E>() > 0;

template <class E> consteval auto enum_values()
{
constexpr auto first = Detail::from_to<E>().first;
constexpr auto n = std::size(enum_names<E>);
std::array<E, n> res{};
for (std::size_t i = 0; i < n; ++i)
// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
res[i] = static_cast<E>(i + first);
return res;
}
Expand Down Expand Up @@ -258,9 +262,8 @@ struct EnumArray : std::array<V, std::size(enum_names<E>)>
bool operator==(const EnumArray &) const = default;
};

template <class E, class... Args>
requires(std::is_enum_v<E>
&& sizeof...(Args) == Detail::count<E>()
template <is_enum E, class... Args>
requires(sizeof...(Args) == Detail::count<E>()
&& Detail::from_to<E>().first == 0)
struct EnumVariant : std::variant<Args...>
{
Expand Down
35 changes: 21 additions & 14 deletions src/base/type/physicalvalue.h
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
#ifndef TYPE_PHYSICALVALUE
#define TYPE_PHYSICALVALUE

#include <cstdint>
#include "base/conv/parse.h"
#include "base/conv/tostring.h"
#include "base/refl/auto_enum.h"
#include "base/text/valueunit.h"

namespace Type
{

enum class SimpleUnit : std::uint8_t { none, relative, absolute };

template <typename Value, typename Unit = SimpleUnit>
class PhysicalValue
template <typename Value, Refl::is_enum Unit> struct PhysicalValue
{
public:
Value value;
Unit unit;

constexpr PhysicalValue() : value{}, unit{} {}
constexpr PhysicalValue(Value value, Unit unit) :
value(value),
unit(unit)
{}
Value value{};
Unit unit{};

constexpr bool operator==(const PhysicalValue &) const = default;

template <std::same_as<double> = Value>
[[nodiscard]] static PhysicalValue fromString(
const std::string &str)
{
const Text::ValueUnit vu{str};
return {vu.getValue(), Refl::get_enum<Unit>(vu.getUnit())};
}

[[nodiscard]] std::string toString() const
{
return Conv::toString(value)
+ std::string{Conv::toString(unit)};
}
};

}
Expand Down
1 change: 0 additions & 1 deletion src/chart/generator/axis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <iterator>
#include <limits>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
Expand Down
4 changes: 2 additions & 2 deletions src/chart/generator/axis.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct ChannelStats
template <ChannelIdLike T>
[[nodiscard]] const TrackType &at(const T &id) const
{
return tracked[-id];
return tracked[+id];
}

void track(ChannelId at, const Data::MarkerId &id)
Expand All @@ -47,7 +47,7 @@ struct ChannelStats
template <ChannelIdLike Id>
void setIfRange(Id at, const Math::Range<> &range)
{
if (auto *r = std::get_if<0>(&tracked[-at])) *r = range;
if (auto *r = std::get_if<0>(&tracked[+at])) *r = range;
}
};

Expand Down
13 changes: 7 additions & 6 deletions src/chart/generator/marker.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "marker.h"

#include <cmath>
#include <cstdint>
#include <optional>
#include <utility>
Expand Down Expand Up @@ -31,10 +32,10 @@ Marker::Marker(const Options &options,
bool needMarkerInfo) :
cellInfo(data.cellInfo(index, needMarkerInfo)),
mainId(data.getId(mainAxisList,
options.dimLabelIndex(-options.mainAxisType()),
options.dimLabelIndex(+options.mainAxisType()),
index)),
subId(data.getId(subAxisList,
options.dimLabelIndex(-options.subAxisType()),
options.dimLabelIndex(+options.subAxisType()),
index)),
sizeId(data.getId(
options.getChannels().at(ChannelId::size).dimensions(),
Expand Down Expand Up @@ -76,7 +77,7 @@ Marker::Marker(const Options &options,
if (subAxisList != options.subAxis().dimensions())
subId.label =
data.getId(options.subAxis().dimensions(),
options.dimLabelIndex(-options.subAxisType()),
options.dimLabelIndex(+options.subAxisType()),
index)
.label;

Expand Down Expand Up @@ -223,14 +224,14 @@ void Marker::fromRectangle(const Geom::Rect &rect)

Math::Range<> Marker::getSizeBy(AxisId axisId) const
{
return isHorizontal(+axisId) ? toRectangle().hSize()
: toRectangle().vSize();
return isHorizontal(orientation(axisId)) ? toRectangle().hSize()
: toRectangle().vSize();
}

void Marker::setSizeBy(AxisId axisId, const Math::Range<> range)
{
auto rect = toRectangle();
if (isHorizontal(+axisId))
if (isHorizontal(orientation(axisId)))
rect.setHSize(range);
else
rect.setVSize(range);
Expand Down
93 changes: 48 additions & 45 deletions src/chart/generator/plotbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "base/anim/interpolated.h"
#include "base/math/floating.h"
#include "base/math/range.h"
#include "base/refl/auto_enum.h"
#include "chart/main/style.h"
#include "chart/options/align.h"
#include "chart/options/channel.h"
Expand Down Expand Up @@ -206,8 +207,8 @@ bool PlotBuilder::linkMarkers(const Buckets &buckets,
std::numeric_limits<double>::lowest());

auto isAggregatable =
!plot->getOptions()->isMeasure(-axisIndex)
|| (isMain && plot->getOptions()->isMeasure(-!axisIndex)
!plot->getOptions()->isMeasure(+axisIndex)
|| (isMain && plot->getOptions()->isMeasure(+!axisIndex)
&& plot->getOptions()->geometry.get()
== ShapeType::rectangle);

Expand All @@ -228,7 +229,7 @@ bool PlotBuilder::linkMarkers(const Buckets &buckets,
auto &marker = **it.base().base().base();
if (!marker.enabled) continue;
o = std::max(o,
marker.size.getCoord(+axisIndex),
marker.size.getCoord(orientation(axisIndex)),
Math::Floating::less);
}
if (o == std::numeric_limits<double>::lowest()) o = 0.0;
Expand Down Expand Up @@ -274,7 +275,8 @@ bool PlotBuilder::linkMarkers(const Buckets &buckets,
: *it.base().base().base();

if (act)
prevPos = act->position.getCoord(+axisIndex) +=
prevPos =
act->position.getCoord(orientation(axisIndex)) +=
isAggregatable ? dimOffset[i] : prevPos;

hasConnection |=
Expand Down Expand Up @@ -350,7 +352,7 @@ void PlotBuilder::calcLegendAndLabel(const Data::DataTable &dataTable)
if (scale.title) calcLegend.title = *scale.title;

if (auto &&meas = scale.measure()) {
if (plot->getOptions()->isMeasure(-type)) {
if (plot->getOptions()->isMeasure(+type)) {
if (isAutoTitle)
calcLegend.title = dataCube.getName(*meas);
calcLegend.measure = {std::get<0>(stats.at(type)),
Expand All @@ -364,7 +366,7 @@ void PlotBuilder::calcLegendAndLabel(const Data::DataTable &dataTable)
auto merge =
type == LegendId::size
|| (type == LegendId::lightness
&& plot->getOptions()->dimLabelIndex(-type) == 0);
&& plot->getOptions()->dimLabelIndex(+type) == 0);
for (std::uint32_t i{}, count{}; i < indices.size(); ++i)
if (const auto &sliceIndex = indices[i]) {
auto rangeId = static_cast<double>(i);
Expand Down Expand Up @@ -412,7 +414,7 @@ void PlotBuilder::calcAxis(const Data::DataTable &dataTable,
auto isAutoTitle = scale.title.isAuto();
if (scale.title) axis.title = *scale.title;

if (plot->getOptions()->isMeasure(-type)) {
if (plot->getOptions()->isMeasure(+type)) {
const auto &meas = *scale.measure();
if (isAutoTitle) axis.title = dataCube.getName(meas);

Expand All @@ -431,7 +433,7 @@ void PlotBuilder::calcAxis(const Data::DataTable &dataTable,
}
else {
for (auto merge =
plot->getOptions()->dimLabelIndex(-type) == 0
plot->getOptions()->dimLabelIndex(+type) == 0
&& (type != plot->getOptions()->mainAxisType()
|| plot->getOptions()->sort != Sort::byValue
|| scale.dimensions().size() == 1);
Expand Down Expand Up @@ -504,44 +506,45 @@ void PlotBuilder::addAlignment(const Buckets &subBuckets) const
void PlotBuilder::addSeparation(const Buckets &subBuckets,
const std::size_t &mainBucketSize) const
{
if (plot->getOptions()->isSplit()) {
auto align = plot->getOptions()->align;

std::vector ranges{mainBucketSize,
Math::Range<>::Raw({}, {})};
std::vector<bool> anyEnabled(mainBucketSize);

auto &&subAxis = plot->getOptions()->subAxisType();
for (auto &&bucket : subBuckets)
for (std::size_t i{}, prIx{};
auto &&[marker, idx] : bucket) {
(i += idx.itemId - std::exchange(prIx, idx.itemId)) %=
ranges.size();
if (marker.enabled) {
ranges[i].include(
marker.getSizeBy(subAxis).size());
anyEnabled[i] = true;
}
}
if (!plot->getOptions()->isSplit()) return;

auto max = Math::Range<>::Raw({}, {});
for (auto i = 0U; i < ranges.size(); ++i)
if (anyEnabled[i]) max = max + ranges[i];

for (auto i = 1U; i < ranges.size(); ++i)
ranges[i] = ranges[i] + ranges[i - 1].getMax()
+ (anyEnabled[i - 1] ? max.getMax() / 15 : 0);

for (auto &&bucket : subBuckets)
for (std::size_t i{}, prIx{};
auto &&[marker, idx] : bucket) {
(i += idx.itemId - std::exchange(prIx, idx.itemId)) %=
ranges.size();
marker.setSizeBy(subAxis,
Base::Align{align, ranges[i]}.getAligned(
marker.getSizeBy(subAxis)));
}
}
auto align = plot->getOptions()->align;

std::vector ranges{mainBucketSize, Math::Range<>::Raw({}, {})};
std::vector<bool> anyEnabled(mainBucketSize);

auto &&subAxis = plot->getOptions()->subAxisType();
for (auto &&bucket : subBuckets)
for (std::size_t i{}, prIx{}; auto &&[marker, idx] : bucket) {
if (!marker.enabled) continue;
(i += idx.itemId - std::exchange(prIx, idx.itemId)) %=
ranges.size();
ranges[i].include(marker.getSizeBy(subAxis).size());
anyEnabled[i] = true;
}

auto max = Math::Range<>::Raw({}, {});
for (auto i = 0U; i < ranges.size(); ++i)
if (anyEnabled[i]) max = max + ranges[i];

auto splitSpace =
plot->getStyle()
.plot.getAxis(plot->getOptions()->subAxisType())
.spacing->get(max.getMax(),
plot->getStyle().calculatedSize());

for (auto i = 1U; i < ranges.size(); ++i)
ranges[i] = ranges[i] + ranges[i - 1].getMax()
+ (anyEnabled[i - 1] ? splitSpace : 0);

for (auto &&bucket : subBuckets)
for (std::size_t i{}, prIx{}; auto &&[marker, idx] : bucket) {
(i += idx.itemId - std::exchange(prIx, idx.itemId)) %=
ranges.size();
marker.setSizeBy(subAxis,
Base::Align{align, ranges[i]}.getAligned(
marker.getSizeBy(subAxis)));
}
}

void PlotBuilder::normalizeSizes()
Expand Down
6 changes: 4 additions & 2 deletions src/chart/main/style.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ Chart Chart::def()
},
.interlacing = {
.color = Gfx::Color::Gray(0.97)
}
},
.spacing = Gfx::Length::Relative(1 / 15.)
},
.yAxis = {
.color = Gfx::Color::Gray(0.8),
Expand Down Expand Up @@ -370,7 +371,8 @@ Chart Chart::def()
},
.interlacing = {
.color = Gfx::Color::Gray(0.97)
}
},
.spacing = Gfx::Length::Relative(1 / 15.)
},
.areaColor = Gfx::Color::Transparent(),
.overflow = ::Anim::Interpolated<Overflow>(Overflow::hidden)
Expand Down
Loading

0 comments on commit 717d4ef

Please sign in to comment.