From 3b99f21e6dfbb33d56df48c4d6dc445abedb4fe1 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Sat, 7 Dec 2024 20:11:11 +0100 Subject: [PATCH 1/9] add filter + BucketSeparationInfo --- src/chart/generator/axis.cpp | 152 +++++++++++++++++------- src/chart/generator/axis.h | 4 +- src/chart/generator/plotbuilder.cpp | 104 +++++++++++----- src/chart/generator/plotbuilder.h | 16 ++- src/chart/rendering/drawaxes.cpp | 70 ++++++----- src/chart/rendering/drawaxes.h | 32 +++-- src/chart/rendering/drawguides.cpp | 3 +- src/chart/rendering/drawguides.h | 1 + src/chart/rendering/drawinterlacing.cpp | 19 +-- src/chart/rendering/drawinterlacing.h | 2 + 10 files changed, 275 insertions(+), 128 deletions(-) diff --git a/src/chart/generator/axis.cpp b/src/chart/generator/axis.cpp index 02f0b3267..5f30bbfd9 100644 --- a/src/chart/generator/axis.cpp +++ b/src/chart/generator/axis.cpp @@ -348,7 +348,8 @@ interpolate(const SplitAxis &op0, const SplitAxis &op1, double factor) interpolate(static_cast(op0), static_cast(op1), factor); - if (!op0.parts.empty() && !op1.parts.empty()) { + if (!op0.parts.empty() && !op1.parts.empty() + && op0.seriesName() == op1.seriesName()) { using PartPair = const decltype(res.parts)::value_type; Alg::union_foreach( op0.parts, @@ -360,69 +361,128 @@ interpolate(const SplitAxis &op0, const SplitAxis &op1, double factor) switch (type) { case Alg::union_call_t::only_left: { auto from = lhs->second.range.min; - res.parts[lhs->first] = { - .weight = interpolate(lhs->second.weight, - 0.0, - factor), - .range = interpolate(lhs->second.range, - Math::Range<>{from, from}, - factor)}; + res.parts.insert({lhs->first, + {.weight = interpolate(lhs->second.weight, + 0.0, + factor), + .range = interpolate(lhs->second.range, + Math::Range<>{from, from}, + factor), + .measureRange = + interpolate(lhs->second.measureRange, + Math::Range<>{0, 1}, + factor)}}); break; } case Alg::union_call_t::only_right: { auto from = rhs->second.range.min; - res.parts[rhs->first] = { - .weight = interpolate(0.0, - rhs->second.weight, - factor), - .range = - interpolate(Math::Range<>{from, from}, - rhs->second.range, - factor)}; + res.parts.insert({rhs->first, + {.weight = interpolate(0.0, + rhs->second.weight, + factor), + .range = + interpolate(Math::Range<>{from, from}, + rhs->second.range, + factor), + .measureRange = + interpolate(Math::Range<>{0, 1}, + rhs->second.measureRange, + factor)}}); break; } default: case Alg::union_call_t::both: { - res.parts[lhs->first] = - interpolate(lhs->second, rhs->second, factor); + res.parts.insert({lhs->first, + interpolate(lhs->second, + rhs->second, + factor)}); break; } } }, res.parts.value_comp()); + + return res; } - else if (!op0.parts.empty()) { - auto begin = op0.parts.begin(); - res.parts[begin->first] = { - .weight = interpolate(begin->second.weight, 1.0, factor), - .range = interpolate(begin->second.range, - Math::Range<>{0, 1}, - factor)}; - while (++begin != op0.parts.end()) { - res.parts[begin->first] = { - .weight = - interpolate(begin->second.weight, 0.0, factor), - .range = interpolate(begin->second.range, - Math::Range<>{0, 1}, - factor)}; + + if (!op0.parts.empty()) { + if (op0.seriesName() != op1.seriesName()) { + for (auto &&[index, part] : op0.parts) + res.parts.insert({index, + {.weight = part.weight * (1 - factor), + .range = part.range, + .measureRange = part.measureRange}}); + } + else { + auto begin = op0.parts.begin(); + res.parts.insert({begin->first, + {.weight = interpolate(begin->second.weight, + 1.0, + factor), + .range = interpolate(begin->second.range, + Math::Range<>{0, 1}, + factor), + .measureRange = + interpolate(begin->second.measureRange, + Math::Range<>{0, 1}, + factor)}}); + while (++begin != op0.parts.end()) { + res.parts.insert({begin->first, + {.weight = interpolate(begin->second.weight, + 0.0, + factor), + .range = interpolate(begin->second.range, + Math::Range<>{0, 1}, + factor), + .measureRange = + interpolate(begin->second.measureRange, + Math::Range<>{0, 1}, + factor)}}); + } } } - else if (!op1.parts.empty()) { - auto begin = op1.parts.begin(); - res.parts[begin->first] = { - .weight = interpolate(1.0, begin->second.weight, factor), - .range = interpolate(Math::Range<>{0, 1}, - begin->second.range, - factor)}; - while (++begin != op1.parts.end()) { - res.parts[begin->first] = { - .weight = - interpolate(0.0, begin->second.weight, factor), - .range = interpolate(Math::Range<>{0, 1}, - begin->second.range, - factor)}; + else if (!op1.parts.empty() + && op0.seriesName() != op1.seriesName()) + res.parts.insert({std::nullopt, {.weight = 1 - factor}}); + + if (!op1.parts.empty()) { + if (op0.seriesName() != op1.seriesName()) { + for (auto &&[index, part] : op1.parts) + res.parts.insert({index, + {.weight = part.weight * factor, + .range = part.range, + .measureRange = part.measureRange}}); + } + else { + auto begin = op1.parts.begin(); + res.parts.insert({begin->first, + {.weight = interpolate(1.0, + begin->second.weight, + factor), + .range = interpolate(Math::Range<>{0, 1}, + begin->second.range, + factor), + .measureRange = interpolate(Math::Range<>{0, 1}, + begin->second.measureRange, + factor)}}); + while (++begin != op1.parts.end()) { + res.parts.insert({begin->first, + {.weight = interpolate(0.0, + begin->second.weight, + factor), + .range = interpolate(Math::Range<>{0, 1}, + begin->second.range, + factor), + .measureRange = + interpolate(Math::Range<>{0, 1}, + begin->second.measureRange, + factor)}}); + } } } + else if (!op0.parts.empty() + && op0.seriesName() != op1.seriesName()) + res.parts.insert({std::nullopt, {.weight = factor}}); return res; } diff --git a/src/chart/generator/axis.h b/src/chart/generator/axis.h index f99080533..e0c135cd1 100644 --- a/src/chart/generator/axis.h +++ b/src/chart/generator/axis.h @@ -202,12 +202,14 @@ struct SplitAxis : Axis { double weight{1.0}; Math::Range<> range{0, 1}; + Math::Range<> measureRange{0, 1}; [[nodiscard]] bool operator==( const Part &other) const = default; }; - using Parts = std::map; + using Parts = + std::multimap, Part>; Parts parts; [[nodiscard]] bool operator==( diff --git a/src/chart/generator/plotbuilder.cpp b/src/chart/generator/plotbuilder.cpp index 7ed228c62..abd75e515 100644 --- a/src/chart/generator/plotbuilder.cpp +++ b/src/chart/generator/plotbuilder.cpp @@ -145,11 +145,11 @@ Buckets PlotBuilder::generateMarkers(std::size_t &mainBucketSize, return Buckets{plot->markers}; } -std::vector PlotBuilder::sortedBuckets( +std::vector PlotBuilder::sortedBuckets( const Buckets &buckets, AxisId axisIndex) const { - std::vector sorted; + std::vector sorted; for (auto &&bucket : buckets) for (auto &&[marker, idx] : bucket) { @@ -159,7 +159,7 @@ std::vector PlotBuilder::sortedBuckets( auto it = std::ranges::lower_bound(sorted, idx.itemId, {}, - &BucketInfo::index); + &BucketSortInfo::index); if (it == sorted.end() || it->index != idx.itemId) it = sorted.emplace(it, idx.itemId, 0.0); @@ -170,7 +170,7 @@ std::vector PlotBuilder::sortedBuckets( if (plot->getOptions()->getChannels().axisPropsAt(axisIndex).sort == Sort::byValue) std::ranges::stable_sort(sorted, - [](const BucketInfo &lhs, const BucketInfo &rhs) + [](const BucketSortInfo &lhs, const BucketSortInfo &rhs) { return Math::Floating::less(lhs.size, rhs.size); }); @@ -345,14 +345,19 @@ void PlotBuilder::calcAxises(const Data::DataTable &dataTable, (mainAxis == AxisId::x ? subBoundRect : mainBoundRect) .min)); + if (!mainRanges.empty()) + plot->getOptions()->mainAxis().range = {}; + if (!subRanges.empty()) plot->getOptions()->subAxis().range = {}; + mainBoundRect = plot->getOptions()->mainAxis().range.getRange(mainBoundRect); subBoundRect = plot->getOptions()->subAxis().range.getRange(subBoundRect); - for (auto &&[axis, ranges, boundSize] : - {std::tuple{mainAxis, &mainRanges, mainBoundRect}, - {!mainAxis, &subRanges, subBoundRect}}) { + for (auto &&[axis, needRanges, boundSize] : + {std::tuple{mainAxis, mainRanges.empty(), mainBoundRect}, + {!mainAxis, subRanges.empty(), subBoundRect}}) { + for (auto &marker : plot->markers) { auto &&markerSize = marker.getSizeBy(axis); if (!boundSize.positive().intersects( @@ -363,12 +368,20 @@ void PlotBuilder::calcAxises(const Data::DataTable &dataTable, {boundSize.rescale(markerSize.min, 0.0), boundSize.rescale(markerSize.max, 0.0)}); } - - stats.setIfRange(axis, boundSize); + if (needRanges) stats.setIfRange(axis, boundSize); } - for (const AxisId &ch : {AxisId::x, AxisId::y}) + for (auto &&[ch, ranges] : + {std::pair{mainAxis, &mainRanges}, {!mainAxis, &subRanges}}) { calcAxis(dataTable, ch); + if (ranges->empty()) continue; + + auto &axis = plot->axises.at(ch); + for (auto &&range : *ranges) + if (range.enabled) + axis.parts.insert({range.index, + {1.0, range.atRange, range.containsValues}}); + } } void PlotBuilder::calcLegendAndLabel(const Data::DataTable &dataTable) @@ -534,10 +547,10 @@ void PlotBuilder::addAlignment(const Buckets &buckets, } } -std::vector> PlotBuilder::addSeparation( - const Buckets &buckets, +std::vector +PlotBuilder::addSeparation(const Buckets &buckets, AxisId axisIndex, - const std::size_t &otherBucketSize) const + const std::size_t &otherBucketSize) { if (!plot->getOptions()->isSplit(axisIndex)) return {}; @@ -545,41 +558,74 @@ std::vector> PlotBuilder::addSeparation( plot->getOptions()->getChannels().axisPropsAt(axisIndex); auto align = axisProps.align; - std::vector ranges{otherBucketSize, Math::Range<>{{}, {}}}; - std::vector anyEnabled(otherBucketSize); + std::vector res(otherBucketSize); for (auto &&bucket : buckets) for (auto &&[marker, idx] : bucket) { if (!marker.enabled) continue; - auto i = idx.itemId; - ranges[i].include(marker.getSizeBy(axisIndex).size()); - anyEnabled[i] = true; + auto &resItem = res[idx.itemId]; + if (!resItem.index && idx.label) + resItem.index = idx.label; + + resItem.containsValues.include( + marker.getSizeBy(axisIndex).size()); + resItem.enabled = true; } auto max = Math::Range<>{{}, {}}; - for (auto i = 0U; i < ranges.size(); ++i) - if (anyEnabled[i]) max = max + ranges[i]; + auto maxRange = Math::Range<>{{}, {}}; + for (auto &resItem : res) { + if (!resItem.enabled) continue; + max = max + resItem.containsValues; + maxRange.include(resItem.containsValues); + } auto splitSpace = plot->getStyle().plot.getAxis(axisIndex).spacing->get( max.size(), plot->getStyle().calculatedSize()); - auto onMax = ranges[0].max; - ranges[0] = ranges[0] - ranges[0].min; - for (auto i = 1U; i < ranges.size(); ++i) { - onMax += anyEnabled[i - 1] ? splitSpace : 0; - ranges[i] = ranges[i] + onMax - ranges[i].min * 2; - onMax += ranges[i].size(); + res[0].atRange = + res[0].containsValues - res[0].containsValues.min * 2; + auto onMax = res[0].containsValues.size(); + for (auto i = 1U; i < res.size(); ++i) { + onMax += res[i - 1].enabled ? splitSpace : 0; + res[i].atRange = res[i].containsValues + onMax + - res[i].containsValues.min * 2; + onMax += res[i].containsValues.size(); } for (auto &&bucket : buckets) for (auto &&[marker, idx] : bucket) marker.setSizeBy(axisIndex, - Base::Align{align, ranges[idx.itemId]}.getAligned( - marker.getSizeBy(axisIndex))); + Base::Align{align, res.at(idx.itemId).atRange} + .getAligned(marker.getSizeBy(axisIndex))); + + auto alignedRange = + Base::Align{align, {maxRange.min, maxRange.min}}.getAligned( + maxRange); + + res[0].atRange = res[0].atRange + - maxRange.min * res[0].containsValues.size() + / maxRange.size(); + for (auto &resItem : res) { + if (!resItem.enabled) continue; + + resItem.atRange = + ((resItem.atRange + resItem.containsValues.min + - resItem.atRange.min) + * maxRange.size() / resItem.containsValues.size() + + resItem.atRange.min - resItem.containsValues.min + + maxRange.min) + / onMax; + + resItem.containsValues = { + maxRange.rescale(resItem.containsValues.min), + maxRange.rescale(resItem.containsValues.max)}; + } - return ranges; + stats.setIfRange(axisIndex, alignedRange); + return res; } void PlotBuilder::normalizeSizes() diff --git a/src/chart/generator/plotbuilder.h b/src/chart/generator/plotbuilder.h index b7ab2b25a..04206a054 100644 --- a/src/chart/generator/plotbuilder.h +++ b/src/chart/generator/plotbuilder.h @@ -27,12 +27,20 @@ class PlotBuilder PlotPtr plot; ChannelStats stats; - struct BucketInfo + struct BucketSortInfo { std::size_t index{}; double size{}; }; + struct BucketSeparationInfo + { + std::optional index{}; + bool enabled{}; + Math::Range<> containsValues{0.0, 0.0}; + Math::Range<> atRange{0.0, 0.0}; + }; + void initDimensionTrackers(); Buckets generateMarkers(std::size_t &mainBucketSize, std::size_t &subBucketSize); @@ -46,13 +54,13 @@ class PlotBuilder void calcLegendAndLabel(const Data::DataTable &dataTable); void calcAxis(const Data::DataTable &dataTable, AxisId type); void addAlignment(const Buckets &buckets, AxisId axisIndex) const; - [[nodiscard]] std::vector> addSeparation( + [[nodiscard]] std::vector addSeparation( const Buckets &buckets, AxisId axisIndex, - const std::size_t &otherBucketSize) const; + const std::size_t &otherBucketSize); void normalizeSizes(); void normalizeColors(); - [[nodiscard]] std::vector + [[nodiscard]] std::vector sortedBuckets(const Buckets &buckets, AxisId axisIndex) const; void addSpecLayout(Buckets &buckets); void addAxisLayout(Buckets &buckets, diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index efb88f3b0..e34f692ae 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -55,49 +55,61 @@ void DrawAxes::drawGeometries() const ySplit.range.min}; DrawInterlacing{*this}.drawGeometries(Gen::AxisId::y, + ySplit.measureRange, tr, weight); DrawInterlacing{*this}.drawGeometries(Gen::AxisId::x, + xSplit.measureRange, tr, weight); drawAxis(Gen::AxisId::x, tr, weight); drawAxis(Gen::AxisId::y, tr, weight); - DrawGuides{*this}.draw(Gen::AxisId::x, tr, weight); - DrawGuides{*this}.draw(Gen::AxisId::y, tr, weight); + DrawGuides{*this}.draw(Gen::AxisId::x, + xSplit.measureRange, + tr, + weight); + DrawGuides{*this}.draw(Gen::AxisId::y, + ySplit.measureRange, + tr, + weight); } } void DrawAxes::drawLabels() const { - for (auto &&xSplit : std::views::values(splits[Gen::AxisId::x])) - for (auto &&ySplit : - std::views::values(splits[Gen::AxisId::y])) { - auto weight = - Math::FuzzyBool::And(xSplit.weight, ySplit.weight); - if (Math::Floating::is_zero(weight)) continue; - - const Geom::AffineTransform tr{xSplit.range.size(), - 0.0, - xSplit.range.min, - 0.0, - ySplit.range.size(), - ySplit.range.min}; + for (auto &&ySplit : std::views::values(splits[Gen::AxisId::y])) { + const Geom::AffineTransform tr{1, + 0.0, + 0, + 0.0, + ySplit.range.size(), + ySplit.range.min}; + DrawInterlacing{*this}.drawTexts(Gen::AxisId::y, + ySplit.measureRange, + tr, + ySplit.weight); + } - DrawInterlacing{*this}.drawTexts(Gen::AxisId::y, - tr, - weight); - DrawInterlacing{*this}.drawTexts(Gen::AxisId::x, - tr, - weight); + for (auto &&xSplit : std::views::values(splits[Gen::AxisId::x])) { + const Geom::AffineTransform tr{xSplit.range.size(), + 0.0, + xSplit.range.min, + 0.0, + 1, + 0}; + DrawInterlacing{*this}.drawTexts(Gen::AxisId::x, + xSplit.measureRange, + tr, + xSplit.weight); + } - drawDimensionLabels(Gen::AxisId::x, tr, weight); - drawDimensionLabels(Gen::AxisId::y, tr, weight); + drawDimensionLabels(Gen::AxisId::x); + drawDimensionLabels(Gen::AxisId::y); - drawTitle(Gen::AxisId::x, tr, weight); - drawTitle(Gen::AxisId::y, tr, weight); - } + drawTitle(Gen::AxisId::x); + drawTitle(Gen::AxisId::y); } const DrawAxes &&DrawAxes::init() && @@ -106,7 +118,7 @@ const DrawAxes &&DrawAxes::init() && const auto &axis = plot->axises.at(axisIndex); const static Gen::SplitAxis::Parts oneSized{ - {std::size_t{}, Gen::SplitAxis::Part{}}}; + {std::nullopt, Gen::SplitAxis::Part{}}}; splits[axisIndex] = axis.parts.empty() ? oneSized : axis.parts; @@ -125,7 +137,7 @@ const DrawAxes &&DrawAxes::init() && guides.interlacings.more()) != false; - intervals.emplace_back(item.range, + intervals.emplace_back(item.range.positive(), weight, Math::FuzzyBool::And( Math::Niebloid::interpolate( @@ -245,7 +257,7 @@ void DrawAxes::generateMeasure(Gen::AxisId axisIndex, if (!singleLabelRange) { intervals.emplace_back( - Math::Range<>{bottom, bottom + stripWidth}, + Math::Range<>{bottom, bottom + stripWidth}.positive(), weight, 1.0); diff --git a/src/chart/rendering/drawaxes.h b/src/chart/rendering/drawaxes.h index 0696df994..d6b46d45c 100644 --- a/src/chart/rendering/drawaxes.h +++ b/src/chart/rendering/drawaxes.h @@ -65,19 +65,29 @@ class DrawAxes : public DrawingContext Refl::EnumArray> intervals; Refl::EnumArray> separators; Refl::EnumArray> + std::ranges::subrange> splits; - [[nodiscard]] const auto &getIntervals( - Gen::AxisId axisIndex) const + [[nodiscard]] auto getIntervals(Gen::AxisId axisIndex, + const Math::Range<> &filter = {0.0, 1.0}) const { - return intervals[axisIndex]; + return std::views::filter(intervals[axisIndex], + [&filter](const Interval &interval) -> bool + { + return filter.intersects(interval.range); + }); } - [[nodiscard]] const auto &getSeparators( - Gen::AxisId axisIndex) const + [[nodiscard]] auto getSeparators(Gen::AxisId axisIndex, + const Math::Range<> &filter) const { - return separators[axisIndex]; + return std::views::filter(separators[axisIndex], + [&filter](const Separator &sep) -> bool + { + return filter.includes(sep.position); + }); } private: @@ -94,11 +104,11 @@ class DrawAxes : public DrawingContext const Geom::AffineTransform &tr, double w) const; void drawTitle(Gen::AxisId axisIndex, - const Geom::AffineTransform &tr, - double w) const; + const Geom::AffineTransform &tr = {}, + double w = 1.0) const; void drawDimensionLabels(Gen::AxisId axisIndex, - const Geom::AffineTransform &tr, - double w) const; + const Geom::AffineTransform &tr = {}, + double w = 1.0) const; void drawDimensionLabel(Gen::AxisId axisIndex, const Geom::Point &origo, const Interval &interval, diff --git a/src/chart/rendering/drawguides.cpp b/src/chart/rendering/drawguides.cpp index c8c3c1a7c..2dca1bcfc 100644 --- a/src/chart/rendering/drawguides.cpp +++ b/src/chart/rendering/drawguides.cpp @@ -15,6 +15,7 @@ namespace Vizzu::Draw { void DrawGuides::draw(Gen::AxisId axisId, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) { @@ -27,7 +28,7 @@ void DrawGuides::draw(Gen::AxisId axisId, && parent.plot->guides.at(axisId).axisGuides != false) { parent.canvas.setLineWidth(*guideStyle.lineWidth); - for (const auto &sep : parent.getSeparators(axisId)) + for (const auto &sep : parent.getSeparators(axisId, filter)) drawGuide(axisId, sep.position, tr, diff --git a/src/chart/rendering/drawguides.h b/src/chart/rendering/drawguides.h index 10568ca83..44ea8ebad 100644 --- a/src/chart/rendering/drawguides.h +++ b/src/chart/rendering/drawguides.h @@ -11,6 +11,7 @@ class DrawGuides { public: void draw(Gen::AxisId axisId, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w); diff --git a/src/chart/rendering/drawinterlacing.cpp b/src/chart/rendering/drawinterlacing.cpp index f1b6553bd..3d9324d85 100644 --- a/src/chart/rendering/drawinterlacing.cpp +++ b/src/chart/rendering/drawinterlacing.cpp @@ -29,6 +29,7 @@ namespace Vizzu::Draw { void DrawInterlacing::drawGeometries(Gen::AxisId axisIndex, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) const { @@ -45,13 +46,16 @@ void DrawInterlacing::drawGeometries(Gen::AxisId axisIndex, parent.painter.setPolygonToCircleFactor(0); parent.painter.setPolygonStraightFactor(0); - for (const auto &interval : parent.getIntervals(axisIndex)) { + for (const auto &interval : + parent.getIntervals(axisIndex, filter)) { if (Math::Floating::is_zero(interval.isSecond)) continue; - auto clippedBottom = - std::max(interval.range.min, 0.0, Math::Floating::less); - auto clippedSize = - std::min(interval.range.max, 1.0, Math::Floating::less) - - clippedBottom; + auto clippedBottom = std::max(interval.range.min, + filter.min, + Math::Floating::less); + auto clippedSize = std::min(interval.range.max, + filter.max, + Math::Floating::less) + - clippedBottom; auto rect = [&, orientation = orientation(axisIndex)]( const double &from, @@ -95,6 +99,7 @@ void DrawInterlacing::drawGeometries(Gen::AxisId axisIndex, } void DrawInterlacing::drawTexts(Gen::AxisId axisIndex, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) const { @@ -118,7 +123,7 @@ void DrawInterlacing::drawTexts(Gen::AxisId axisIndex, if (!needText && !needTick) return; - for (const auto &sep : parent.getSeparators(axisIndex)) { + for (const auto &sep : parent.getSeparators(axisIndex, filter)) { auto tickPos = Geom::Point::Coord(orientation, origo, sep.position); if (needText && sep.label) diff --git a/src/chart/rendering/drawinterlacing.h b/src/chart/rendering/drawinterlacing.h index 17896ff58..524ffb342 100644 --- a/src/chart/rendering/drawinterlacing.h +++ b/src/chart/rendering/drawinterlacing.h @@ -11,9 +11,11 @@ class DrawInterlacing { public: void drawGeometries(Gen::AxisId axisIndex, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) const; void drawTexts(Gen::AxisId axisIndex, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) const; From d7f8c4b034e9bea3a820c4b34e95fce1f2b85c84 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Sun, 8 Dec 2024 07:48:47 +0100 Subject: [PATCH 2/9] Fix align to min --- src/chart/generator/plotbuilder.cpp | 44 ++++++++++++++++------------- src/chart/options/align.h | 2 +- src/chart/rendering/drawaxes.cpp | 9 ++++-- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/chart/generator/plotbuilder.cpp b/src/chart/generator/plotbuilder.cpp index abd75e515..0550301dc 100644 --- a/src/chart/generator/plotbuilder.cpp +++ b/src/chart/generator/plotbuilder.cpp @@ -526,9 +526,8 @@ void PlotBuilder::addAlignment(const Buckets &buckets, if (axisProps.align == Base::Align::Type::center) { auto &&halfSize = axisRange.size() / 2.0; - if (!Math::Floating::is_zero(halfSize)) - axisRange = {axisRange.min - halfSize, - axisRange.max - halfSize}; + axisRange = {axisRange.min - halfSize, + axisRange.max - halfSize}; } const Base::Align align{axisProps.align, {0.0, 1.0}}; @@ -586,35 +585,42 @@ PlotBuilder::addSeparation(const Buckets &buckets, plot->getStyle().calculatedSize()); res[0].atRange = - res[0].containsValues - res[0].containsValues.min * 2; + res[0].containsValues - res[0].containsValues.min; auto onMax = res[0].containsValues.size(); for (auto i = 1U; i < res.size(); ++i) { - onMax += res[i - 1].enabled ? splitSpace : 0; - res[i].atRange = res[i].containsValues + onMax - - res[i].containsValues.min * 2; + if (!res[i].enabled) continue; + onMax += splitSpace; + res[i].atRange = + res[i].containsValues + onMax - res[i].containsValues.min; onMax += res[i].containsValues.size(); } for (auto &&bucket : buckets) - for (auto &&[marker, idx] : bucket) + for (auto &&[marker, idx] : bucket) { + auto buc = res[idx.itemId]; + auto markerSize = marker.getSizeBy(axisIndex); + marker.setSizeBy(axisIndex, - Base::Align{align, res.at(idx.itemId).atRange} - .getAligned(marker.getSizeBy(axisIndex))); + Base::Align{align, + buc.atRange - buc.atRange.min + + buc.containsValues.min} + .getAligned(markerSize - markerSize.min) + + buc.atRange.min - buc.containsValues.min); + } - auto alignedRange = - Base::Align{align, {maxRange.min, maxRange.min}}.getAligned( - maxRange); + auto alignedRange = maxRange; + if (align == Base::Align::Type::center) { + auto &&halfSize = maxRange.size() / 2.0; + alignedRange = {maxRange.min - halfSize, + maxRange.max - halfSize}; + } - res[0].atRange = res[0].atRange - - maxRange.min * res[0].containsValues.size() - / maxRange.size(); for (auto &resItem : res) { if (!resItem.enabled) continue; resItem.atRange = - ((resItem.atRange + resItem.containsValues.min - - resItem.atRange.min) - * maxRange.size() / resItem.containsValues.size() + ((resItem.atRange - resItem.atRange.min) * maxRange.size() + / resItem.containsValues.size() + resItem.atRange.min - resItem.containsValues.min + maxRange.min) / onMax; diff --git a/src/chart/options/align.h b/src/chart/options/align.h index 13221fea2..31ac9a8b3 100644 --- a/src/chart/options/align.h +++ b/src/chart/options/align.h @@ -17,7 +17,7 @@ struct Align : private Math::Range<> [[nodiscard]] Range getAligned(const Range &range) const { switch (type) { - case Type::none: return range - range.min + min; + case Type::none: return range; case Type::center: return range - range.middle() + middle(); case Type::stretch: return static_cast(*this); } diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index e34f692ae..962aeb51d 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -40,6 +40,7 @@ namespace Vizzu::Draw void DrawAxes::drawGeometries() const { + auto origo = this->origo(); for (auto &&xSplit : std::views::values(splits[Gen::AxisId::x])) for (auto &&ySplit : std::views::values(splits[Gen::AxisId::y])) { @@ -63,8 +64,12 @@ void DrawAxes::drawGeometries() const tr, weight); - drawAxis(Gen::AxisId::x, tr, weight); - drawAxis(Gen::AxisId::y, tr, weight); + if (ySplit.measureRange.includes( + origo.getCoord(orientation(Gen::AxisId::y)))) + drawAxis(Gen::AxisId::x, tr, weight); + if (xSplit.measureRange.includes( + origo.getCoord(orientation(Gen::AxisId::x)))) + drawAxis(Gen::AxisId::y, tr, weight); DrawGuides{*this}.draw(Gen::AxisId::x, xSplit.measureRange, From 159c07c2e42531ab18d773dae47d1ca49856ef55 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 13:51:19 +0100 Subject: [PATCH 3/9] Normalize split chart inside addSeparation --- src/base/math/range.h | 3 ++- src/chart/generator/plotbuilder.cpp | 16 +++++++++++----- src/chart/options/options.cpp | 6 ++++-- src/chart/options/options.h | 4 +++- src/chart/rendering/drawaxes.cpp | 12 ++++++++---- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/base/math/range.h b/src/base/math/range.h index d864b5a85..7bf88afad 100644 --- a/src/base/math/range.h +++ b/src/base/math/range.h @@ -42,7 +42,8 @@ template struct Range [[nodiscard]] bool includes(const T &value) const { - return !less(value, min) && !less(max, value); + return !is_lt(std::weak_order(value, min)) + && !is_lt(std::weak_order(max, value)); } [[nodiscard]] T rescale(const T &value, T def = 0.5) const diff --git a/src/chart/generator/plotbuilder.cpp b/src/chart/generator/plotbuilder.cpp index 0550301dc..5fcc5a319 100644 --- a/src/chart/generator/plotbuilder.cpp +++ b/src/chart/generator/plotbuilder.cpp @@ -357,6 +357,7 @@ void PlotBuilder::calcAxises(const Data::DataTable &dataTable, for (auto &&[axis, needRanges, boundSize] : {std::tuple{mainAxis, mainRanges.empty(), mainBoundRect}, {!mainAxis, subRanges.empty(), subBoundRect}}) { + if (!needRanges) continue; for (auto &marker : plot->markers) { auto &&markerSize = marker.getSizeBy(axis); @@ -368,7 +369,7 @@ void PlotBuilder::calcAxises(const Data::DataTable &dataTable, {boundSize.rescale(markerSize.min, 0.0), boundSize.rescale(markerSize.max, 0.0)}); } - if (needRanges) stats.setIfRange(axis, boundSize); + stats.setIfRange(axis, boundSize); } for (auto &&[ch, ranges] : @@ -595,17 +596,22 @@ PlotBuilder::addSeparation(const Buckets &buckets, onMax += res[i].containsValues.size(); } + if (plot->getOptions()->coordSystem == CoordSystem::polar + && axisIndex == AxisId::x) + onMax += splitSpace; + for (auto &&bucket : buckets) for (auto &&[marker, idx] : bucket) { auto buc = res[idx.itemId]; auto markerSize = marker.getSizeBy(axisIndex); marker.setSizeBy(axisIndex, - Base::Align{align, - buc.atRange - buc.atRange.min - + buc.containsValues.min} + (Base::Align{align, + buc.atRange - buc.atRange.min + + buc.containsValues.min} .getAligned(markerSize - markerSize.min) - + buc.atRange.min - buc.containsValues.min); + + buc.atRange.min - buc.containsValues.min) + / onMax); } auto alignedRange = maxRange; diff --git a/src/chart/options/options.cpp b/src/chart/options/options.cpp index 1d3fdaab2..41e5b51bb 100644 --- a/src/chart/options/options.cpp +++ b/src/chart/options/options.cpp @@ -293,7 +293,9 @@ std::optional Options::getAutoLegend() const return std::nullopt; } -void Options::setAutoRange(bool hPositive, bool vPositive) +void Options::setAutoRange(bool hPositive, + bool vPositive, + bool isSplit) { auto &v = getChannels().at(AxisId::y); auto &h = getChannels().at(AxisId::x); @@ -302,7 +304,7 @@ void Options::setAutoRange(bool hPositive, bool vPositive) auto &&cart = coordSystem.get() == CoordSystem::cartesian; auto &&nrect = geometry != ShapeType::rectangle; - if (cart && hHasMeasure && (!vHasMeasure || nrect)) + if ((cart || isSplit) && hHasMeasure && (!vHasMeasure || nrect)) setMeasureRange(h, hPositive); else if (!cart && hHasMeasure && !vHasMeasure && v.hasDimension()) setRange(h, 0.0_perc, 133.0_perc); diff --git a/src/chart/options/options.h b/src/chart/options/options.h index 29abea81a..921f09c0d 100644 --- a/src/chart/options/options.h +++ b/src/chart/options/options.h @@ -168,7 +168,9 @@ class Options : public OptionProperties static MarkerInfoId generateMarkerInfoId(); void setAutoParameters(); - void setAutoRange(bool hPositive, bool vPositive); + void setAutoRange(bool hPositive, + bool vPositive, + bool isSplit = false); [[nodiscard]] bool labelsShownFor( const Data::SeriesIndex &series) const; diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index 962aeb51d..73d352a7c 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -176,15 +176,19 @@ const DrawAxes &&DrawAxes::init() && if (measEnabled == 0.0) continue; auto step = axis.measure.step.combine(); + using Math::Floating::less; + auto &&[min, max] = std::minmax( axis.measure.step.get_or_first(::Anim::first).value, axis.measure.step.get_or_first(::Anim::second).value, - Math::Floating::less); + less); auto stepHigh = - std::clamp(Math::Renard::R5().ceil(step), min, max); - auto stepLow = - std::clamp(Math::Renard::R5().floor(step), min, max); + std::clamp(Math::Renard::R5().ceil(step), min, max, less); + auto stepLow = std::clamp(Math::Renard::R5().floor(step), + min, + max, + less); if (Math::Floating::is_zero(axis.measure.range.size())) step = stepHigh = stepLow = 1.0; From 735f33dbe9edeea8f7430aafcea985e29c5226f7 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 16:35:54 +0100 Subject: [PATCH 4/9] fix center align + axis + override --- src/chart/generator/plotbuilder.cpp | 47 ++++++++++++++--------------- src/chart/rendering/drawaxes.cpp | 30 ++++++++++-------- src/chart/rendering/drawaxes.h | 4 ++- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/chart/generator/plotbuilder.cpp b/src/chart/generator/plotbuilder.cpp index 5fcc5a319..1f1d5bc09 100644 --- a/src/chart/generator/plotbuilder.cpp +++ b/src/chart/generator/plotbuilder.cpp @@ -345,10 +345,6 @@ void PlotBuilder::calcAxises(const Data::DataTable &dataTable, (mainAxis == AxisId::x ? subBoundRect : mainBoundRect) .min)); - if (!mainRanges.empty()) - plot->getOptions()->mainAxis().range = {}; - if (!subRanges.empty()) plot->getOptions()->subAxis().range = {}; - mainBoundRect = plot->getOptions()->mainAxis().range.getRange(mainBoundRect); subBoundRect = @@ -526,7 +522,7 @@ void PlotBuilder::addAlignment(const Buckets &buckets, if (axisProps.align == Base::Align::Type::none) return; if (axisProps.align == Base::Align::Type::center) { - auto &&halfSize = axisRange.size() / 2.0; + auto &&halfSize = axisRange.middle(); axisRange = {axisRange.min - halfSize, axisRange.max - halfSize}; } @@ -585,19 +581,21 @@ PlotBuilder::addSeparation(const Buckets &buckets, max.size(), plot->getStyle().calculatedSize()); - res[0].atRange = - res[0].containsValues - res[0].containsValues.min; - auto onMax = res[0].containsValues.size(); - for (auto i = 1U; i < res.size(); ++i) { - if (!res[i].enabled) continue; - onMax += splitSpace; - res[i].atRange = - res[i].containsValues + onMax - res[i].containsValues.min; - onMax += res[i].containsValues.size(); + double onMax = 0.0; + bool first = true; + for (auto &&resItem : res) { + if (!resItem.enabled) continue; + if (first) + first = false; + else + onMax += splitSpace; + resItem.atRange = resItem.containsValues + onMax + - resItem.containsValues.min; + onMax += resItem.containsValues.size(); } if (plot->getOptions()->coordSystem == CoordSystem::polar - && axisIndex == AxisId::x) + && axisIndex == AxisId::x && !first) onMax += splitSpace; for (auto &&bucket : buckets) @@ -616,7 +614,7 @@ PlotBuilder::addSeparation(const Buckets &buckets, auto alignedRange = maxRange; if (align == Base::Align::Type::center) { - auto &&halfSize = maxRange.size() / 2.0; + auto &&halfSize = maxRange.middle(); alignedRange = {maxRange.min - halfSize, maxRange.max - halfSize}; } @@ -624,16 +622,17 @@ PlotBuilder::addSeparation(const Buckets &buckets, for (auto &resItem : res) { if (!resItem.enabled) continue; - resItem.atRange = - ((resItem.atRange - resItem.atRange.min) * maxRange.size() - / resItem.containsValues.size() - + resItem.atRange.min - resItem.containsValues.min - + maxRange.min) - / onMax; + auto aligned = + Base::Align{align, resItem.containsValues}.getAligned( + maxRange); + + resItem.atRange = (aligned + resItem.atRange.min + - resItem.containsValues.min) + / onMax; resItem.containsValues = { - maxRange.rescale(resItem.containsValues.min), - maxRange.rescale(resItem.containsValues.max)}; + aligned.rescale(resItem.containsValues.min), + aligned.rescale(resItem.containsValues.max)}; } stats.setIfRange(axisIndex, alignedRange); diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index 73d352a7c..6f35c5cab 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -66,10 +66,16 @@ void DrawAxes::drawGeometries() const if (ySplit.measureRange.includes( origo.getCoord(orientation(Gen::AxisId::y)))) - drawAxis(Gen::AxisId::x, tr, weight); + drawAxis(Gen::AxisId::x, + xSplit.measureRange, + tr, + weight); if (xSplit.measureRange.includes( origo.getCoord(orientation(Gen::AxisId::x)))) - drawAxis(Gen::AxisId::y, tr, weight); + drawAxis(Gen::AxisId::y, + ySplit.measureRange, + tr, + weight); DrawGuides{*this}.draw(Gen::AxisId::x, xSplit.measureRange, @@ -281,24 +287,24 @@ void DrawAxes::generateMeasure(Gen::AxisId axisIndex, } } -Geom::Line DrawAxes::getAxisLine(Gen::AxisId axisIndex) const +Geom::Line DrawAxes::getAxisLine(Gen::AxisId axisIndex, + const Math::Range<> &filter) const { - auto offset = this->origo().getCoord(!orientation(axisIndex)); - - auto direction = Geom::Point::Ident(orientation(axisIndex)); - - auto p0 = direction.flip() * offset; - auto p1 = p0 + direction; - - if (offset >= 0 && offset <= 1) return {p0, p1}; + auto o = orientation(axisIndex); + if (auto offset = this->origo().getCoord(!o); + Math::Range<>{0.0, 1.0}.includes(offset)) + return {Geom::Point::Coord(o, filter.min, offset), + Geom::Point::Coord(o, filter.max, offset)}; return {}; } void DrawAxes::drawAxis(Gen::AxisId axisIndex, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) const { - if (auto line = tr(getAxisLine(axisIndex)); !line.isPoint()) { + if (auto line = tr(getAxisLine(axisIndex, filter)); + !line.isPoint()) { auto lineColor = *rootStyle.plot.getAxis(axisIndex).color * Math::FuzzyBool::And(w, plot->guides.at(axisIndex).axis); diff --git a/src/chart/rendering/drawaxes.h b/src/chart/rendering/drawaxes.h index d6b46d45c..1d838ab29 100644 --- a/src/chart/rendering/drawaxes.h +++ b/src/chart/rendering/drawaxes.h @@ -94,13 +94,15 @@ class DrawAxes : public DrawingContext void generateMeasure(Gen::AxisId axisIndex, double stepSize, double weight); - [[nodiscard]] Geom::Line getAxisLine(Gen::AxisId axisIndex) const; + [[nodiscard]] Geom::Line getAxisLine(Gen::AxisId axisIndex, + const Math::Range<> &filter) const; [[nodiscard]] Geom::Point getTitleBasePos(Gen::AxisId axisIndex, ::Anim::InterpolateIndex index) const; [[nodiscard]] Geom::Point getTitleOffset(Gen::AxisId axisIndex, ::Anim::InterpolateIndex index, bool fades) const; void drawAxis(Gen::AxisId axisIndex, + const Math::Range<> &filter, const Geom::AffineTransform &tr, double w) const; void drawTitle(Gen::AxisId axisIndex, From ed0b8d5f0a3e0f74270f01bfb90e08a3357020e8 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 18:58:21 +0100 Subject: [PATCH 5/9] Add axis range on split chart --- src/chart/generator/plotbuilder.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/chart/generator/plotbuilder.cpp b/src/chart/generator/plotbuilder.cpp index 1f1d5bc09..44be48c4f 100644 --- a/src/chart/generator/plotbuilder.cpp +++ b/src/chart/generator/plotbuilder.cpp @@ -572,6 +572,18 @@ PlotBuilder::addSeparation(const Buckets &buckets, auto maxRange = Math::Range<>{{}, {}}; for (auto &resItem : res) { if (!resItem.enabled) continue; + + auto onlyPositive = !std::signbit(resItem.containsValues.min); + plot->getOptions()->setAutoRange(onlyPositive, + onlyPositive, + true); + + resItem.containsValues = + plot->getOptions() + ->getChannels() + .at(axisIndex) + .range.getRange(resItem.containsValues); + max = max + resItem.containsValues; maxRange.include(resItem.containsValues); } From 76ddb8c1c99519ccf8aed9ffaab49e30766cb715 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 19:04:44 +0100 Subject: [PATCH 6/9] Update testcases --- test/e2e/test_cases/test_cases.json | 184 ++++++++++++++-------------- test/e2e/tests/config_tests.json | 2 +- test/e2e/tests/docs.json | 2 +- test/e2e/tests/features.json | 2 +- test/e2e/tests/fixes.json | 2 +- test/e2e/tests/style_tests.json | 2 +- test/e2e/tests/tickets.json | 2 +- test/e2e/tests/tickets/146.mjs | 11 +- 8 files changed, 108 insertions(+), 99 deletions(-) diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index cb3d47e63..a8e33740b 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -113,13 +113,13 @@ "refs": ["69db5d4"] }, "basic_animations/someOtherTests/merge_split_area_stream_2dis_1con": { - "refs": ["e4d1b13"] + "refs": ["ac444ed"] }, "basic_animations/someOtherTests/total_time_area_bar": { - "refs": ["d908ab5"] + "refs": ["5c623b7"] }, "basic_animations/someOtherTests/total_time_area_column": { - "refs": ["65ee04d"] + "refs": ["bf507c7"] }, "basic_animations/someOtherTests/total_time_bar_line": { "refs": ["a8a1f7c"] @@ -305,16 +305,16 @@ "refs": ["4c39423"] }, "operations/split_merge_tutorial_data/area_split_merge": { - "refs": ["083a277"] + "refs": ["8c5355a"] }, "operations/split_merge_tutorial_data/circle_split_merge": { - "refs": ["97d235c"] + "refs": ["f205ff9"] }, "operations/split_merge_tutorial_data/line_split_merge": { - "refs": ["21ddd62"] + "refs": ["780edd6"] }, "operations/split_merge_tutorial_data/rectangle_split_merge": { - "refs": ["cbaf4b9"] + "refs": ["c218798"] }, "shorthands/column_shorthands": { "refs": ["01a9469"] @@ -461,13 +461,13 @@ "refs": ["7d026fc"] }, "web_content/analytical_operations/compare/area_polar_split": { - "refs": ["40c2342"] + "refs": ["c967f87"] }, "web_content/analytical_operations/compare/area_polar_stacked": { "refs": ["68c7e81"] }, "web_content/analytical_operations/compare/area_split_stacked": { - "refs": ["e198f58"] + "refs": ["3f72973"] }, "web_content/analytical_operations/compare/area_stacked": { "refs": ["8a34995"] @@ -476,16 +476,16 @@ "refs": ["d74c70f"] }, "web_content/analytical_operations/compare/column_groupped_1": { - "refs": ["c8edfba"] + "refs": ["ce66d34"] }, "web_content/analytical_operations/compare/column_groupped_2": { - "refs": ["908c6fe"] + "refs": ["4ec1cee"] }, "web_content/analytical_operations/compare/column_split_stacked_1": { - "refs": ["30f4b8b"] + "refs": ["2fc9d8a"] }, "web_content/analytical_operations/compare/column_split_stacked_2": { - "refs": ["e47af80"] + "refs": ["5f4919d"] }, "web_content/analytical_operations/compare/column_stacked_1": { "refs": ["ed1ea56"] @@ -494,19 +494,19 @@ "refs": ["9704152"] }, "web_content/analytical_operations/compare/coxcomb_1": { - "refs": ["2ccfd04"] + "refs": ["8b3d305"] }, "web_content/analytical_operations/compare/coxcomb_2": { "refs": ["3760392"] }, "web_content/analytical_operations/compare/line": { - "refs": ["4b3c8f8"] + "refs": ["7430ac6"] }, "web_content/analytical_operations/compare/line_polar": { - "refs": ["592c356"] + "refs": ["a7bec0e"] }, "web_content/analytical_operations/compare/stream_stacked": { - "refs": ["18519bf"] + "refs": ["a938ce6"] }, "web_content/analytical_operations/compare/waterfall": { "refs": ["6c43732"] @@ -536,7 +536,7 @@ "refs": ["365c900"] }, "web_content/analytical_operations/distribute/existingmeasure_scatterplot_split": { - "refs": ["5d62a8d"] + "refs": ["aaa67a5"] }, "web_content/analytical_operations/distribute/existingmeasure_treemap_stacked": { "refs": ["24dc9e3"] @@ -545,13 +545,13 @@ "refs": ["a25a78e"] }, "web_content/analytical_operations/distribute/newmeasure_column_split_stacked": { - "refs": ["6e7ffff"] + "refs": ["36d0ba5"] }, "web_content/analytical_operations/distribute/newmeasure_column_stacked": { "refs": ["facf971"] }, "web_content/analytical_operations/distribute/newmeasure_dotplot_1": { - "refs": ["8c8c549"] + "refs": ["0d49b62"] }, "web_content/analytical_operations/distribute/newmeasure_dotplot_2": { "refs": ["082147a"] @@ -638,28 +638,28 @@ "refs": ["5ad66da"] }, "web_content/analytical_operations/split/area_polar_stacked": { - "refs": ["0516130"] + "refs": ["d83146e"] }, "web_content/analytical_operations/split/area_stacked": { - "refs": ["476c72f"] + "refs": ["857519e"] }, "web_content/analytical_operations/split/column_100percent_stacked": { - "refs": ["fdfd341"] + "refs": ["8b3ffda"] }, "web_content/analytical_operations/split/column_stacked": { - "refs": ["55f434b"] + "refs": ["6295378"] }, "web_content/analytical_operations/split/coxcomb": { - "refs": ["c58daf9"] + "refs": ["f74e883"] }, "web_content/analytical_operations/split/radial_stacked": { - "refs": ["9e448a8"] + "refs": ["7f5bc17"] }, "web_content/analytical_operations/split/scatterplot_1": { - "refs": ["f49f901"] + "refs": ["73ca0bf"] }, "web_content/analytical_operations/split/scatterplot_2": { - "refs": ["b2e8b78"] + "refs": ["2200420"] }, "web_content/analytical_operations/stretch_to_proportion/area_stacked": { "refs": ["8681c04"] @@ -668,7 +668,7 @@ "refs": ["c39a7bd"] }, "web_content/analytical_operations/stretch_to_proportion/column_split_stacked": { - "refs": ["ea1fcb4"] + "refs": ["d69242c"] }, "web_content/analytical_operations/stretch_to_proportion/column_stacked": { "refs": ["9ad75d7"] @@ -680,13 +680,13 @@ "refs": ["2c78a47"] }, "web_content/analytical_operations/sum/area_polar_split": { - "refs": ["e0351ee"] + "refs": ["9afb358"] }, "web_content/analytical_operations/sum/area_polar_stacked": { "refs": ["6f4d1f8"] }, "web_content/analytical_operations/sum/area_split": { - "refs": ["1640017"] + "refs": ["9630927"] }, "web_content/analytical_operations/sum/area_stacked": { "refs": ["9078a5a"] @@ -731,7 +731,7 @@ "refs": ["c84d593"] }, "web_content/analytical_operations/sum/column_split_stacked": { - "refs": ["754c575"] + "refs": ["9905d5f"] }, "web_content/analytical_operations/sum/column_stacked_1": { "refs": ["3396093"] @@ -746,7 +746,7 @@ "refs": ["f8eee6c"] }, "web_content/analytical_operations/sum/coxcomb_split": { - "refs": ["105e2f2"] + "refs": ["61a8caf"] }, "web_content/analytical_operations/sum/dotplot": { "refs": ["966a4e8"] @@ -770,7 +770,7 @@ "refs": ["4e1f1e8"] }, "web_content/analytical_operations/sum/stream_stacked": { - "refs": ["def9ca7"] + "refs": ["165e7dc"] }, "web_content/analytical_operations/sum/treemap": { "refs": ["bfa16c8"] @@ -782,13 +782,13 @@ "refs": ["b2b0648"] }, "web_content_removed/animated/composition_percentage_area_stream_3dis_1con": { - "refs": ["6c914ae"] + "refs": ["3735590"] }, "web_content_removed/animated/composition_percentage_column_3dis_1con": { - "refs": ["349e13e"] + "refs": ["2cc5f01"] }, "web_content_removed/animated/composition_percentage_column_stream_3dis_1con": { - "refs": ["fb5e380"] + "refs": ["8bd46e2"] }, "web_content_removed/animated/distribution_relationship_dotplot_dotplot": { "refs": ["d005bda"] @@ -800,13 +800,13 @@ "refs": ["8a45f1d"] }, "web_content_removed/animated/merge_split_area_stream_3dis_1con": { - "refs": ["44510ac"] + "refs": ["b7744ef"] }, "web_content_removed/animated/merge_split_bar": { - "refs": ["99a64ed"] + "refs": ["d95ae70"] }, "web_content_removed/animated/merge_split_radial_stacked_rectangle_2dis_1con": { - "refs": ["8d9bbe2"] + "refs": ["dd1e39d"] }, "web_content_removed/animated/orientation_circle": { "refs": ["a517273"] @@ -860,7 +860,7 @@ "refs": ["205401d"] }, "web_content/infinite": { - "refs": ["c82e29b"] + "refs": ["283c824"] }, "web_content/presets/chart/column": { "refs": ["4f73d47"] @@ -872,7 +872,7 @@ "refs": ["116901e"] }, "web_content/presets/chart/column_splitted": { - "refs": ["2ab7958"] + "refs": ["46df3f0"] }, "web_content/presets/chart/column_percentage": { "refs": ["be20c3e"] @@ -896,7 +896,7 @@ "refs": ["ba6b634"] }, "web_content/presets/chart/bar_splitted": { - "refs": ["a66ca72"] + "refs": ["0d63a84"] }, "web_content/presets/chart/bar_percentage": { "refs": ["cfd480a"] @@ -920,7 +920,7 @@ "refs": ["018ddbc"] }, "web_content/presets/chart/area_splitted": { - "refs": ["4bebc47"] + "refs": ["3959b14"] }, "web_content/presets/graph/stream": { "refs": ["24b7673"] @@ -929,10 +929,10 @@ "refs": ["f675019"] }, "web_content/presets/graph/violin": { - "refs": ["683b150"] + "refs": ["ecbb9f0"] }, "web_content/presets/graph/violin_vertical": { - "refs": ["45f0d9d"] + "refs": ["0cf1a64"] }, "web_content/presets/chart/line": { "refs": ["6ce0fa0"] @@ -1205,13 +1205,13 @@ "refs": ["059e2c7"] }, "ww_animTiming/descartes_orientation/03_d-d_o_a-r-a_split": { - "refs": ["ce475b3"] + "refs": ["751e084"] }, "ww_animTiming/descartes_orientation/04_d-d_o_l-r-l": { "refs": ["96218ea"] }, "ww_animTiming/descartes_orientation/04_d-d_o_l-r-l_stacked": { - "refs": ["2dd6e64"] + "refs": ["0dedf8a"] }, "ww_animTiming/descartes_orientation/05_d-d_o_r-c-r": { "refs": ["7360223"] @@ -1538,13 +1538,13 @@ "refs": ["9b65e14"] }, "ww_animTiming_TESTS/descartes_orientation/03_d-d_o_a-r-a_split": { - "refs": ["2e7a9a1"] + "refs": ["a88b3ee"] }, "ww_animTiming_TESTS/descartes_orientation/04_d-d_o_l-r-l": { "refs": ["d1b4afe"] }, "ww_animTiming_TESTS/descartes_orientation/04_d-d_o_l-r-l_stacked": { - "refs": ["967c21c"] + "refs": ["10b62af"] }, "ww_animTiming_TESTS/descartes_orientation/05_d-d_o_r-c-r": { "refs": ["b2045df"] @@ -1757,25 +1757,25 @@ "refs": ["37f0b4b"] }, "ww_next_steps/next_steps/04_C_R": { - "refs": ["2600462"] + "refs": ["b31a5a5"] }, "ww_next_steps/next_steps/05_C_R": { - "refs": ["116a26a"] + "refs": ["debee42"] }, "ww_next_steps/next_steps/21_C_C_dotplot": { - "refs": ["f661bb8"] + "refs": ["be7bac7"] }, "ww_next_steps/next_steps/22_C_C": { - "refs": ["0794755"] + "refs": ["c99f101"] }, "ww_next_steps/next_steps/28_C_A": { - "refs": ["6d367be"] + "refs": ["6e92b9d"] }, "ww_next_steps/next_steps/35_C_A_violin": { - "refs": ["d332c8f"] + "refs": ["3a79637"] }, "ww_next_steps/next_steps/38_C_L_line": { - "refs": ["4e92c0a"] + "refs": ["dd7bb57"] }, "ww_next_steps/next_steps_Tests/02_C_R": { "refs": ["2c12152"] @@ -1787,22 +1787,22 @@ "refs": ["e5e38b9"] }, "ww_next_steps/next_steps_Tests/04_C_R": { - "refs": ["9dee69e"] + "refs": ["f402f8a"] }, "ww_next_steps/next_steps_Tests/05_C_R": { - "refs": ["d80c108"] + "refs": ["ef565bd"] }, "ww_next_steps/next_steps_Tests/21_C_C_dotplot": { - "refs": ["d4ab905"] + "refs": ["4340228"] }, "ww_next_steps/next_steps_Tests/22_C_C": { - "refs": ["02b9b4d"] + "refs": ["d5b3c1f"] }, "ww_next_steps/next_steps_Tests/28_C_A": { - "refs": ["63b9bdb"] + "refs": ["4b29d69"] }, "ww_next_steps/next_steps_Tests/38_C_L_line": { - "refs": ["196ae23"] + "refs": ["e97795b"] }, "ww_next_steps/next_steps_Tests/axisLabel_problem": { "refs": ["6e22ebb"] @@ -1820,13 +1820,13 @@ "refs": ["52d3608"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_05": { - "refs": ["c686e8a"] + "refs": ["615273e"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_06": { - "refs": ["831b857"] + "refs": ["acea363"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_09": { - "refs": ["e3cc8f1"] + "refs": ["3fada11"] }, "ww_next_steps/next_steps_byOperations/compare/comparison_10": { "refs": ["d7882c8"] @@ -1835,25 +1835,25 @@ "refs": ["c227840"] }, "ww_next_steps/next_steps_byOperations/components/components_01": { - "refs": ["ca11f2d"] + "refs": ["0a8b12c"] }, "ww_next_steps/next_steps_byOperations/components/components_02": { - "refs": ["98305b6"] + "refs": ["bb80f7f"] }, "ww_next_steps/next_steps_byOperations/components/components_03": { - "refs": ["d3742b9"] + "refs": ["c15ea98"] }, "ww_next_steps/next_steps_byOperations/components/components_04": { - "refs": ["d370a97"] + "refs": ["584e2a2"] }, "ww_next_steps/next_steps_byOperations/components/components_05": { - "refs": ["b926a55"] + "refs": ["8eb3d42"] }, "ww_next_steps/next_steps_byOperations/components/components_06": { - "refs": ["ce65254"] + "refs": ["b449fc6"] }, "ww_next_steps/next_steps_byOperations/components/components_07": { - "refs": ["bec2ab6"] + "refs": ["b4c76b1"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_01": { "refs": ["f89236a"] @@ -1868,16 +1868,16 @@ "refs": ["f9ee7a4"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_05": { - "refs": ["bce12de"] + "refs": ["e6c5f20"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_06": { - "refs": ["9131d17"] + "refs": ["a97aa8c"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_07": { "refs": ["c1a4283"] }, "ww_next_steps/next_steps_byOperations/distribute/distribution_08": { - "refs": ["101d566"] + "refs": ["1936a77"] }, "ww_next_steps/next_steps_byOperations/drilldown/drilldown_01": { "refs": ["277fa16"] @@ -1922,7 +1922,7 @@ "refs": ["334220e"] }, "ww_next_steps/next_steps_byOperations/ratio/ratio_03": { - "refs": ["510388f"] + "refs": ["3f5a96b"] }, "ww_next_steps/next_steps_byOperations/ratio/ratio_04": { "refs": ["04e508f"] @@ -1937,7 +1937,7 @@ "refs": ["8095c4a"] }, "ww_next_steps/next_steps_byOperations/remove/remove_03": { - "refs": ["a8c9b3e"] + "refs": ["3e45a82"] }, "ww_next_steps/next_steps_byOperations/remove/remove_04": { "refs": ["2406eab"] @@ -1976,7 +1976,7 @@ "refs": ["ea1d10f"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_10": { - "refs": ["0086d14"] + "refs": ["1210df0"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_11": { "refs": ["3ac4f8c"] @@ -1997,7 +1997,7 @@ "refs": ["1e1c654"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_18": { - "refs": ["a32372f"] + "refs": ["0683d47"] }, "ww_next_steps/next_steps_byOperations/sum_aggregate/sum_aggregate_19": { "refs": ["1468471"] @@ -2036,7 +2036,7 @@ "refs": ["92cb883"] }, "ww_next_steps/next_steps_byOperations/wOld_animated/merge_split_radial_stacked_rectangle_2dis_1con": { - "refs": ["4b8da78"] + "refs": ["0b27446"] }, "ww_next_steps/next_steps_byOperations/wOld_animated/orientation_dot_circle": { "refs": ["761614f"] @@ -2291,7 +2291,7 @@ "refs": ["d676cb2"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/area-rectangle/10_d-w_are_temporal_bubble": { - "refs": ["40ff971"] + "refs": ["4be7a2b"] }, "ww_noFade/wNoFade_Tests/2_des_pol-without/area/03_d-w_are": { "refs": ["207f46e"] @@ -2453,7 +2453,7 @@ "refs": ["e5678fa"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/area_column_time_sum": { - "refs": ["727c6d5"] + "refs": ["28a03ad"] }, "ww_noFade/wNoFade_Tests/Marker_transition_problem/area_orientation": { "refs": ["8c0d580"] @@ -2615,7 +2615,7 @@ "refs": ["e1bbbe9"] }, "ww_noFade/wNoFade_cases/1_des_pol/rectangle/09_rec_TemporalDistribution": { - "refs": ["f6863e9"] + "refs": ["6d6ac0a"] }, "ww_noFade/wNoFade_cases/1_des_pol/rectangle_V1/05b_rec_2c_V1": { "refs": ["017ef35"] @@ -2660,7 +2660,7 @@ "refs": ["96441e2"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/area-rectangle/10_d-w_are_temporal_bubble": { - "refs": ["74e171c"] + "refs": ["38f5864"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/area/03_d-w_are": { "refs": ["a1b0278"] @@ -2678,7 +2678,7 @@ "refs": ["07a2162"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/area/10_d-w_are_temporal_bubble": { - "refs": ["5276fd1"] + "refs": ["f98805f"] }, "ww_noFade/wNoFade_cases/2_des_pol-without/area_V1/03_d-w_are_V1": { "refs": ["965630f"] @@ -2987,7 +2987,7 @@ "refs": ["116901e"] }, "ww_samples_for_presets/cartesian_coo_sys/05_C_R_split_column_chart": { - "refs": ["e774d1c"] + "refs": ["66db5a8"] }, "ww_samples_for_presets/cartesian_coo_sys/06_C_R_100_stacked_column_chart": { "refs": ["7c3feaa"] @@ -3023,7 +3023,7 @@ "refs": ["ba6b634"] }, "ww_samples_for_presets/cartesian_coo_sys/16_C_R_splitted_bar_chart": { - "refs": ["01dbaac"] + "refs": ["87c022d"] }, "ww_samples_for_presets/cartesian_coo_sys/17_C_R_100_stacked_bar_chart": { "refs": ["0f0f014"] @@ -3062,7 +3062,7 @@ "refs": ["a745467"] }, "ww_samples_for_presets/cartesian_coo_sys/31_C_A_splitted_area_chart": { - "refs": ["52a2da9"] + "refs": ["3086495"] }, "ww_samples_for_presets/cartesian_coo_sys/32_C_A_stream_graph": { "refs": ["f4d215c"] @@ -3071,10 +3071,10 @@ "refs": ["f128560"] }, "ww_samples_for_presets/cartesian_coo_sys/34_C_A_violin_graph": { - "refs": ["0cddf74"] + "refs": ["66407dd"] }, "ww_samples_for_presets/cartesian_coo_sys/35_C_A_violin_graph_vert": { - "refs": ["5e95cb6"] + "refs": ["149b38a"] }, "ww_samples_for_presets/cartesian_coo_sys/36_C_A_range_area_chart": { "refs": ["a17416d"] @@ -3131,7 +3131,7 @@ "refs": ["e58d779"] }, "ww_samples_for_presets/polar_coo_sys/551_P_A_polar_stream_graph": { - "refs": ["c424a25"] + "refs": ["2a55726"] }, "ww_samples_for_presets/polar_coo_sys/55_P_A_polar_overlay_area_chart": { "refs": ["4546661"] diff --git a/test/e2e/tests/config_tests.json b/test/e2e/tests/config_tests.json index eca0d0a7d..b53cd58f9 100644 --- a/test/e2e/tests/config_tests.json +++ b/test/e2e/tests/config_tests.json @@ -50,7 +50,7 @@ "refs": ["d037f4d"] }, "channel_ranges/dim_axis": { - "refs": ["0e9a9fd"] + "refs": ["7df2363"] }, "channel_ranges/dim_color": { "refs": ["c5b912f"] diff --git a/test/e2e/tests/docs.json b/test/e2e/tests/docs.json index 981272ea2..9ba1ae18d 100644 --- a/test/e2e/tests/docs.json +++ b/test/e2e/tests/docs.json @@ -44,7 +44,7 @@ "refs": ["981e902"] }, "orientation_split_polar": { - "refs": ["5e3012a"] + "refs": ["2969623"] }, "shorthands_store": { "refs": ["df4002a"] diff --git a/test/e2e/tests/features.json b/test/e2e/tests/features.json index b345352da..84f3221a9 100644 --- a/test/e2e/tests/features.json +++ b/test/e2e/tests/features.json @@ -26,7 +26,7 @@ "refs": ["b6c8e6a"] }, "presets": { - "refs": ["84541ff"] + "refs": ["ddf2173"] }, "detach": { "refs": ["e3b0c44"] diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index 418a2b9cf..5390cf423 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -8,7 +8,7 @@ "refs": ["1732a49"] }, "143": { - "refs": ["0775a8d"] + "refs": ["3f9e324"] }, "144": { "refs": ["fde02e4"] diff --git a/test/e2e/tests/style_tests.json b/test/e2e/tests/style_tests.json index cb820c8db..e44243ac5 100644 --- a/test/e2e/tests/style_tests.json +++ b/test/e2e/tests/style_tests.json @@ -356,7 +356,7 @@ "refs": ["bcfbd92"] }, "plot/spacing": { - "refs": ["d21c2a7"] + "refs": ["cb2894b"] }, "plot/xAxis/color/hexa/animated_yellowCustom_0.25-blueCustom_0.75": { "refs": ["77b26be"] diff --git a/test/e2e/tests/tickets.json b/test/e2e/tests/tickets.json index 773a70a11..d1498de98 100644 --- a/test/e2e/tests/tickets.json +++ b/test/e2e/tests/tickets.json @@ -11,7 +11,7 @@ "refs": ["1928a0a"] }, "146": { - "refs": ["777e2e4"] + "refs": ["fccca41"] }, "255": { "refs": ["29b4a25"] diff --git a/test/e2e/tests/tickets/146.mjs b/test/e2e/tests/tickets/146.mjs index 2b9682c37..b55333eb7 100644 --- a/test/e2e/tests/tickets/146.mjs +++ b/test/e2e/tests/tickets/146.mjs @@ -25,7 +25,16 @@ const testSteps = [ (chart) => chart.animate({ y: { set: ['Colors', 'Val'], split: true }, - x: 'Letters' + x: 'Letters', + label: 'Val' + }), + (chart) => + chart.animate({ + y: { align: 'center' } + }), + (chart) => + chart.animate({ + y: { align: 'stretch' } }) ] From 6a5d92cd5c9f6e608c72e0f41a1bfa7c661df347 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 20:06:35 +0100 Subject: [PATCH 7/9] fix clang-format --- src/chart/rendering/drawaxes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart/rendering/drawaxes.h b/src/chart/rendering/drawaxes.h index 1d838ab29..15ced15c8 100644 --- a/src/chart/rendering/drawaxes.h +++ b/src/chart/rendering/drawaxes.h @@ -74,7 +74,7 @@ class DrawAxes : public DrawingContext const Math::Range<> &filter = {0.0, 1.0}) const { return std::views::filter(intervals[axisIndex], - [&filter](const Interval &interval) -> bool + [filter](const Interval &interval) -> bool { return filter.intersects(interval.range); }); From 53dce2bb7c70599a47c94b5061faa8506058195e Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 20:53:49 +0100 Subject: [PATCH 8/9] fix tcs hash --- test/e2e/tests/config_tests.json | 2 +- test/e2e/tests/docs.json | 2 +- test/e2e/tests/features.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/tests/config_tests.json b/test/e2e/tests/config_tests.json index b53cd58f9..eca0d0a7d 100644 --- a/test/e2e/tests/config_tests.json +++ b/test/e2e/tests/config_tests.json @@ -50,7 +50,7 @@ "refs": ["d037f4d"] }, "channel_ranges/dim_axis": { - "refs": ["7df2363"] + "refs": ["0e9a9fd"] }, "channel_ranges/dim_color": { "refs": ["c5b912f"] diff --git a/test/e2e/tests/docs.json b/test/e2e/tests/docs.json index 9ba1ae18d..7e3e83c0b 100644 --- a/test/e2e/tests/docs.json +++ b/test/e2e/tests/docs.json @@ -44,7 +44,7 @@ "refs": ["981e902"] }, "orientation_split_polar": { - "refs": ["2969623"] + "refs": ["00c4ea3"] }, "shorthands_store": { "refs": ["df4002a"] diff --git a/test/e2e/tests/features.json b/test/e2e/tests/features.json index 84f3221a9..b345352da 100644 --- a/test/e2e/tests/features.json +++ b/test/e2e/tests/features.json @@ -26,7 +26,7 @@ "refs": ["b6c8e6a"] }, "presets": { - "refs": ["ddf2173"] + "refs": ["84541ff"] }, "detach": { "refs": ["e3b0c44"] From cd36edec4882d153953ff6a7a426ac31fe4db0ad Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Mon, 9 Dec 2024 21:08:40 +0100 Subject: [PATCH 9/9] add changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e20f63564..040b60e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ - Separate Channel properties to AxisChannel properties at config. - Channels 'set' rewrite doesn't clear AxisChannel properties. +- Split charts + - axis line multiplication. + - axis labels multiplication. + - axis range interpretation differently for all split part. + - negative values are handled correctly. + - align center / stretch fix. ### Added