Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix single value range interpolation #575

Merged
merged 7 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Fix axis interpolation. From now the axis and axis labels are following the markers.
- Fix measure axis labels when the range started after the 2000th step value from origo.
- Remove marker labels at intermediate steps.
- Fix single value range interpolation and show.

### Added

Expand Down
56 changes: 49 additions & 7 deletions src/chart/generator/axis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,57 @@ MeasureAxis interpolate(const MeasureAxis &op0,
const auto s0 = op0.range.size();
const auto s1 = op1.range.size();

if (auto s0Zero = is_zero(s0); s0Zero || is_zero(s1)) {
if (auto s0Zero = is_zero(s0), s1Zero = is_zero(s1);
s0Zero && s1Zero) {
res.range = Math::Range<double>::Raw(
Math::interpolate(op0.range.getMin(),
op1.range.getMin(),
factor),
Math::interpolate(op0.range.getMax(),
op1.range.getMax(),
factor));
res.step = s0Zero ? op1.step : op0.step;
res.step = interpolate(op0.step, op1.step, factor);
}
else if (s1Zero) {
auto size = factor == 1.0 ? MAX : s0 / (1 - factor);

auto middleAt = Math::interpolate(
op0.range.rescale(op1.range.middle()),
0.0,
factor);

res.range = Math::Range<double>::Raw(
op1.range.middle() - middleAt * size,
op1.range.middle()
+ (factor == 1.0 ? 0.0 : (1 - middleAt) * size));

auto step = op0.step.get() / s0 * size;
auto max = std::copysign(MAX, step);

res.step = interpolate(op0.step,
Anim::Interpolated{max},
Math::Range<double>::Raw(op0.step.get(), max)
.rescale(step));
}
else if (s0Zero) {
auto size = factor == 0.0 ? MAX : s1 / factor;

auto middleAt = Math::interpolate(0.0,
op1.range.rescale(op0.range.middle()),
factor);

res.range = Math::Range<double>::Raw(
op0.range.middle() - middleAt * size,
op0.range.middle()
+ (factor == 0.0 ? 0.0 : (1 - middleAt) * size));

auto step = op1.step.get() / s1 * size;
auto max = std::copysign(MAX, step);

res.step = interpolate(op1.step,
Anim::Interpolated{max},
Math::Range<double>::Raw(op1.step.get(), max)
.rescale(step));
}
else {
auto s0Inv = 1 / s0;
Expand All @@ -99,22 +141,22 @@ MeasureAxis interpolate(const MeasureAxis &op0,
const auto interp =
Math::interpolate(s0Inv, s1Inv, factor);

const auto s = is_zero(interp) ? MAX : 1 / interp;
const auto size = is_zero(interp) ? MAX : 1 / interp;

res.range = Math::Range<double>::Raw(
Math::interpolate(op0.range.getMin() * s0Inv,
op1.range.getMin() * s1Inv,
factor)
* s,
* size,
Math::interpolate(op0.range.getMax() * s0Inv,
op1.range.getMax() * s1Inv,
factor)
* s);
* size);

auto step = Math::interpolate(op0.step.get() * s0Inv,
op1.step.get() * s1Inv,
factor)
* s;
* size;

if (auto op0sign = std::signbit(op0.step.get());
op0sign == std::signbit(op1.step.get()))
Expand Down Expand Up @@ -166,7 +208,7 @@ bool DimensionAxis::add(const Data::SliceIndex &index,
}
values.emplace(std::piecewise_construct,
std::tuple{index},
std::tuple{range, value, enabled});
std::tuple{range, value});

return true;
}
Expand Down
10 changes: 4 additions & 6 deletions src/chart/generator/axis.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,12 @@ struct DimensionAxis
std::string categoryValue;
double weight;

Item(Math::Range<double> range,
double value,
double enabled) :
Item(Math::Range<double> range, double value) :
start(true),
end(true),
range(range),
value(value),
weight(enabled)
weight(1.0)
{}

Item(const Item &item, bool starter, double factor) :
Expand All @@ -86,12 +84,12 @@ struct DimensionAxis
colorBase(item.colorBase),
label(item.label),
categoryValue(item.categoryValue),
weight(item.weight * factor)
weight(Math::FuzzyBool::And(item.weight, factor))
{}

bool operator==(const Item &other) const
{
return range == other.range;
return range == other.range && weight == other.weight;
}

[[nodiscard]] bool presentAt(
Expand Down
7 changes: 7 additions & 0 deletions src/chart/generator/plotbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,11 +581,18 @@ void PlotBuilder::normalizeColors()
Math::Range<double> lightness;
Math::Range<double> color;

bool wasValidMarker{};
for (auto &marker : plot->markers) {
if (!marker.enabled) continue;
auto &&cbase = marker.colorBase.get();
if (!cbase.isDiscrete()) color.include(cbase.getPos());
lightness.include(cbase.getLightness());
wasValidMarker = true;
}

if (!wasValidMarker) {
lightness.include(0.5);
color.include(0);
}

color = plot->getOptions()
Expand Down
40 changes: 22 additions & 18 deletions src/chart/rendering/drawinterlacing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,32 +133,35 @@ void DrawInterlacing::draw(
auto textAlpha =
Math::FuzzyBool::And<double>(weight, enabled.labels);

if (std::signbit(rangeSize) != std::signbit(stepSize)
|| Math::Floating::is_zero(rangeSize))
return;

auto stripWidth = stepSize / rangeSize;
auto singleLabelRange = Math::Floating::is_zero(rangeSize);

double stripWidth{};
if (singleLabelRange)
stepSize = 1.0;
else {
stripWidth = stepSize / rangeSize;
if (stripWidth <= 0) return;
}

auto axisBottom = axis.origo() + stripWidth;

auto iMin =
axisBottom > 0 ? static_cast<int>(
std::floor(-axis.origo() / (2 * stripWidth)))
: static_cast<int>(
(axis.range.getMin() - stepSize) / 2);
axisBottom > 0
? std::floor(-axis.origo() / (2 * stripWidth)) * 2
: std::round(axis.range.getMin() - stepSize);

if (stripWidth <= 0) return;
auto interlaceCount = 0U;
const auto maxInterlaceCount = 1000U;
for (int i = iMin; ++interlaceCount <= maxInterlaceCount;
++i) {
auto bottom = axisBottom + i * 2 * stripWidth;
if (bottom >= 1.0) break;
for (auto i = static_cast<int>(iMin);
++interlaceCount <= maxInterlaceCount;
i += 2) {
auto bottom = axisBottom + i * stripWidth;
if (bottom > 1.0) break;
auto clippedBottom = bottom;
auto top = bottom + stripWidth;
auto clipTop = top > 1.0;
auto clipBottom = bottom < 0.0;
auto topUnderflow = top <= 0.0;
auto topUnderflow = top < 0.0;
if (clipTop) top = 1.0;
if (clipBottom) clippedBottom = 0.0;

Expand All @@ -174,7 +177,7 @@ void DrawInterlacing::draw(
canvas.setFont(Gfx::Font{axisStyle.label});

if (!clipBottom) {
auto value = (i * 2 + 1) * stepSize;
auto value = (i + 1) * stepSize;
auto tickPos =
rect.bottomLeft().comp(!horizontal)
+ origo.comp(horizontal);
Expand All @@ -192,8 +195,9 @@ void DrawInterlacing::draw(
horizontal,
tickPos);
}
if (singleLabelRange) break;
if (!clipTop) {
auto value = (i * 2 + 2) * stepSize;
auto value = (i + 2) * stepSize;
auto tickPos =
rect.topRight().comp(!horizontal)
+ origo.comp(horizontal);
Expand All @@ -212,7 +216,7 @@ void DrawInterlacing::draw(
tickPos);
}
}
else {
else if (!singleLabelRange) {
canvas.save();

canvas.setLineColor(Gfx::Color::Transparent());
Expand Down
26 changes: 16 additions & 10 deletions src/chart/rendering/drawlegend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
#include "base/gfx/colortransform.h"
#include "base/gfx/draw/roundedrect.h"
#include "base/gfx/lineargradient.h"
#include "base/math/floating.h"
#include "base/math/fuzzybool.h"
#include "base/math/range.h"
#include "base/text/smartstring.h"
#include "chart/generator/plot.h" // NOLINT(misc-include-cleaner)
#include "chart/main/events.h"
Expand Down Expand Up @@ -70,12 +70,14 @@ void DrawLegend::draw(Gfx::ICanvas &canvas,
{1.0, {}},
}}}}};

info.properties.scrollHeight = markersLegendFullSize(info);
auto &&range = markersLegendRange(info);
info.properties.scrollHeight = range.size();
auto yOverflow =
info.properties.scrollHeight - markerWindowHeight;
if (std::signbit(yOverflow)) yOverflow = 0.0;
info.properties.scrollTop =
style.translateY->get(yOverflow, info.itemHeight);
style.translateY->get(yOverflow, info.itemHeight)
+ range.getMin();

DrawBackground{{ctx()}}.draw(canvas,
legendLayout,
Expand Down Expand Up @@ -324,17 +326,21 @@ Geom::Rect DrawLegend::getBarRect(const Info &info)
return res;
}

double DrawLegend::markersLegendFullSize(const Info &info)
Math::Range<double> DrawLegend::markersLegendRange(const Info &info)
{
double itemCount{info.measureEnabled <= 0.0 ? 0.0 : 6.0};
Math::Range<double> res;
if (info.measureEnabled > 0.0) {
res.include(0.0);
res.include(6.0 * info.itemHeight);
}

if (info.dimensionEnabled)
for (const auto &value : info.dimension)
if (auto itemPos = value.second.range.getMin() + 1;
value.second.weight > 0
&& Math::Floating::less(itemCount, itemPos))
itemCount = itemPos;
res.include({value.second.range.getMin()
* info.itemHeight,
(value.second.range.getMax() + 1) * info.itemHeight});

return itemCount * info.itemHeight;
return res;
}

void DrawLegend::colorBar(const Info &info,
Expand Down
2 changes: 1 addition & 1 deletion src/chart/rendering/drawlegend.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class DrawLegend : public DrawingContext
getLabelRect(const Info &info, const Geom::Rect &itemRect);
[[nodiscard]] static Geom::Rect getBarRect(const Info &info);

[[nodiscard]] static double markersLegendFullSize(
[[nodiscard]] static Math::Range<double> markersLegendRange(
const Info &info);

void extremaLabel(const Info &info,
Expand Down
12 changes: 12 additions & 0 deletions test/e2e/tests/config_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@
},
"geometry/animated_area-circle": {
"refs": ["d037f4d"]
},
"channel_ranges/dim_axis": {
"refs": ["0e9a9fd"]
},
"channel_ranges/dim_color": {
"refs": ["c5b912f"]
},
"channel_ranges/dim_lightness": {
"refs": ["b26b94f"]
},
"channel_ranges/meas_axis": {
"refs": ["7e9e5ba"]
}
}
}
40 changes: 40 additions & 0 deletions test/e2e/tests/config_tests/channel_ranges/dim_axis.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const testSteps = [
(chart) => {
const data = {
series: [
{ name: 'Foo', values: ['Alice', 'Bob', 'Ted'] },
{ name: 'Bar', values: ['Happy', 'Happy', 'Sad'] },
{ name: 'Baz', values: [1, 2, 3] }
]
}

return chart.animate({ data })
},
(chart) =>
chart.animate({
x: { set: 'Foo' },
y: { set: 'Bar' }
}),
(chart) =>
chart.animate({
x: { range: { min: '100%', max: '0%' } },
y: { range: { min: '100%', max: '0%' } }
}),
(chart) =>
chart.animate({
x: { attach: 'Baz', range: { min: 'auto', max: 'auto' } },
y: { range: { min: '-100%', max: '110%' } },
label: 'Foo',
color: 'Foo'
}),
(chart) =>
chart.animate({
y: { range: { min: '1', max: '1' } }
}),
(chart) =>
chart.animate({
y: { range: { min: '100%', max: '-100%' } }
})
]

export default testSteps
Loading