From 7b3777ee551e6ae6cd585b43f2d1f5412b7bfc97 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Wed, 20 Nov 2024 16:12:15 +0100 Subject: [PATCH 1/9] Make drawInterlacing and drawGuides to child of drawAxes --- src/chart/generator/axis.h | 9 ----- src/chart/rendering/drawaxes.cpp | 24 ++++++------- src/chart/rendering/drawaxes.h | 12 +++++-- src/chart/rendering/drawguides.cpp | 22 ++++++------ src/chart/rendering/drawguides.h | 6 ++-- src/chart/rendering/drawinterlacing.cpp | 45 ++++++++++++++----------- src/chart/rendering/drawinterlacing.h | 6 ++-- src/chart/rendering/drawplot.cpp | 5 +-- src/chart/rendering/markerrenderer.cpp | 7 +--- src/chart/rendering/markerrenderer.h | 2 -- 10 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/chart/generator/axis.h b/src/chart/generator/axis.h index c4bd34549..5d01a9ab7 100644 --- a/src/chart/generator/axis.h +++ b/src/chart/generator/axis.h @@ -237,15 +237,6 @@ struct Axises return axises[axisType]; } - [[nodiscard]] const Axis &other(AxisId axisType) const - { - switch (axisType) { - default: - case AxisId::x: return at(AxisId::y); - case AxisId::y: return at(AxisId::x); - } - } - [[nodiscard]] Geom::Point origo() const; private: diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index 29123ff83..ef8c94d61 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -21,6 +21,7 @@ #include "dataframe/old/types.h" #include "drawguides.h" +#include "drawinterlacing.h" #include "drawlabel.h" #include "orientedlabel.h" #include "renderedchart.h" @@ -30,17 +31,17 @@ namespace Vizzu::Draw void DrawAxes::drawGeometries() const { - interlacing.drawGeometries(); + DrawInterlacing{*this}.drawGeometries(); drawAxis(Gen::AxisId::x); drawAxis(Gen::AxisId::y); - DrawGuides{{ctx()}, canvas, painter}.draw(); + DrawGuides{*this}.draw(); } void DrawAxes::drawLabels() const { - interlacing.drawTexts(); + DrawInterlacing{*this}.drawTexts(); drawDimensionLabels(Gen::AxisId::x); drawDimensionLabels(Gen::AxisId::y); @@ -49,9 +50,9 @@ void DrawAxes::drawLabels() const drawTitle(Gen::AxisId::y); } -Geom::Line DrawAxes::getAxis(Gen::AxisId axisIndex) const +Geom::Line DrawAxes::getAxisLine(Gen::AxisId axisIndex) const { - auto offset = plot->axises.other(axisIndex).measure.origo(); + auto offset = this->origo().getCoord(+!axisIndex); auto direction = Geom::Point::Ident(+axisIndex); @@ -64,7 +65,7 @@ Geom::Line DrawAxes::getAxis(Gen::AxisId axisIndex) const void DrawAxes::drawAxis(Gen::AxisId axisIndex) const { - if (auto line = getAxis(axisIndex); !line.isPoint()) { + if (auto line = getAxisLine(axisIndex); !line.isPoint()) { auto lineColor = *rootStyle.plot.getAxis(axisIndex).color * static_cast(plot->guides.at(axisIndex).axis); @@ -104,9 +105,7 @@ Geom::Point DrawAxes::getTitleBasePos(Gen::AxisId axisIndex, default: case Pos::min_edge: break; case Pos::max_edge: orthogonal = 1.0; break; - case Pos::axis: - orthogonal = plot->axises.other(axisIndex).measure.origo(); - break; + case Pos::axis: orthogonal = origo().getCoord(+!axisIndex); break; } double parallel{0.0}; @@ -166,7 +165,7 @@ Geom::Point DrawAxes::getTitleOffset(Gen::AxisId axisIndex, void DrawAxes::drawTitle(Gen::AxisId axisIndex) const { - const auto &titleString = plot->axises.at(axisIndex).title; + const auto &titleString = getAxis(axisIndex).title; const auto &titleStyle = rootStyle.plot.getAxis(axisIndex).title; @@ -256,9 +255,8 @@ void DrawAxes::drawDimensionLabels(Gen::AxisId axisIndex) const auto textColor = *labelStyle.color; if (textColor.isTransparent()) return; - auto origo = plot->axises.origo(); - const auto &axises = plot->axises; - const auto &axis = axises.at(axisIndex).dimension; + auto origo = this->origo(); + const auto &axis = getAxis(axisIndex).dimension; if (!axis.empty()) { canvas.setFont(Gfx::Font{labelStyle}); diff --git a/src/chart/rendering/drawaxes.h b/src/chart/rendering/drawaxes.h index 85afbcbb0..80ae643d2 100644 --- a/src/chart/rendering/drawaxes.h +++ b/src/chart/rendering/drawaxes.h @@ -3,9 +3,9 @@ #include "base/geom/line.h" #include "chart/generator/axis.h" +#include "chart/generator/plot.h" // NOLINT(misc-include-cleaner) #include "drawingcontext.h" -#include "drawinterlacing.h" namespace Vizzu::Draw { @@ -18,10 +18,16 @@ class DrawAxes : public DrawingContext Gfx::ICanvas &canvas; Painter &painter; - DrawInterlacing interlacing; + + const Gen::Axis &getAxis(Gen::AxisId axisIndex) const + { + return plot->axises.at(axisIndex); + } + + Geom::Point origo() const { return plot->axises.origo(); } private: - [[nodiscard]] Geom::Line getAxis(Gen::AxisId axisIndex) const; + [[nodiscard]] Geom::Line getAxisLine(Gen::AxisId axisIndex) const; [[nodiscard]] Geom::Point getTitleBasePos(Gen::AxisId axisIndex, ::Anim::InterpolateIndex index) const; [[nodiscard]] Geom::Point getTitleOffset(Gen::AxisId axisIndex, diff --git a/src/chart/rendering/drawguides.cpp b/src/chart/rendering/drawguides.cpp index 729095649..06d90f15e 100644 --- a/src/chart/rendering/drawguides.cpp +++ b/src/chart/rendering/drawguides.cpp @@ -23,15 +23,16 @@ void DrawGuides::draw() void DrawGuides::draw(Gen::AxisId axisId) { - const auto &guideStyle = rootStyle.plot.getAxis(axisId).guides; + const auto &guideStyle = + parent.rootStyle.plot.getAxis(axisId).guides; auto baseColor = *guideStyle.color; - if (const auto &axis = plot->axises.at(axisId).dimension; + if (const auto &axis = parent.getAxis(axisId).dimension; !baseColor.isTransparent() && !axis.empty() && *guideStyle.lineWidth > 0 - && plot->guides.at(axisId).axisGuides != false) { - canvas.setLineWidth(*guideStyle.lineWidth); + && parent.plot->guides.at(axisId).axisGuides != false) { + parent.canvas.setLineWidth(*guideStyle.lineWidth); for (auto it = axis.begin(), end = std::prev(axis.end()); it != end; @@ -41,10 +42,11 @@ void DrawGuides::draw(Gen::AxisId axisId) (*it).range.getMax(), baseColor * Math::FuzzyBool::And(weight, - plot->guides.at(axisId).axisGuides)); + parent.plot->guides.at(axisId) + .axisGuides)); } - canvas.setLineWidth(0); + parent.canvas.setLineWidth(0); } } @@ -58,12 +60,12 @@ void DrawGuides::drawGuide(Gen::AxisId axisId, auto normal = Geom::Point::Ident(!+axisId); auto relMax = ident * val; - canvas.setLineColor(color); + parent.canvas.setLineColor(color); const Geom::Line line(relMax, relMax + normal); - if (rootEvents.draw.plot.axis.guide->invoke( + if (parent.rootEvents.draw.plot.axis.guide->invoke( Events::OnLineDrawEvent(*eventTarget, {line, true}))) { - painter.drawLine(line); - renderedChart.emplace(Draw::Line{line, true}, + parent.painter.drawLine(line); + parent.renderedChart.emplace(Line{line, true}, std::move(eventTarget)); } } diff --git a/src/chart/rendering/drawguides.h b/src/chart/rendering/drawguides.h index 2840d6afb..37f9e7bb7 100644 --- a/src/chart/rendering/drawguides.h +++ b/src/chart/rendering/drawguides.h @@ -1,18 +1,18 @@ #ifndef DRAWGUIDES_H #define DRAWGUIDES_H +#include "drawaxes.h" #include "drawingcontext.h" namespace Vizzu::Draw { -class DrawGuides : public DrawingContext +class DrawGuides { public: void draw(); - Gfx::ICanvas &canvas; - Painter &painter; + const DrawAxes &parent; private: void draw(Gen::AxisId axisId); diff --git a/src/chart/rendering/drawinterlacing.cpp b/src/chart/rendering/drawinterlacing.cpp index c445da157..029e6876d 100644 --- a/src/chart/rendering/drawinterlacing.cpp +++ b/src/chart/rendering/drawinterlacing.cpp @@ -40,7 +40,7 @@ void DrawInterlacing::drawTexts() const void DrawInterlacing::draw(Gen::AxisId axisIndex, bool text) const { - const auto &axis = plot->axises.at(axisIndex).measure; + const auto &axis = parent.getAxis(axisIndex).measure; auto enabled = axis.enabled.combine(); if (enabled == 0.0) return; @@ -107,16 +107,16 @@ void DrawInterlacing::draw( bool text) const { auto orientation = !+axisIndex; - const auto &guides = plot->guides.at(axisIndex); - const auto &axisStyle = rootStyle.plot.getAxis(axisIndex); - const auto &axis = plot->axises.at(axisIndex).measure; + const auto &guides = parent.plot->guides.at(axisIndex); + const auto &axisStyle = parent.rootStyle.plot.getAxis(axisIndex); + const auto &axis = parent.getAxis(axisIndex).measure; auto interlacingColor = *axisStyle.interlacing.color * Math::FuzzyBool::And(weight, guides.interlacings); auto tickLength = axisStyle.ticks.length->get( - coordSys.getRect().size.getCoord(orientation), + parent.coordSys.getRect().size.getCoord(orientation), axisStyle.label.calculatedSize()); auto tickColor = @@ -142,7 +142,7 @@ void DrawInterlacing::draw( if (stripWidth <= 0) return; } - const auto origo = plot->axises.origo(); + const auto origo = parent.origo(); auto axisBottom = origo.getCoord(!orientation) + stripWidth; auto iMin = static_cast( @@ -158,8 +158,8 @@ void DrawInterlacing::draw( -(axisBottom + stripWidth) / (2 * stripWidth))); if (!text) { - painter.setPolygonToCircleFactor(0); - painter.setPolygonStraightFactor(0); + parent.painter.setPolygonToCircleFactor(0); + parent.painter.setPolygonStraightFactor(0); } auto Transform = [&](int x) @@ -223,16 +223,17 @@ void DrawInterlacing::draw( } } else { + auto &canvas = parent.canvas; canvas.save(); canvas.setLineColor(Gfx::Color::Transparent()); canvas.setBrushColor(interlacingColor); if (auto &&eventTarget = Events::Targets::axisInterlacing(axisIndex); - rootEvents.draw.plot.axis.interlacing->invoke( + parent.rootEvents.draw.plot.axis.interlacing->invoke( Events::OnRectDrawEvent(*eventTarget, {rect, true}))) { - painter.drawPolygon(rect.points()); - renderedChart.emplace(Draw::Rect{rect, true}, + parent.painter.drawPolygon(rect.points()); + parent.renderedChart.emplace(Rect{rect, true}, std::move(eventTarget)); } canvas.restore(); @@ -249,9 +250,10 @@ void DrawInterlacing::drawDataLabel( double alpha) const { auto orientation = !+axisIndex; - const auto &labelStyle = rootStyle.plot.getAxis(axisIndex).label; + const auto &labelStyle = + parent.rootStyle.plot.getAxis(axisIndex).label; - auto drawLabel = OrientedLabel{{ctx()}}; + auto drawLabel = OrientedLabel{{parent.ctx()}}; auto interpolates = labelStyle.position->maxIndex() || unit.maxIndex(); @@ -278,7 +280,8 @@ void DrawInterlacing::drawDataLabel( Styles::AxisLabel::Side::negative); auto &&posDir = - coordSys.convertDirectionAt({refPos, refPos + normal}) + parent.coordSys + .convertDirectionAt({refPos, refPos + normal}) .extend(1 - 2 * under); auto &&wUnit = unit.get_or_first(index); @@ -287,7 +290,7 @@ void DrawInterlacing::drawDataLabel( static_cast(*labelStyle.maxFractionDigits), *labelStyle.numberScale, wUnit.value); - drawLabel.draw(canvas, + drawLabel.draw(parent.canvas, str, posDir, labelStyle, @@ -295,7 +298,7 @@ void DrawInterlacing::drawDataLabel( Gfx::ColorTransform::Opacity(Math::FuzzyBool::And(alpha, position.weight, wUnit.weight)), - *rootEvents.draw.plot.axis.label, + *parent.rootEvents.draw.plot.axis.label, Events::Targets::measAxisLabel(str, axisIndex)); } } @@ -305,7 +308,9 @@ void DrawInterlacing::drawSticks(double tickLength, Gen::AxisId axisIndex, const Geom::Point &tickPos) const { - const auto &tickStyle = rootStyle.plot.getAxis(axisIndex).ticks; + auto &canvas = parent.canvas; + const auto &tickStyle = + parent.rootStyle.plot.getAxis(axisIndex).ticks; canvas.save(); @@ -316,7 +321,7 @@ void DrawInterlacing::drawSticks(double tickLength, auto tickLine = tickStyle.position->combine( [tickLine = - coordSys + parent.coordSys .convertDirectionAt({tickPos, tickPos + Geom::Point::Coord(!+axisIndex, -1.0)}) @@ -332,11 +337,11 @@ void DrawInterlacing::drawSticks(double tickLength, }); if (auto &&eventTarget = Events::Targets::axisTick(axisIndex); - rootEvents.draw.plot.axis.tick->invoke( + parent.rootEvents.draw.plot.axis.tick->invoke( Events::OnLineDrawEvent(*eventTarget, {tickLine, false}))) { canvas.line(tickLine); - renderedChart.emplace(Line{tickLine, false}, + parent.renderedChart.emplace(Line{tickLine, false}, std::move(eventTarget)); } diff --git a/src/chart/rendering/drawinterlacing.h b/src/chart/rendering/drawinterlacing.h index e295a3dac..7b1849816 100644 --- a/src/chart/rendering/drawinterlacing.h +++ b/src/chart/rendering/drawinterlacing.h @@ -1,19 +1,19 @@ #ifndef DRAWINTERLACING_H #define DRAWINTERLACING_H +#include "drawaxes.h" #include "drawingcontext.h" namespace Vizzu::Draw { -class DrawInterlacing : public DrawingContext +class DrawInterlacing { public: void drawGeometries() const; void drawTexts() const; - Gfx::ICanvas &canvas; - Painter &painter; + const DrawAxes &parent; private: void draw(Gen::AxisId axisIndex, bool text) const; diff --git a/src/chart/rendering/drawplot.cpp b/src/chart/rendering/drawplot.cpp index 245d23d39..81ea25929 100644 --- a/src/chart/rendering/drawplot.cpp +++ b/src/chart/rendering/drawplot.cpp @@ -27,10 +27,7 @@ void DrawPlot::draw(Gfx::ICanvas &canvas, drawPlotArea(canvas, painter, false); - auto axes = DrawAxes{{ctx()}, - canvas, - painter, - {{ctx()}, canvas, painter}}; + auto axes = DrawAxes{{ctx()}, canvas, painter}; axes.drawGeometries(); auto clip = rootStyle.plot.overflow == Styles::Overflow::hidden; diff --git a/src/chart/rendering/markerrenderer.cpp b/src/chart/rendering/markerrenderer.cpp index 3f3204670..8248e0ed2 100644 --- a/src/chart/rendering/markerrenderer.cpp +++ b/src/chart/rendering/markerrenderer.cpp @@ -255,11 +255,6 @@ void MarkerRenderer::drawLabels(Gfx::ICanvas &canvas) const } } -bool MarkerRenderer::shouldDrawMarkerBody(const Gen::Marker &marker) -{ - return marker.enabled != false; -} - void MarkerRenderer::draw(Gfx::ICanvas &canvas, Painter &painter, const AbstractMarker &abstractMarker, @@ -491,7 +486,7 @@ MarkerRenderer MarkerRenderer::create(const DrawingContext &ctx) { MarkerRenderer res{{ctx}, {}}; for (auto &marker : res.plot->getMarkers()) { - if (!shouldDrawMarkerBody(marker)) continue; + if (marker.enabled == false) continue; res.markers.push_back(AbstractMarker::createInterpolated(ctx, marker, diff --git a/src/chart/rendering/markerrenderer.h b/src/chart/rendering/markerrenderer.h index 1625b8381..954b00016 100644 --- a/src/chart/rendering/markerrenderer.h +++ b/src/chart/rendering/markerrenderer.h @@ -20,8 +20,6 @@ class MarkerRenderer : public DrawingContext std::vector markers; private: - [[nodiscard]] static bool shouldDrawMarkerBody( - const Gen::Marker &marker); [[nodiscard]] std::pair getColor( const AbstractMarker &abstractMarker, bool label = false) const; From 7f24dd7a88ef548e2018ea8c7b4b686558b4e708 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Nov 2024 18:16:24 +0100 Subject: [PATCH 2/9] Refactor drawAxes --- src/base/math/interpolation.h | 3 +- src/chart/generator/axis.cpp | 20 +- src/chart/generator/axis.h | 48 ++-- src/chart/generator/plotbuilder.cpp | 4 + src/chart/options/autoparam.h | 9 +- src/chart/rendering/drawaxes.cpp | 195 +++++++++++-- src/chart/rendering/drawaxes.h | 50 +++- src/chart/rendering/drawguides.cpp | 24 +- src/chart/rendering/drawguides.h | 3 +- src/chart/rendering/drawinterlacing.cpp | 267 +++++------------- src/chart/rendering/drawinterlacing.h | 13 +- src/chart/rendering/drawlegend.cpp | 38 ++- src/chart/rendering/drawplot.cpp | 2 +- .../cookbook/chart_types/network_graph.mjs | 2 +- 14 files changed, 382 insertions(+), 296 deletions(-) diff --git a/src/base/math/interpolation.h b/src/base/math/interpolation.h index 9e2098ab0..f69f0eb59 100644 --- a/src/base/math/interpolation.h +++ b/src/base/math/interpolation.h @@ -28,7 +28,8 @@ template struct interpolatable_t return requires(const T &a, const T &b, double factor) { { interpolate(a, b, factor) - } -> std::same_as; + } -> std::same_as< + std::conditional_t, double, T>>; }; } }; diff --git a/src/chart/generator/axis.cpp b/src/chart/generator/axis.cpp index 5edf39f85..661d9b7ac 100644 --- a/src/chart/generator/axis.cpp +++ b/src/chart/generator/axis.cpp @@ -252,7 +252,7 @@ MeasureAxis interpolate(const MeasureAxis &op0, } bool DimensionAxis::add(const Data::SliceIndex &index, const Math::Range<> &range, - const std::optional &position, + std::uint32_t position, const std::optional &color, bool label, bool merge) @@ -286,15 +286,7 @@ bool DimensionAxis::setLabels(double step) step = std::max(step, 1.0, Math::Floating::less); auto currStep = 0.0; - using SortedItems = - std::multiset, decltype( - [] (Item& lhs, Item &rhs) - { - return Math::Floating::less(lhs.range.getMin(), rhs.range.getMin()); - })>; - - for (auto curr = int{}; - auto &&item : SortedItems{begin(), end()}) { + for (auto curr = int{}; auto &&item : sortedItems()) { if (++curr <= currStep) continue; currStep += step; item.get().label = true; @@ -346,7 +338,7 @@ DimensionAxis interpolate(const DimensionAxis &op0, res.values .emplace(key, interpolate(first1->second, latest, factor)) - ->second.end = false; + ->second.endPos.makeAuto(); for (const auto &latest = std::prev(to1)->second; first2 != to2; @@ -354,7 +346,7 @@ DimensionAxis interpolate(const DimensionAxis &op0, res.values .emplace(key, interpolate(latest, first2->second, factor)) - ->second.start = false; + ->second.startPos.makeAuto(); } return res; @@ -366,11 +358,11 @@ DimensionAxis::Item interpolate(const DimensionAxis::Item &op0, { using Math::Niebloid::interpolate; DimensionAxis::Item res; - res.start = res.end = true; + res.startPos = op0.startPos; + res.endPos = op1.endPos; res.range = interpolate(op0.range, op1.range, factor); res.colorBase = interpolate(op0.colorBase, op1.colorBase, factor); res.label = interpolate(op0.label, op1.label, factor); - res.position = interpolate(op0.position, op1.position, factor); return res; } diff --git a/src/chart/generator/axis.h b/src/chart/generator/axis.h index 5d01a9ab7..7870f8887 100644 --- a/src/chart/generator/axis.h +++ b/src/chart/generator/axis.h @@ -83,50 +83,43 @@ struct DimensionAxis explicit Item() = default; public: - bool start{}; - bool end{}; + using PosType = Base::AutoParam; + PosType startPos{}; + PosType endPos{}; Math::Range<> range; - ::Anim::Interpolated position; ::Anim::Interpolated colorBase; ::Anim::Interpolated label; Item(Math::Range<> range, - const std::optional &position, + std::uint32_t position, const std::optional &color, bool setCategoryAsLabel) : - start(true), - end(true), + startPos(position), + endPos(position), range(range), label(setCategoryAsLabel) { - if (position) this->position = *position; if (color) colorBase = *color; } Item(const Item &item, bool starter) : - start(starter), - end(!starter), + startPos(starter ? item.startPos : PosType{}), + endPos(starter ? PosType{} : item.endPos), range(item.range), - position(item.position), colorBase(item.colorBase), label(item.label) {} bool operator==(const Item &other) const { - return range == other.range && position == other.position; - } - - [[nodiscard]] bool presentAt( - ::Anim::InterpolateIndex index) const - { - return (index == ::Anim::first && start) - || (index == ::Anim::second && end); + return range == other.range && startPos == other.startPos; } [[nodiscard]] double weight(double atEnd) const { - return Math::Niebloid::interpolate(start, end, atEnd); + return Math::Niebloid::interpolate(!startPos.isAuto(), + !endPos.isAuto(), + atEnd); } friend Item @@ -139,7 +132,7 @@ struct DimensionAxis DimensionAxis() = default; bool add(const Data::SliceIndex &index, const Math::Range<> &range, - const std::optional &position, + std::uint32_t position, const std::optional &color, bool label, bool merge); @@ -167,6 +160,21 @@ struct DimensionAxis [[nodiscard]] const Values &getValues() const { return values; } + [[nodiscard]] auto sortedItems() + { + struct ItemSorterByRangeStart + { + [[nodiscard]] bool operator()(const Item &lhs, + const Item &rhs) const + { + return Math::Floating::less(lhs.range.getMin(), + rhs.range.getMin()); + } + }; + return std::multiset, + ItemSorterByRangeStart>{begin(), end()}; + } + private: Values values; }; diff --git a/src/chart/generator/plotbuilder.cpp b/src/chart/generator/plotbuilder.cpp index ad5979afa..6e26c4f51 100644 --- a/src/chart/generator/plotbuilder.cpp +++ b/src/chart/generator/plotbuilder.cpp @@ -452,6 +452,10 @@ void PlotBuilder::calcAxis(const Data::DataTable &dataTable, !axis.dimension.setLabels(scale.step.getValue(1.0)) && series && isAutoTitle) axis.title = series.value().getColIndex(); + for (std::uint32_t pos{}; + DimensionAxis::Item & i : axis.dimension.sortedItems()) + i.endPos = i.startPos = + DimensionAxis::Item::PosType{pos++}; } } diff --git a/src/chart/options/autoparam.h b/src/chart/options/autoparam.h index 545e3c9ec..832e77636 100644 --- a/src/chart/options/autoparam.h +++ b/src/chart/options/autoparam.h @@ -45,7 +45,7 @@ template struct AutoParam return Conv::toString(*value); } - explicit operator bool() const + [[nodiscard]] explicit operator bool() const { return static_cast(value); } @@ -76,7 +76,12 @@ template struct AutoParam bool operator==(const AutoParam &other) const = default; - const std::optional &getValueOrAuto() { return value; } + [[nodiscard]] const std::optional &getValueOrAuto() const + { + return value; + } + + void makeAuto() { autoSet = true; } private: bool autoSet{}; diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index ef8c94d61..355d4ef52 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -13,6 +13,7 @@ #include "base/gfx/colortransform.h" #include "base/gfx/font.h" #include "base/math/fuzzybool.h" +#include "base/math/renard.h" #include "base/type/booliter.h" #include "chart/generator/plot.h" // NOLINT(misc-include-cleaner) #include "chart/main/events.h" @@ -31,17 +32,20 @@ namespace Vizzu::Draw void DrawAxes::drawGeometries() const { - DrawInterlacing{*this}.drawGeometries(); + DrawInterlacing{*this}.drawGeometries(Gen::AxisId::y); + DrawInterlacing{*this}.drawGeometries(Gen::AxisId::x); drawAxis(Gen::AxisId::x); drawAxis(Gen::AxisId::y); - DrawGuides{*this}.draw(); + DrawGuides{*this}.draw(Gen::AxisId::x); + DrawGuides{*this}.draw(Gen::AxisId::y); } void DrawAxes::drawLabels() const { - DrawInterlacing{*this}.drawTexts(); + DrawInterlacing{*this}.drawTexts(Gen::AxisId::y); + DrawInterlacing{*this}.drawTexts(Gen::AxisId::x); drawDimensionLabels(Gen::AxisId::x); drawDimensionLabels(Gen::AxisId::y); @@ -50,6 +54,150 @@ void DrawAxes::drawLabels() const drawTitle(Gen::AxisId::y); } +const DrawAxes &&DrawAxes::init() && +{ + for (auto axisIndex : Refl::enum_values()) { + auto &axis = getAxis(axisIndex); + + auto &intervals = this->intervals[axisIndex]; + auto &separators = this->separators[axisIndex]; + const auto &guides = plot->guides.at(axisIndex); + + for (auto &&[index, item] : axis.dimension.getValues()) { + auto weight = item.weight(axis.dimension.factor); + if (Math::Floating::is_zero(weight)) continue; + + intervals.emplace_back(item.range, + weight, + Math::FuzzyBool::And( + Math::Niebloid::interpolate( + (item.startPos ? *item.startPos + : *item.endPos) + % 2, + (item.endPos ? *item.endPos : *item.startPos) + % 2, + axis.dimension.factor), + Math::FuzzyBool::more(weight), + guides.interlacings.more()), + Interval::DimLabel{index, + item.label, + !item.startPos.isAuto(), + !item.endPos.isAuto()}); + + if (auto sepWeight = Math::Niebloid::interpolate( + !item.startPos.isAuto() && *item.startPos, + !item.endPos.isAuto() && *item.endPos, + axis.dimension.factor); + sepWeight > 0) + separators.emplace_back(item.range.getMin(), + sepWeight); + } + + auto enabled = axis.measure.enabled.combine(); + if (enabled == 0.0) continue; + auto step = axis.measure.step.combine(); + + 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); + + auto stepHigh = + std::clamp(Math::Renard::R5().ceil(step), min, max); + auto stepLow = + std::clamp(Math::Renard::R5().floor(step), min, max); + + if (Math::Floating::is_zero(axis.measure.range.size())) + step = stepHigh = stepLow = 1.0; + + if (stepHigh == step || stepLow == step) + generateMeasure(axisIndex, step, enabled); + else { + auto highWeight = + Math::Range<>::Raw(stepLow, stepHigh).rescale(step); + + generateMeasure(axisIndex, + stepLow, + (1.0 - highWeight) * enabled); + generateMeasure(axisIndex, + stepHigh, + highWeight * enabled); + } + } + + return std::move(*this); +} + +void DrawAxes::generateMeasure(Gen::AxisId axisIndex, + double stepSize, + double weight) +{ + auto orientation = !+axisIndex; + const auto &meas = getAxis(axisIndex).measure; + auto rangeSize = meas.range.size(); + bool singleLabelRange = Math::Floating::is_zero(rangeSize); + + double stripWidth{}; + if (!singleLabelRange) { + stripWidth = stepSize / rangeSize; + if (stripWidth <= 0) return; + } + + const auto origo = this->origo(); + auto axisBottom = origo.getCoord(!orientation) + stripWidth; + + auto iMin = static_cast( + axisBottom > 0 ? std::floor(-origo.getCoord(!orientation) + / (2 * stripWidth)) + * 2 + : std::round((meas.range.getMin() - stepSize) + / stepSize)); + + if (axisBottom + iMin * stripWidth + stripWidth < 0.0) + iMin += 2 + * static_cast(std::ceil( + -(axisBottom + stripWidth) / (2 * stripWidth))); + + auto Transform = [&](int x) + { + return std::pair{iMin + x * 2, + axisBottom + (iMin + x * 2) * stripWidth}; + }; + constexpr static auto Predicate = + [](const std::pair &x) + { + return x.second <= 1.0; + }; + + auto &separators = this->separators[axisIndex]; + auto &intervals = this->intervals[axisIndex]; + + for (auto &&[i, bottom] : + std::views::iota(0) | std::views::transform(Transform) + | std::views::take_while(Predicate)) { + if (!std::signbit(bottom)) { + separators.emplace_back(bottom, + weight, + (i + 1) * stepSize); + } + + if (!singleLabelRange) { + intervals.emplace_back( + Math::Range<>::Raw(bottom, bottom + stripWidth), + weight, + 1.0); + + if (!Math::Floating::less(1.0, bottom + stripWidth)) { + separators.emplace_back(bottom + stripWidth, + weight, + (i + 2) * stepSize); + } + } + else + break; + } +} + Geom::Line DrawAxes::getAxisLine(Gen::AxisId axisIndex) const { auto offset = this->origo().getCoord(+!axisIndex); @@ -262,20 +410,20 @@ void DrawAxes::drawDimensionLabels(Gen::AxisId axisIndex) const canvas.setFont(Gfx::Font{labelStyle}); const auto &enabled = plot->guides.at(axisIndex); - for (const auto &[slice, item] : axis.getValues()) + for (const auto &interval : getIntervals(axisIndex)) { + if (!interval.label) continue; drawDimensionLabel(axisIndex, origo, - item, - slice, - Math::FuzzyBool::And(item.weight(axis.factor), + interval, + Math::FuzzyBool::And(interval.weight, enabled.labels)); + } } } void DrawAxes::drawDimensionLabel(Gen::AxisId axisIndex, const Geom::Point &origo, - const Gen::DimensionAxis::Item &item, - const Data::SliceIndex &index, + const Interval &interval, double weight) const { if (weight == 0) return; @@ -289,18 +437,17 @@ void DrawAxes::drawDimensionLabel(Gen::AxisId axisIndex, &axisIndex, &drawLabel, &labelStyle, - &item, + &interval, &orientation, &origo, ident = Geom::Point::Ident(orientation), normal = Geom::Point::Ident(!orientation), - &text = item.label, - &weight, - &sindex = index](::Anim::InterpolateIndex index, + &dimInfo = *interval.label, + &weight](::Anim::InterpolateIndex index, const auto &position) { if (labelStyle.position->interpolates() - && !item.presentAt(index)) + && !dimInfo.presentAt(index)) return; Geom::Point refPos; @@ -313,7 +460,7 @@ void DrawAxes::drawDimensionLabel(Gen::AxisId axisIndex, case Pos::min_edge: refPos = Geom::Point(); break; } - auto relCenter = refPos + ident * item.range.middle(); + auto relCenter = refPos + ident * interval.range.middle(); auto under = labelStyle.position->interpolates() @@ -333,7 +480,7 @@ void DrawAxes::drawDimensionLabel(Gen::AxisId axisIndex, { if (!str.value) return; drawLabel.draw(canvas, - sindex.value, + dimInfo.index.value, posDir, labelStyle, 0, @@ -342,22 +489,22 @@ void DrawAxes::drawDimensionLabel(Gen::AxisId axisIndex, str.weight, plusWeight)), *rootEvents.draw.plot.axis.label, - Events::Targets::dimAxisLabel(sindex.column, - sindex.value, + Events::Targets::dimAxisLabel( + dimInfo.index.column, + dimInfo.index.value, axisIndex)); }; - if (text.interpolates() + if (dimInfo.presented.interpolates() && !labelStyle.position->interpolates()) { - draw(text.get_or_first(::Anim::first)); - draw(text.get_or_first(::Anim::second)); + draw(dimInfo.presented.get_or_first(::Anim::first)); + draw(dimInfo.presented.get_or_first(::Anim::second)); } - else { - draw(text.get_or_first(index), + else + draw(dimInfo.presented.get_or_first(index), !labelStyle.position->interpolates() ? 1.0 : position.weight); - } }); } diff --git a/src/chart/rendering/drawaxes.h b/src/chart/rendering/drawaxes.h index 80ae643d2..9474bbfca 100644 --- a/src/chart/rendering/drawaxes.h +++ b/src/chart/rendering/drawaxes.h @@ -26,7 +26,54 @@ class DrawAxes : public DrawingContext Geom::Point origo() const { return plot->axises.origo(); } + struct Separator + { + double position; + double weight; + std::optional label{}; + }; + + struct Interval + { + struct DimLabel + { + const Data::SliceIndex &index; + const ::Anim::Interpolated &presented; + bool start; + bool end; + + [[nodiscard]] bool presentAt( + ::Anim::InterpolateIndex index) const + { + return (index == ::Anim::first && start) + || (index == ::Anim::second && end); + } + }; + Math::Range<> range; + double weight; + double isSecond; + std::optional label{}; + }; + + const DrawAxes &&init() &&; + + Refl::EnumArray> intervals; + Refl::EnumArray> separators; + + const auto &getIntervals(Gen::AxisId axisIndex) const + { + return intervals[axisIndex]; + } + + const auto &getSeparators(Gen::AxisId axisIndex) const + { + return separators[axisIndex]; + } + private: + void generateMeasure(Gen::AxisId axisIndex, + double stepSize, + double weight); [[nodiscard]] Geom::Line getAxisLine(Gen::AxisId axisIndex) const; [[nodiscard]] Geom::Point getTitleBasePos(Gen::AxisId axisIndex, ::Anim::InterpolateIndex index) const; @@ -38,8 +85,7 @@ class DrawAxes : public DrawingContext void drawDimensionLabels(Gen::AxisId axisIndex) const; void drawDimensionLabel(Gen::AxisId axisIndex, const Geom::Point &origo, - const Gen::DimensionAxis::Item &item, - const Data::SliceIndex &index, + const Interval &interval, double weight) const; }; diff --git a/src/chart/rendering/drawguides.cpp b/src/chart/rendering/drawguides.cpp index 06d90f15e..30a8d5335 100644 --- a/src/chart/rendering/drawguides.cpp +++ b/src/chart/rendering/drawguides.cpp @@ -14,13 +14,6 @@ namespace Vizzu::Draw { - -void DrawGuides::draw() -{ - draw(Gen::AxisId::x); - draw(Gen::AxisId::y); -} - void DrawGuides::draw(Gen::AxisId axisId) { const auto &guideStyle = @@ -34,17 +27,12 @@ void DrawGuides::draw(Gen::AxisId axisId) && parent.plot->guides.at(axisId).axisGuides != false) { parent.canvas.setLineWidth(*guideStyle.lineWidth); - for (auto it = axis.begin(), end = std::prev(axis.end()); - it != end; - ++it) { - if (auto &&weight = (*it).weight(axis.factor); weight > 0) - drawGuide(axisId, - (*it).range.getMax(), - baseColor - * Math::FuzzyBool::And(weight, - parent.plot->guides.at(axisId) - .axisGuides)); - } + for (const auto &sep : parent.getSeparators(axisId)) + drawGuide(axisId, + sep.position, + baseColor + * Math::FuzzyBool::And(sep.weight, + parent.plot->guides.at(axisId).axisGuides)); parent.canvas.setLineWidth(0); } diff --git a/src/chart/rendering/drawguides.h b/src/chart/rendering/drawguides.h index 37f9e7bb7..06ae8ad77 100644 --- a/src/chart/rendering/drawguides.h +++ b/src/chart/rendering/drawguides.h @@ -10,12 +10,11 @@ namespace Vizzu::Draw class DrawGuides { public: - void draw(); + void draw(Gen::AxisId axisId); const DrawAxes &parent; private: - void draw(Gen::AxisId axisId); void drawGuide(Gen::AxisId axisId, double val, const Gfx::Color &color); diff --git a/src/chart/rendering/drawinterlacing.cpp b/src/chart/rendering/drawinterlacing.cpp index 029e6876d..c90138ab6 100644 --- a/src/chart/rendering/drawinterlacing.cpp +++ b/src/chart/rendering/drawinterlacing.cpp @@ -26,218 +26,97 @@ namespace Vizzu::Draw { -void DrawInterlacing::drawGeometries() const +void DrawInterlacing::drawGeometries(Gen::AxisId axisIndex) const { - draw(Gen::AxisId::y, false); - draw(Gen::AxisId::x, false); -} - -void DrawInterlacing::drawTexts() const -{ - draw(Gen::AxisId::y, true); - draw(Gen::AxisId::x, true); -} - -void DrawInterlacing::draw(Gen::AxisId axisIndex, bool text) const -{ - const auto &axis = parent.getAxis(axisIndex).measure; - auto enabled = axis.enabled.combine(); - - if (enabled == 0.0) return; - - auto step = axis.step.combine(); - - auto &&[min, max] = - std::minmax(axis.step.get_or_first(::Anim::first).value, - axis.step.get_or_first(::Anim::second).value, - Math::Floating::less); + const auto &guides = parent.plot->guides.at(axisIndex); + const auto &axisStyle = parent.rootStyle.plot.getAxis(axisIndex); + if (axisStyle.interlacing.color->isTransparent() + || guides.interlacings == false) + return; - auto stepHigh = - std::clamp(Math::Renard::R5().ceil(step), min, max); - auto stepLow = - std::clamp(Math::Renard::R5().floor(step), min, max); + auto orientation = !+axisIndex; - if (Math::Floating::is_zero(axis.range.size())) - step = stepHigh = stepLow = 1.0; + parent.painter.setPolygonToCircleFactor(0); + parent.painter.setPolygonStraightFactor(0); + + for (const auto &interval : parent.getIntervals(axisIndex)) { + if (Math::Floating::is_zero(interval.isSecond)) continue; + auto clippedBottom = std::max(interval.range.getMin(), + 0.0, + Math::Floating::less); + auto clippedSize = std::min(interval.range.getMax(), + 1.0, + Math::Floating::less) + - clippedBottom; + auto rect = Geom::Rect{ + Geom::Point::Coord(orientation, 0.0, clippedBottom), + {Geom::Size::Coord(orientation, 1.0, clippedSize)}}; - if (stepHigh == step) { - draw(axis.enabled, - axisIndex, - stepHigh, - enabled, - axis.range.size(), - text); - } - else if (stepLow == step) { - draw(axis.enabled, - axisIndex, - stepLow, - enabled, - axis.range.size(), - text); - } - else { - auto highWeight = - Math::Range<>::Raw(stepLow, stepHigh).rescale(step); - - auto lowWeight = (1.0 - highWeight) * enabled; - highWeight *= enabled; - - draw(axis.enabled, - axisIndex, - stepLow, - lowWeight, - axis.range.size(), - text); - draw(axis.enabled, - axisIndex, - stepHigh, - highWeight, - axis.range.size(), - text); + auto interlacingColor = + *axisStyle.interlacing.color + * Math::FuzzyBool::And(interval.weight, + interval.isSecond, + guides.interlacings); + + auto &canvas = parent.canvas; + canvas.save(); + canvas.setLineColor(Gfx::Color::Transparent()); + canvas.setBrushColor(interlacingColor); + if (auto &&eventTarget = + Events::Targets::axisInterlacing(axisIndex); + parent.rootEvents.draw.plot.axis.interlacing->invoke( + Events::OnRectDrawEvent(*eventTarget, + {rect, true}))) { + parent.painter.drawPolygon(rect.points()); + parent.renderedChart.emplace(Rect{rect, true}, + std::move(eventTarget)); + } + canvas.restore(); } } -void DrawInterlacing::draw( - const ::Anim::Interpolated &axisEnabled, - Gen::AxisId axisIndex, - double stepSize, - double weight, - double rangeSize, - bool text) const +void DrawInterlacing::drawTexts(Gen::AxisId axisIndex) const { + const auto &axis = parent.getAxis(axisIndex).measure; auto orientation = !+axisIndex; + auto origo = parent.origo().getCoord(orientation); const auto &guides = parent.plot->guides.at(axisIndex); const auto &axisStyle = parent.rootStyle.plot.getAxis(axisIndex); - const auto &axis = parent.getAxis(axisIndex).measure; - - auto interlacingColor = - *axisStyle.interlacing.color - * Math::FuzzyBool::And(weight, guides.interlacings); auto tickLength = axisStyle.ticks.length->get( parent.coordSys.getRect().size.getCoord(orientation), axisStyle.label.calculatedSize()); - auto tickColor = - *axisStyle.ticks.color - * Math::FuzzyBool::And(weight, guides.axisSticks); - - auto needTick = tickLength > 0 && !tickColor.isTransparent() + auto needTick = tickLength > 0 + && !axisStyle.ticks.color->isTransparent() + && guides.axisSticks != false && *axisStyle.ticks.lineWidth > 0; - auto textAlpha = - Math::FuzzyBool::And(weight, guides.labels); - - if ((!text && interlacingColor.isTransparent()) - || (text && !needTick && textAlpha == 0.0)) - return; - - auto singleLabelRange = Math::Floating::is_zero(rangeSize); - if (singleLabelRange && !text) return; - - double stripWidth{}; - if (!singleLabelRange) { - stripWidth = stepSize / rangeSize; - if (stripWidth <= 0) return; - } - - const auto origo = parent.origo(); - auto axisBottom = origo.getCoord(!orientation) + stripWidth; - - auto iMin = static_cast( - axisBottom > 0 ? std::floor(-origo.getCoord(!orientation) - / (2 * stripWidth)) - * 2 - : std::round((axis.range.getMin() - stepSize) - / stepSize)); - - if (axisBottom + iMin * stripWidth + stripWidth < 0.0) - iMin += 2 - * static_cast(std::ceil( - -(axisBottom + stripWidth) / (2 * stripWidth))); - - if (!text) { - parent.painter.setPolygonToCircleFactor(0); - parent.painter.setPolygonStraightFactor(0); - } - - auto Transform = [&](int x) - { - return std::pair{iMin + x * 2, - axisBottom + (iMin + x * 2) * stripWidth}; - }; - constexpr static auto Predicate = - [](const std::pair &x) - { - return x.second <= 1.0; - }; - - for (auto &&[i, bottom] : - std::views::iota(0) | std::views::transform(Transform) - | std::views::take_while(Predicate)) { - auto clippedBottom = - std::max(bottom, 0.0, Math::Floating::less); - auto clippedSize = - std::min(bottom + stripWidth, 1.0, Math::Floating::less) - - clippedBottom; - auto rect = Geom::Rect{ - Geom::Point::Coord(orientation, 0.0, clippedBottom), - {Geom::Size::Coord(orientation, 1.0, clippedSize)}}; - - if (text) { - if (auto tickPos = rect.bottomLeft().comp(!orientation) - + origo.comp(orientation); - bottom >= 0.0) { - if (textAlpha > 0) - drawDataLabel(axisEnabled, - axisIndex, - tickPos, - (i + 1) * stepSize, - axis.unit, - textAlpha); - - if (needTick) - drawSticks(tickLength, - tickColor, - axisIndex, - tickPos); - } - if (singleLabelRange) break; - if (auto tickPos = rect.topRight().comp(!orientation) - + origo.comp(orientation); - bottom + stripWidth <= 1.0) { - if (textAlpha > 0) - drawDataLabel(axisEnabled, - axisIndex, - tickPos, - (i + 2) * stepSize, - axis.unit, - textAlpha); - - if (needTick) - drawSticks(tickLength, - tickColor, - axisIndex, - tickPos); - } - } - else { - auto &canvas = parent.canvas; - canvas.save(); - canvas.setLineColor(Gfx::Color::Transparent()); - canvas.setBrushColor(interlacingColor); - if (auto &&eventTarget = - Events::Targets::axisInterlacing(axisIndex); - parent.rootEvents.draw.plot.axis.interlacing->invoke( - Events::OnRectDrawEvent(*eventTarget, - {rect, true}))) { - parent.painter.drawPolygon(rect.points()); - parent.renderedChart.emplace(Rect{rect, true}, - std::move(eventTarget)); - } - canvas.restore(); - } + auto needText = !axisStyle.label.color->isTransparent() + && guides.labels != false; + + if (!needText && !needTick) return; + + for (const auto &sep : parent.getSeparators(axisIndex)) { + if (!sep.label) continue; + auto tickPos = + Geom::Point::Coord(orientation, origo, sep.position); + if (needText) + drawDataLabel(axis.enabled, + axisIndex, + tickPos, + *sep.label, + axis.unit, + Math::FuzzyBool::And(sep.weight, + guides.labels)); + + if (needTick) + drawSticks(tickLength, + *axisStyle.ticks.color + * Math::FuzzyBool::And(sep.weight, + guides.axisSticks), + axisIndex, + tickPos); } } diff --git a/src/chart/rendering/drawinterlacing.h b/src/chart/rendering/drawinterlacing.h index 7b1849816..1e1935a7e 100644 --- a/src/chart/rendering/drawinterlacing.h +++ b/src/chart/rendering/drawinterlacing.h @@ -10,21 +10,12 @@ namespace Vizzu::Draw class DrawInterlacing { public: - void drawGeometries() const; - void drawTexts() const; + void drawGeometries(Gen::AxisId axisIndex) const; + void drawTexts(Gen::AxisId axisIndex) const; const DrawAxes &parent; private: - void draw(Gen::AxisId axisIndex, bool text) const; - - void draw(const ::Anim::Interpolated &enabled, - Gen::AxisId axisIndex, - double stepSize, - double weight, - double rangeSize, - bool text) const; - void drawDataLabel(const ::Anim::Interpolated &enabled, Gen::AxisId axisIndex, const Geom::Point &tickPos, diff --git a/src/chart/rendering/drawlegend.cpp b/src/chart/rendering/drawlegend.cpp index fd7c2011e..354eebaca 100644 --- a/src/chart/rendering/drawlegend.cpp +++ b/src/chart/rendering/drawlegend.cpp @@ -153,8 +153,18 @@ void DrawLegend::drawDimension(Info &info) const auto weight = item.weight(info.axis.dimension.factor); if (weight <= 0) continue; - auto itemRect = - getItemRect(info, item.position.combine()); + double pos{}; + if (auto &&start = item.startPos, &&end = item.endPos; + start && end) + pos = Math::Niebloid::interpolate(*start, + *end, + info.axis.dimension.factor); + else if (start) + pos = *start; + else + pos = *end; + + auto itemRect = getItemRect(info, pos); if (itemRect.y().getMin() > info.markerWindowRect.y().getMax() || itemRect.y().getMax() @@ -319,12 +329,28 @@ Math::Range<> DrawLegend::markersLegendRange(const Info &info) { auto res = Math::Range<>::Raw({}, {}); - if (info.measureEnabled > 0.0) res.include(6.0 * info.itemHeight); + std::uint32_t measMax = + Math::Floating::is_zero(info.measureEnabled) ? 0 : 6; - for (const auto &item : info.axis.dimension) - res.include( - (item.position.combine() + 1) * info.itemHeight); + std::uint32_t startMax{}; + std::uint32_t endMax{}; + for (const auto &item : info.axis.dimension) { + if (auto &&start = item.startPos) + startMax = std::max(startMax, *start + 1); + if (auto &&end = item.endPos) + endMax = std::max(endMax, *end + 1); + } + if (auto &&[min, max] = std::minmax(startMax, endMax); !min) + res.include(Math::Niebloid::interpolate(measMax, + max, + Math::FuzzyBool::And(1 - info.measureEnabled, + info.axis.dimension.factor))); + else + res.include(Math::Niebloid::interpolate(startMax, + endMax, + info.axis.dimension.factor) + * info.itemHeight); return res; } diff --git a/src/chart/rendering/drawplot.cpp b/src/chart/rendering/drawplot.cpp index 81ea25929..72ca72ae4 100644 --- a/src/chart/rendering/drawplot.cpp +++ b/src/chart/rendering/drawplot.cpp @@ -27,7 +27,7 @@ void DrawPlot::draw(Gfx::ICanvas &canvas, drawPlotArea(canvas, painter, false); - auto axes = DrawAxes{{ctx()}, canvas, painter}; + auto axes = DrawAxes{{ctx()}, canvas, painter, {}, {}}.init(); axes.drawGeometries(); auto clip = rootStyle.plot.overflow == Styles::Overflow::hidden; diff --git a/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs b/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs index c6f66746c..95e96af6f 100644 --- a/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs +++ b/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs @@ -126,7 +126,7 @@ const testSteps = [ (chart) => chart.animate({ - x: Object.assign({ set: 'node' }, axisOptions(true)), + x: Object.assign(axisOptions(true), { set: 'node', interlacing: false }), y: Object.assign({ set: ['edge', 'part', 'data'] }, axisOptions(true)), label: null, noop: null, From 47126dbc375afcde42b2e3805cc33ecaff5bed3c Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Nov 2024 19:09:06 +0100 Subject: [PATCH 3/9] Fix dimension ticks --- src/chart/rendering/drawaxes.cpp | 28 +++++++++++++------ src/chart/rendering/drawinterlacing.cpp | 3 +- test/e2e/test_cases/test_cases.json | 2 +- test/e2e/tests/features.json | 2 +- .../tests/features/events/drawing_events.mjs | 5 ++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index 355d4ef52..c9db811ee 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -59,6 +59,7 @@ const DrawAxes &&DrawAxes::init() && for (auto axisIndex : Refl::enum_values()) { auto &axis = getAxis(axisIndex); + auto measEnabled = axis.measure.enabled.combine(); auto &intervals = this->intervals[axisIndex]; auto &separators = this->separators[axisIndex]; const auto &guides = plot->guides.at(axisIndex); @@ -67,6 +68,12 @@ const DrawAxes &&DrawAxes::init() && auto weight = item.weight(axis.dimension.factor); if (Math::Floating::is_zero(weight)) continue; + bool needInterlacing = + measEnabled == 0.0 + || Math::FuzzyBool::And(Math::FuzzyBool::more(weight), + guides.interlacings.more()) + != false; + intervals.emplace_back(item.range, weight, Math::FuzzyBool::And( @@ -77,24 +84,29 @@ const DrawAxes &&DrawAxes::init() && (item.endPos ? *item.endPos : *item.startPos) % 2, axis.dimension.factor), - Math::FuzzyBool::more(weight), - guides.interlacings.more()), + needInterlacing), Interval::DimLabel{index, item.label, !item.startPos.isAuto(), !item.endPos.isAuto()}); + auto needSeparators = + measEnabled == 0.0 + || Math::FuzzyBool::And(Math::FuzzyBool::more(weight), + Math::FuzzyBool::Or(guides.axisSticks.more(), + guides.axisGuides.more())) + != false; + if (auto sepWeight = Math::Niebloid::interpolate( !item.startPos.isAuto() && *item.startPos, !item.endPos.isAuto() && *item.endPos, axis.dimension.factor); - sepWeight > 0) + needSeparators && sepWeight > 0) separators.emplace_back(item.range.getMin(), sepWeight); } - auto enabled = axis.measure.enabled.combine(); - if (enabled == 0.0) continue; + if (measEnabled == 0.0) continue; auto step = axis.measure.step.combine(); auto &&[min, max] = std::minmax( @@ -111,17 +123,17 @@ const DrawAxes &&DrawAxes::init() && step = stepHigh = stepLow = 1.0; if (stepHigh == step || stepLow == step) - generateMeasure(axisIndex, step, enabled); + generateMeasure(axisIndex, step, measEnabled); else { auto highWeight = Math::Range<>::Raw(stepLow, stepHigh).rescale(step); generateMeasure(axisIndex, stepLow, - (1.0 - highWeight) * enabled); + (1.0 - highWeight) * measEnabled); generateMeasure(axisIndex, stepHigh, - highWeight * enabled); + highWeight * measEnabled); } } diff --git a/src/chart/rendering/drawinterlacing.cpp b/src/chart/rendering/drawinterlacing.cpp index c90138ab6..c89323744 100644 --- a/src/chart/rendering/drawinterlacing.cpp +++ b/src/chart/rendering/drawinterlacing.cpp @@ -98,10 +98,9 @@ void DrawInterlacing::drawTexts(Gen::AxisId axisIndex) const if (!needText && !needTick) return; for (const auto &sep : parent.getSeparators(axisIndex)) { - if (!sep.label) continue; auto tickPos = Geom::Point::Coord(orientation, origo, sep.position); - if (needText) + if (needText && sep.label) drawDataLabel(axis.enabled, axisIndex, tickPos, diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index 607edb576..daa00196d 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -3230,7 +3230,7 @@ "refs": ["82a2780"] }, "web_content/cookbook/rendering/sparse_axis_labels": { - "refs": ["1cbd58c"] + "refs": ["0c9e8de"] }, "web_content/cookbook/style/d3_color_palette": { "refs": ["bd104b0"] diff --git a/test/e2e/tests/features.json b/test/e2e/tests/features.json index 0d383c088..5aec256cf 100644 --- a/test/e2e/tests/features.json +++ b/test/e2e/tests/features.json @@ -14,7 +14,7 @@ "refs": ["ba17dad"] }, "events/drawing_events": { - "refs": ["c3169b0"] + "refs": ["f3e3115"] }, "subtitle_caption": { "refs": ["f6dabf0"] diff --git a/test/e2e/tests/features/events/drawing_events.mjs b/test/e2e/tests/features/events/drawing_events.mjs index 62f0ac8d2..3335bad51 100644 --- a/test/e2e/tests/features/events/drawing_events.mjs +++ b/test/e2e/tests/features/events/drawing_events.mjs @@ -124,7 +124,7 @@ function setupEvents(chart) { }) }) chart.on('draw-complete', (e) => { - const references = [329751708, -2021415194] + const references = [-243018988] receivedEvents.push(e) const result = JSON.stringify(receivedEvents, null, 2) const hash = (str) => @@ -167,7 +167,8 @@ const testSteps = [ legend: 'size', geometry: 'circle' } - }) + }, + 0) } ] From 794d79e5342ae06d4391d42583d6805992a42747 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Nov 2024 19:14:21 +0100 Subject: [PATCH 4/9] clang-tidy --- src/chart/rendering/drawaxes.cpp | 4 ++-- src/chart/rendering/drawaxes.h | 27 +++++++++++++++++---------- src/chart/rendering/drawlegend.cpp | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index c9db811ee..db89faf3b 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -57,7 +57,7 @@ void DrawAxes::drawLabels() const const DrawAxes &&DrawAxes::init() && { for (auto axisIndex : Refl::enum_values()) { - auto &axis = getAxis(axisIndex); + const auto &axis = getAxis(axisIndex); auto measEnabled = axis.measure.enabled.combine(); auto &intervals = this->intervals[axisIndex]; @@ -147,7 +147,7 @@ void DrawAxes::generateMeasure(Gen::AxisId axisIndex, auto orientation = !+axisIndex; const auto &meas = getAxis(axisIndex).measure; auto rangeSize = meas.range.size(); - bool singleLabelRange = Math::Floating::is_zero(rangeSize); + auto singleLabelRange = Math::Floating::is_zero(rangeSize); double stripWidth{}; if (!singleLabelRange) { diff --git a/src/chart/rendering/drawaxes.h b/src/chart/rendering/drawaxes.h index 9474bbfca..4afdfc69f 100644 --- a/src/chart/rendering/drawaxes.h +++ b/src/chart/rendering/drawaxes.h @@ -10,6 +10,7 @@ namespace Vizzu::Draw { +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) class DrawAxes : public DrawingContext { public: @@ -19,17 +20,21 @@ class DrawAxes : public DrawingContext Gfx::ICanvas &canvas; Painter &painter; - const Gen::Axis &getAxis(Gen::AxisId axisIndex) const + [[nodiscard]] const Gen::Axis &getAxis( + Gen::AxisId axisIndex) const { return plot->axises.at(axisIndex); } - Geom::Point origo() const { return plot->axises.origo(); } + [[nodiscard]] Geom::Point origo() const + { + return plot->axises.origo(); + } struct Separator { - double position; - double weight; + double position{}; + double weight{}; std::optional label{}; }; @@ -49,23 +54,25 @@ class DrawAxes : public DrawingContext || (index == ::Anim::second && end); } }; - Math::Range<> range; - double weight; - double isSecond; + Math::Range<> range{}; + double weight{}; + double isSecond{}; std::optional label{}; }; - const DrawAxes &&init() &&; + [[nodiscard]] const DrawAxes &&init() &&; Refl::EnumArray> intervals; Refl::EnumArray> separators; - const auto &getIntervals(Gen::AxisId axisIndex) const + [[nodiscard]] const auto &getIntervals( + Gen::AxisId axisIndex) const { return intervals[axisIndex]; } - const auto &getSeparators(Gen::AxisId axisIndex) const + [[nodiscard]] const auto &getSeparators( + Gen::AxisId axisIndex) const { return separators[axisIndex]; } diff --git a/src/chart/rendering/drawlegend.cpp b/src/chart/rendering/drawlegend.cpp index 354eebaca..cce00099a 100644 --- a/src/chart/rendering/drawlegend.cpp +++ b/src/chart/rendering/drawlegend.cpp @@ -329,7 +329,7 @@ Math::Range<> DrawLegend::markersLegendRange(const Info &info) { auto res = Math::Range<>::Raw({}, {}); - std::uint32_t measMax = + const std::uint32_t measMax = Math::Floating::is_zero(info.measureEnabled) ? 0 : 6; std::uint32_t startMax{}; From b97d7028ee3eef7e54951f7869bd884b45e050dd Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Nov 2024 19:34:28 +0100 Subject: [PATCH 5/9] format --- .../tests/features/events/drawing_events.mjs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/test/e2e/tests/features/events/drawing_events.mjs b/test/e2e/tests/features/events/drawing_events.mjs index 3335bad51..5d43fdd4c 100644 --- a/test/e2e/tests/features/events/drawing_events.mjs +++ b/test/e2e/tests/features/events/drawing_events.mjs @@ -155,20 +155,22 @@ const data = { const testSteps = [ (chart) => { setupEvents(chart) - return chart.animate({ - data, - config: { - color: 'Foo', - x: { set: 'Foo', guides: true, ticks: true, axis: true }, - y: { set: 'Bar', guides: true, ticks: true, axis: true }, - size: 'Baz', - label: 'Baz', - title: 'My Chart', - legend: 'size', - geometry: 'circle' - } - }, - 0) + return chart.animate( + { + data, + config: { + color: 'Foo', + x: { set: 'Foo', guides: true, ticks: true, axis: true }, + y: { set: 'Bar', guides: true, ticks: true, axis: true }, + size: 'Baz', + label: 'Baz', + title: 'My Chart', + legend: 'size', + geometry: 'circle' + } + }, + 0 + ) } ] From be8c04082603800698d5078efa54cf2c3d974b76 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Nov 2024 20:06:44 +0100 Subject: [PATCH 6/9] clang-tidy --- src/chart/rendering/drawaxes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chart/rendering/drawaxes.cpp b/src/chart/rendering/drawaxes.cpp index db89faf3b..ec364b619 100644 --- a/src/chart/rendering/drawaxes.cpp +++ b/src/chart/rendering/drawaxes.cpp @@ -68,7 +68,7 @@ const DrawAxes &&DrawAxes::init() && auto weight = item.weight(axis.dimension.factor); if (Math::Floating::is_zero(weight)) continue; - bool needInterlacing = + auto needInterlacing = measEnabled == 0.0 || Math::FuzzyBool::And(Math::FuzzyBool::more(weight), guides.interlacings.more()) From f6b5ba9351838b39c3ed7210b4bd467548c3b1a7 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Fri, 22 Nov 2024 22:22:50 +0100 Subject: [PATCH 7/9] fix network graph test --- test/e2e/test_cases/test_cases.json | 2 +- .../web_content/cookbook/chart_types/network_graph.mjs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/test_cases/test_cases.json b/test/e2e/test_cases/test_cases.json index daa00196d..09736e72c 100644 --- a/test/e2e/test_cases/test_cases.json +++ b/test/e2e/test_cases/test_cases.json @@ -3206,7 +3206,7 @@ "refs": ["9ba4e3d"] }, "web_content/cookbook/chart_types/network_graph": { - "refs": ["6508423"] + "refs": ["c7734da"] }, "web_content/cookbook/chart_types/step_line_chart": { "refs": ["7c00ab8"] diff --git a/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs b/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs index 95e96af6f..a95f39cd1 100644 --- a/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs +++ b/test/e2e/test_cases/web_content/cookbook/chart_types/network_graph.mjs @@ -1,9 +1,9 @@ function axisOptions(on) { return { - axis: on, + axis: on ? 'auto' : on, labels: on, markerGuides: false, - interlacing: on, + interlacing: on ? 'auto' : on, title: on ? 'auto' : null } } @@ -126,7 +126,7 @@ const testSteps = [ (chart) => chart.animate({ - x: Object.assign(axisOptions(true), { set: 'node', interlacing: false }), + x: Object.assign({ set: 'node' }, axisOptions(true)), y: Object.assign({ set: ['edge', 'part', 'data'] }, axisOptions(true)), label: null, noop: null, From 5630045c9e01e4ad8833ffe29ed6d245210008f9 Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Sat, 23 Nov 2024 06:34:21 +0100 Subject: [PATCH 8/9] clang-tidy --- src/chart/rendering/drawlegend.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/chart/rendering/drawlegend.cpp b/src/chart/rendering/drawlegend.cpp index cce00099a..f15299b6b 100644 --- a/src/chart/rendering/drawlegend.cpp +++ b/src/chart/rendering/drawlegend.cpp @@ -154,8 +154,10 @@ void DrawLegend::drawDimension(Info &info) const if (weight <= 0) continue; double pos{}; - if (auto &&start = item.startPos, &&end = item.endPos; - start && end) + + auto &&start = item.startPos; + auto &&end = item.endPos; + if (start && end) pos = Math::Niebloid::interpolate(*start, *end, info.axis.dimension.factor); From 46f1cb385a1ba17d6a36ec31cb0c2bf1c329566f Mon Sep 17 00:00:00 2001 From: Bela Schaum Date: Sat, 23 Nov 2024 07:17:00 +0100 Subject: [PATCH 9/9] Fix measure guides + add tc + add changelog --- CHANGELOG.md | 3 +++ src/chart/rendering/drawguides.cpp | 4 +--- test/e2e/tests/features.json | 2 +- .../tests/features/events/drawing_events.mjs | 2 +- test/e2e/tests/fixes.json | 3 +++ test/e2e/tests/fixes/174.mjs | 22 +++++++++++++++++++ 6 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 test/e2e/tests/fixes/174.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 990bf3f53..7eb99a04f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ - Fix chaotic axis labels on sorted chart with multiple dimension. - Fix Mekko charts: The main axis handled as dimension. - LabelLevel can be used to handle measure axis as dimension axis. +- Enable dimension axis ticks and interlacing. +- Enable measure axis guides. +- Fix dimension axis guides on sorted chart. ## [0.15.0] - 2024-10-28 diff --git a/src/chart/rendering/drawguides.cpp b/src/chart/rendering/drawguides.cpp index 30a8d5335..f6e41c903 100644 --- a/src/chart/rendering/drawguides.cpp +++ b/src/chart/rendering/drawguides.cpp @@ -21,9 +21,7 @@ void DrawGuides::draw(Gen::AxisId axisId) auto baseColor = *guideStyle.color; - if (const auto &axis = parent.getAxis(axisId).dimension; - !baseColor.isTransparent() && !axis.empty() - && *guideStyle.lineWidth > 0 + if (!baseColor.isTransparent() && *guideStyle.lineWidth > 0 && parent.plot->guides.at(axisId).axisGuides != false) { parent.canvas.setLineWidth(*guideStyle.lineWidth); diff --git a/test/e2e/tests/features.json b/test/e2e/tests/features.json index 5aec256cf..b345352da 100644 --- a/test/e2e/tests/features.json +++ b/test/e2e/tests/features.json @@ -14,7 +14,7 @@ "refs": ["ba17dad"] }, "events/drawing_events": { - "refs": ["f3e3115"] + "refs": ["962d7d3"] }, "subtitle_caption": { "refs": ["f6dabf0"] diff --git a/test/e2e/tests/features/events/drawing_events.mjs b/test/e2e/tests/features/events/drawing_events.mjs index 5d43fdd4c..90906db90 100644 --- a/test/e2e/tests/features/events/drawing_events.mjs +++ b/test/e2e/tests/features/events/drawing_events.mjs @@ -124,7 +124,7 @@ function setupEvents(chart) { }) }) chart.on('draw-complete', (e) => { - const references = [-243018988] + const references = [-1089061267, -1601698349] receivedEvents.push(e) const result = JSON.stringify(receivedEvents, null, 2) const hash = (str) => diff --git a/test/e2e/tests/fixes.json b/test/e2e/tests/fixes.json index efcf4ee91..7c568ad27 100644 --- a/test/e2e/tests/fixes.json +++ b/test/e2e/tests/fixes.json @@ -19,6 +19,9 @@ "163": { "refs": ["a82431f"] }, + "174": { + "refs": ["39e5361"] + }, "250": { "refs": ["e140a3d"] }, diff --git a/test/e2e/tests/fixes/174.mjs b/test/e2e/tests/fixes/174.mjs new file mode 100644 index 000000000..d212e5792 --- /dev/null +++ b/test/e2e/tests/fixes/174.mjs @@ -0,0 +1,22 @@ +const testSteps = [ + (chart) => { + const data = { + series: [ + { name: 'Foo', values: ['Alice', 'Bob', 'Ted'] }, + { name: 'Bar', values: [15, 32, 12] }, + { name: 'Baz', values: [5, 3, 2] } + ] + } + + return chart.animate({ data }) + }, + (chart) => + chart.animate({ + x: { set: 'Foo', title: 'Valami', ticks: true, guides: true }, + y: { set: 'Bar', title: 'Valami', ticks: true, guides: true, interlacing: false }, + geometry: 'circle', + sort: 'byValue' + }) +] + +export default testSteps