Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axis refactor v11f - Add split style parameter for plot.axes #618

Merged
merged 12 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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