Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Axis refactor v6 - Fix legend multi-dim, link markers only anyAxis #600

Merged
merged 7 commits into from
Nov 7, 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
- From now, it is prohibited to set only aggregator without series name.
- Fix series parsing when aggregator comes first.
- Fix disappearing dimension when aggregated dimension was already set.
- Fix legend with multiple dimension duplicated markers:
- Markers of color are never merged.
- Markers of size are always merged.
- Markers of lightness are only merged when labelLevel == 0.
- When merge happens, the marker shows the middle value of lightness.
- Remove rare fantom empty marker space on scrollable legend.

## [0.15.0] - 2024-10-28

Expand Down
2 changes: 1 addition & 1 deletion src/chart/generator/axis.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ struct DimensionAxis
bool add(const Data::SliceIndex &index,
double value,
const Math::Range<double> &range,
bool merge = true);
bool merge);
[[nodiscard]] bool operator==(const DimensionAxis &other) const;

[[nodiscard]] auto begin()
Expand Down
2 changes: 0 additions & 2 deletions src/chart/generator/marker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ bool Marker::connectMarkers(bool first,
next->polarConnection = polarConnection && first;
return true;
}
if (next && main)
next->prevMainMarker = RelativeMarkerIndex{next->idx, {}};

return false;
}
Expand Down
3 changes: 2 additions & 1 deletion src/chart/generator/marker.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ class Marker
return lhs.idx == rhs.idx;
}
};
::Anim::Interpolated<RelativeMarkerIndex> prevMainMarker;
::Anim::Interpolated<RelativeMarkerIndex> prevMainMarker{
RelativeMarkerIndex{idx, {}}};
::Anim::Interpolated<bool> polarConnection{false};

static bool connectMarkers(bool first,
Expand Down
152 changes: 78 additions & 74 deletions src/chart/generator/plotbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,24 @@ PlotBuilder::PlotBuilder(const Data::DataTable &dataTable,
std::size_t mainBucketSize{};
auto &&subBuckets = generateMarkers(mainBucketSize);

if (!plot->options->getChannels().anyAxisSet()) {
if (!plot->options->getChannels().anyAxisSet())
addSpecLayout(subBuckets);
calcLegendAndLabel(dataTable);
normalizeColors();
if (plot->options->geometry != ShapeType::circle)
normalizeSizes();
}
else {
addSeparation(subBuckets, mainBucketSize);
calcAxises(dataTable);
calcLegendAndLabel(dataTable);
normalizeSizes();
normalizeColors();
addAlignment(subBuckets);
}
else
addAxisLayout(subBuckets, mainBucketSize, dataTable);

calcLegendAndLabel(dataTable);
normalizeColors();
normalizeSizes();
}

void PlotBuilder::addAxisLayout(Buckets &subBuckets,
const std::size_t &mainBucketSize,
const Data::DataTable &dataTable)
{
linkMarkers(subBuckets);
addSeparation(subBuckets, mainBucketSize);
calcAxises(dataTable);
addAlignment(subBuckets);
}

void PlotBuilder::initDimensionTrackers()
Expand Down Expand Up @@ -103,25 +106,7 @@ Buckets PlotBuilder::generateMarkers(std::size_t &mainBucketSize)
plot->markersInfo.insert({first->second,
Plot::MarkerInfo{Plot::MarkerInfoContent{marker}}});

Buckets buckets(plot->markers);
auto &&hasMarkerConnection =
linkMarkers(buckets.sort(&Marker::mainId), true);
std::ignore = linkMarkers(buckets.sort(&Marker::subId), false);

if (hasMarkerConnection
&& plot->getOptions()->geometry.get() == ShapeType::line
&& plot->getOptions()
->getChannels()
.at(AxisId::x)
.isDimension()
&& plot->getOptions()
->getChannels()
.at(AxisId::y)
.isDimension()) {
plot->markerConnectionOrientation.emplace(
*plot->getOptions()->orientation.get());
}
return buckets;
return Buckets{plot->markers};
}

std::vector<PlotBuilder::BucketInfo>
Expand All @@ -137,7 +122,7 @@ PlotBuilder::sortedBuckets(const Buckets &buckets, bool main) const
auto it = std::ranges::lower_bound(sorted,
idx.itemId,
std::less{},
std::mem_fn(&BucketInfo::index));
&BucketInfo::index);
if (it == sorted.end() || it->index != idx.itemId)
it = sorted.emplace(it, idx.itemId, 0.0);

Expand Down Expand Up @@ -182,6 +167,27 @@ void PlotBuilder::addSpecLayout(Buckets &buckets)
}
}

void PlotBuilder::linkMarkers(Buckets &buckets)
{
auto &&hasMarkerConnection =
linkMarkers(buckets.sort(&Marker::mainId), true);
std::ignore = linkMarkers(buckets.sort(&Marker::subId), false);

if (hasMarkerConnection
&& plot->getOptions()->geometry.get() == ShapeType::line
&& plot->getOptions()
->getChannels()
.at(AxisId::x)
.isDimension()
&& plot->getOptions()
->getChannels()
.at(AxisId::y)
.isDimension()) {
plot->markerConnectionOrientation.emplace(
*plot->getOptions()->orientation.get());
}
}

bool PlotBuilder::linkMarkers(const Buckets &buckets, bool main) const
{
auto &&sorted = sortedBuckets(buckets, main);
Expand Down Expand Up @@ -212,19 +218,15 @@ bool PlotBuilder::linkMarkers(const Buckets &buckets, bool main) const
for (std::size_t ix{}, max = sorted.size(); ix < max; ++ix) {
auto &o = dimOffset[ix];
for (const auto &bucket : buckets) {
auto &&ids = std::ranges::views::values(bucket);
auto sIx = sorted[ix].index;
auto it = std::ranges::lower_bound(bucket,
auto it = std::ranges::lower_bound(ids,
sIx,
std::less{},
[](const std::pair<Marker &,
const Data::MarkerId &> &p)
{
return p.second.itemId;
});
if (it == bucket.end() || (*it).second.itemId != sIx)
continue;

auto &marker = (*it).first;
&Data::MarkerId::itemId);
if (it == ids.end() || (*it).itemId != sIx) continue;

auto &marker = **it.base().base().base();
if (!marker.enabled) continue;
o = std::max(o,
marker.size.*coord,
Expand Down Expand Up @@ -253,33 +255,24 @@ bool PlotBuilder::linkMarkers(const Buckets &buckets, bool main) const
double prevPos{};
for (auto i = 0U; i < sorted.size(); ++i) {
auto idAct = sorted[i].index;
auto it = std::ranges::lower_bound(bucket,
auto &&ids = std::ranges::views::values(bucket);
auto it = std::ranges::lower_bound(ids,
idAct,
std::less{},
[](const std::pair<Marker &, const Data::MarkerId &>
&p)
{
return p.second.itemId;
});
Marker *act =
it == bucket.end() || (*it).second.itemId != idAct
? nullptr
: &(*it).first;
&Data::MarkerId::itemId);
Marker *act = it == ids.end() || (*it).itemId != idAct
? nullptr
: *it.base().base().base();

auto iNext = (i + 1) % sorted.size();
auto idNext = sorted[iNext].index;
it = std::ranges::lower_bound(bucket,
it = std::ranges::lower_bound(ids,
idNext,
std::less{},
[](const std::pair<Marker &, const Data::MarkerId &>
&p)
{
return p.second.itemId;
});
Marker *next =
it == bucket.end() || (*it).second.itemId != idNext
? nullptr
: &(*it).first;
&Data::MarkerId::itemId);
Marker *next = it == ids.end() || (*it).itemId != idNext
? nullptr
: *it.base().base().base();

if (act)
prevPos = act->position.*coord +=
Expand Down Expand Up @@ -394,8 +387,8 @@ void PlotBuilder::calcAxis(const Data::DataTable &dataTable, T type)
}
else {
constexpr bool isTypeAxis = std::is_same_v<T, AxisId>;
if constexpr (isTypeAxis) {
auto merge = scale.labelLevel == 0;
if constexpr (auto merge = scale.labelLevel == 0;
isTypeAxis) {
for (const auto &marker : plot->markers) {
if (!marker.enabled) continue;

Expand All @@ -415,13 +408,17 @@ void PlotBuilder::calcAxis(const Data::DataTable &dataTable, T type)
else {
const auto &indices = std::get<1>(stats.at(type));

double count = 0;
for (auto i = 0U; i < indices.size(); ++i)
double count{};
for (std::size_t i{}; i < indices.size(); ++i)
if (const auto &sliceIndex = indices[i];
sliceIndex
&& axis.dimension.add(*sliceIndex,
i,
{count, count}))
count,
{static_cast<double>(i),
static_cast<double>(i)},
type == LegendId::size
|| (type == LegendId::lightness
&& merge)))
count += 1;
}
auto hasLabel = axis.dimension.setLabels(
Expand Down Expand Up @@ -519,6 +516,10 @@ void PlotBuilder::addSeparation(const Buckets &subBuckets,

void PlotBuilder::normalizeSizes()
{
if (plot->getOptions()->geometry == ShapeType::circle
&& !plot->options->getChannels().anyAxisSet())
return;

if (plot->getOptions()->geometry == ShapeType::circle
|| plot->getOptions()->geometry == ShapeType::line) {
Math::Range<double> size;
Expand Down Expand Up @@ -584,17 +585,20 @@ void PlotBuilder::normalizeColors()

for (auto &item :
plot->axises.at(LegendId::color).dimension)
item.colorBase =
ColorBase(static_cast<uint32_t>(item.value), 0.5);
item.colorBase = ColorBase(
static_cast<uint32_t>(item.range.middle()),
0.5);
break;
case LegendId::lightness:
plot->axises.at(LegendId::lightness).measure.range =
lightness;

for (auto &item :
plot->axises.at(LegendId::lightness).dimension) {
item.value = lightness.rescale(item.value);
item.colorBase = ColorBase(0U, item.value);
item.range = Math::Range<double>::Raw(
lightness.rescale(item.range.getMin()),
lightness.rescale(item.range.getMax()));
item.colorBase = ColorBase(0U, item.range.middle());
}
break;
default:;
Expand Down
4 changes: 4 additions & 0 deletions src/chart/generator/plotbuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class PlotBuilder

void initDimensionTrackers();
Buckets generateMarkers(std::size_t &mainBucketSize);
void linkMarkers(Buckets &subBuckets);
[[nodiscard]] bool linkMarkers(const Buckets &buckets,
bool main) const;
void calcAxises(const Data::DataTable &dataTable);
Expand All @@ -49,6 +50,9 @@ class PlotBuilder
[[nodiscard]] std::vector<BucketInfo>
sortedBuckets(const Buckets &buckets, bool main) const;
void addSpecLayout(Buckets &buckets);
void addAxisLayout(Buckets &subBuckets,
const std::size_t &mainBucketSize,
const Data::DataTable &dataTable);
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/chart/options/autoparam.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ template <typename Type, bool nullable = false> struct AutoParam

bool operator==(const AutoParam &other) const = default;

const std::optional<Type> &getValueOrAuto() { return value; }

private:
bool autoSet{};
std::optional<Type> value;
Expand Down
9 changes: 4 additions & 5 deletions src/chart/options/channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,15 @@ bool Channel::isSeriesUsed(const Data::SeriesIndex &index) const

void Channel::reset()
{
set.measureId = std::nullopt;
set.dimensionIds.clear();
title = Base::AutoParam<std::string, true>{};
set = {};
labelLevel = {};
title = {};
axis = Base::AutoBool();
labels = Base::AutoBool();
ticks = Base::AutoBool();
interlacing = Base::AutoBool();
guides = Base::AutoBool();
markerGuides = Base::AutoBool();
labelLevel = 0;
interlacing = Base::AutoBool();
}

bool Channel::isEmpty() const
Expand Down
6 changes: 3 additions & 3 deletions src/chart/rendering/drawlegend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ void DrawLegend::drawDimension(Info &info) const
for (const auto &item : info.dimension) {
if (item.weight <= 0) continue;

auto itemRect = getItemRect(info, item.range.getMin());
auto itemRect = getItemRect(info, item.value);

if (itemRect.y().getMin() > info.markerWindowRect.y().getMax()
|| itemRect.y().getMax()
Expand Down Expand Up @@ -335,8 +335,8 @@ Math::Range<double> DrawLegend::markersLegendRange(const Info &info)

if (info.dimensionEnabled)
for (const auto &item : info.dimension)
res.include({item.range.getMin() * info.itemHeight,
(item.range.getMax() + 1) * info.itemHeight});
res.include({item.value * info.itemHeight,
(item.value + 1) * info.itemHeight});

return res;
}
Expand Down
13 changes: 5 additions & 8 deletions src/chart/rendering/drawmarkerinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,14 @@ void DrawMarkerInfo::MarkerDC::interpolate(double weight1,
void DrawMarkerInfo::MarkerDC::loadMarker(Content &cnt)
{
const auto &marker =
*std::lower_bound(parent.plot->getMarkers().begin(),
parent.plot->getMarkers().end(),
// NOLINTNEXTLINE(misc-include-cleaner)
*std::ranges::lower_bound(parent.plot->getMarkers(),
cnt.markerId.value(),
[](const Gen::Marker &marker,
const Gen::Marker::MarkerIndex &id)
{
return marker.idx < id;
});
std::less{},
&Gen::Marker::idx);

auto blendedMarker =
Draw::AbstractMarker::createInterpolated(parent.ctx(),
AbstractMarker::createInterpolated(parent.ctx(),
marker,
::Anim::first);

Expand Down
10 changes: 4 additions & 6 deletions src/chart/speclayout/sizedependentlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,18 @@ template <class ChartType> struct SizeDependentLayout
std::vector<double> sizes;
for (const auto &level : hierarchy)
for (auto &sum = sizes.emplace_back();
const auto &item : level) {
if (auto &&size = item.first.sizeFactor;
const auto &item : level.base())
if (auto &&size = item->sizeFactor;
std::isfinite(size) && size > 0)
sum += size;
}

const ChartType chart(sizes);

for (auto it = chart.markers.data();
const auto &level : hierarchy) {
std::vector<double> ssizes(level.size());
for (std::size_t ix{}; const auto &item : level) {
ssizes[ix++] = item.first.sizeFactor;
}
for (std::size_t ix{}; const auto &item : level.base())
ssizes[ix++] = item->sizeFactor;

const ChartType subChart(ssizes, it++);

Expand Down
Loading