Skip to content

Commit 0f78e3b

Browse files
authored
Merge pull request #625 from vizzuhq/axis_refactor_v12d
Axis refactor v12d - Enable split and align on mainAxis
2 parents 3b4309c + 0ca1c12 commit 0f78e3b

File tree

8 files changed

+115
-67
lines changed

8 files changed

+115
-67
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010

1111
### Changed
1212

13-
- Separate Channel properties to AxisChannel properties at config.
1413
- Channels 'set' rewrite doesn't clear AxisChannel properties.
1514

1615
### Added
1716

17+
- Separate Channel properties to AxisChannel properties at config.
18+
- Move split align sort and reverse to AxisChannel
1819
- Add new sorting strategy: 'byLabel'.
20+
- Enable split and align on mainAxis.
1921

2022
## [0.16.0] - 2024-11-28
2123

src/apps/weblib/typeschema-api/config.yaml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,12 @@ definitions:
124124
type: boolean
125125
align:
126126
description: |
127-
Sets the alignment of the markers with relation to the main-axis only depending
128-
on where the measure is. In case both axes have measures on them, this is determined
129-
by the `orientation` of the chart. On sub-axis this settings has no effect.
127+
Sets the alignment of the markers along the axis.
130128
type: string
131129
enum: [none, center, stretch]
132130
split:
133131
description: |
134-
If set to true, markers will be split by the dimension(s) along the main-axis.
135-
On sub-axis this settings has no effect.
132+
If set to true, markers will be split by the dimension(s) along the axis.
136133
type: boolean
137134

138135
Channels:

src/chart/generator/plotbuilder.cpp

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,29 +48,35 @@ PlotBuilder::PlotBuilder(const Data::DataTable &dataTable,
4848
initDimensionTrackers();
4949

5050
std::size_t mainBucketSize{};
51-
auto &&subBuckets = generateMarkers(mainBucketSize);
51+
std::size_t subBucketSize{};
52+
auto &&buckets = generateMarkers(mainBucketSize, subBucketSize);
5253

5354
if (!plot->getOptions()->getChannels().anyAxisSet()) {
54-
addSpecLayout(subBuckets);
55+
addSpecLayout(buckets);
5556
normalizeSizes();
5657
}
5758
else {
5859
normalizeSizes();
59-
addAxisLayout(subBuckets, mainBucketSize, dataTable);
60+
addAxisLayout(buckets,
61+
mainBucketSize,
62+
subBucketSize,
63+
dataTable);
6064
}
6165

6266
normalizeColors();
6367
calcLegendAndLabel(dataTable);
6468
}
6569

66-
void PlotBuilder::addAxisLayout(Buckets &subBuckets,
70+
void PlotBuilder::addAxisLayout(Buckets &buckets,
6771
const std::size_t &mainBucketSize,
72+
const std::size_t &subBucketSize,
6873
const Data::DataTable &dataTable)
6974
{
70-
linkMarkers(subBuckets);
71-
addSeparation(subBuckets, mainBucketSize);
75+
linkMarkers(buckets, mainBucketSize, subBucketSize);
7276
calcAxises(dataTable);
73-
addAlignment(subBuckets);
77+
addAlignment(buckets, plot->getOptions()->subAxisType());
78+
addAlignment(buckets.sort(&Marker::mainId),
79+
plot->getOptions()->mainAxisType());
7480
}
7581

7682
void PlotBuilder::initDimensionTrackers()
@@ -82,7 +88,8 @@ void PlotBuilder::initDimensionTrackers()
8288
dataCube.combinedSizeOf(ch.dimensions()).second);
8389
}
8490

85-
Buckets PlotBuilder::generateMarkers(std::size_t &mainBucketSize)
91+
Buckets PlotBuilder::generateMarkers(std::size_t &mainBucketSize,
92+
std::size_t &subBucketSize)
8693
{
8794
const auto &mainIds(plot->getOptions()->mainAxis().dimensions());
8895
auto subIds(plot->getOptions()->subAxis().dimensions());
@@ -91,6 +98,7 @@ Buckets PlotBuilder::generateMarkers(std::size_t &mainBucketSize)
9198
subIds.split_by(mainIds);
9299

93100
mainBucketSize = dataCube.combinedSizeOf(mainIds).first;
101+
subBucketSize = dataCube.combinedSizeOf(subIds).first;
94102
plot->markers.reserve(dataCube.df->get_record_count());
95103
}
96104

@@ -201,13 +209,21 @@ void PlotBuilder::addSpecLayout(Buckets &buckets)
201209
}
202210
}
203211

204-
void PlotBuilder::linkMarkers(Buckets &buckets)
212+
void PlotBuilder::linkMarkers(Buckets &buckets,
213+
const std::size_t &mainBucketSize,
214+
const std::size_t &subBucketSize)
205215
{
206216
auto &&hasMarkerConnection =
207217
linkMarkers(buckets.sort(&Marker::mainId),
208218
plot->getOptions()->mainAxisType());
219+
addSeparation(buckets,
220+
plot->getOptions()->mainAxisType(),
221+
subBucketSize);
209222
std::ignore = linkMarkers(buckets.sort(&Marker::subId),
210223
plot->getOptions()->subAxisType());
224+
addSeparation(buckets,
225+
plot->getOptions()->subAxisType(),
226+
mainBucketSize);
211227

212228
if (hasMarkerConnection
213229
&& plot->getOptions()->geometry.get() == ShapeType::line
@@ -493,67 +509,62 @@ void PlotBuilder::calcAxis(const Data::DataTable &dataTable,
493509
}
494510
}
495511

496-
void PlotBuilder::addAlignment(const Buckets &subBuckets) const
512+
void PlotBuilder::addAlignment(const Buckets &buckets,
513+
AxisId axisIndex) const
497514
{
498-
if (plot->getOptions()->isSplit()) return;
515+
if (plot->getOptions()->isSplit(axisIndex)) return;
499516

500-
auto &subAxisRange =
501-
plot->axises.at(plot->getOptions()->subAxisType())
502-
.measure.range;
503-
if (std::signbit(subAxisRange.min)
504-
|| std::signbit(subAxisRange.max))
517+
auto &axisRange = plot->axises.at(axisIndex).measure.range;
518+
if (std::signbit(axisRange.min) || std::signbit(axisRange.max))
505519
return;
506520

507521
const auto &axisProps =
508-
plot->getOptions()->getChannels().axisPropsAt(
509-
plot->getOptions()->subAxisType());
522+
plot->getOptions()->getChannels().axisPropsAt(axisIndex);
510523

511524
if (axisProps.align == Base::Align::Type::none) return;
512525

513526
if (axisProps.align == Base::Align::Type::center) {
514-
auto &&halfSize = subAxisRange.size() / 2.0;
527+
auto &&halfSize = axisRange.size() / 2.0;
515528
if (!Math::Floating::is_zero(halfSize))
516-
subAxisRange = {subAxisRange.min - halfSize,
517-
subAxisRange.max - halfSize};
529+
axisRange = {axisRange.min - halfSize,
530+
axisRange.max - halfSize};
518531
}
519532

520-
auto &&subAxis = plot->getOptions()->subAxisType();
521533
const Base::Align align{axisProps.align, {0.0, 1.0}};
522-
for (auto &&bucket : subBuckets) {
534+
for (auto &&bucket : buckets) {
523535
Math::Range<> range;
524536

525537
for (auto &&[marker, idx] : bucket)
526538
if (marker.enabled)
527-
range.include(marker.getSizeBy(subAxis));
539+
range.include(marker.getSizeBy(axisIndex));
528540

529541
auto &&transform = align.getAligned(range) / range;
530542

531543
for (auto &&[marker, idx] : bucket)
532-
marker.setSizeBy(subAxis,
533-
marker.getSizeBy(subAxis) * transform);
544+
marker.setSizeBy(axisIndex,
545+
marker.getSizeBy(axisIndex) * transform);
534546
}
535547
}
536548

537-
void PlotBuilder::addSeparation(const Buckets &subBuckets,
538-
const std::size_t &mainBucketSize) const
549+
void PlotBuilder::addSeparation(const Buckets &buckets,
550+
AxisId axisIndex,
551+
const std::size_t &otherBucketSize) const
539552
{
540-
if (!plot->getOptions()->isSplit()) return;
553+
if (!plot->getOptions()->isSplit(axisIndex)) return;
541554

542555
const auto &axisProps =
543-
plot->getOptions()->getChannels().axisPropsAt(
544-
plot->getOptions()->subAxisType());
556+
plot->getOptions()->getChannels().axisPropsAt(axisIndex);
545557
auto align = axisProps.align;
546558

547-
std::vector ranges{mainBucketSize, Math::Range<>{{}, {}}};
548-
std::vector<bool> anyEnabled(mainBucketSize);
559+
std::vector ranges{otherBucketSize, Math::Range<>{{}, {}}};
560+
std::vector<bool> anyEnabled(otherBucketSize);
549561

550-
auto &&subAxis = plot->getOptions()->subAxisType();
551-
for (auto &&bucket : subBuckets)
562+
for (auto &&bucket : buckets)
552563
for (std::size_t i{}, prIx{}; auto &&[marker, idx] : bucket) {
553564
if (!marker.enabled) continue;
554565
(i += idx.itemId - std::exchange(prIx, idx.itemId)) %=
555566
ranges.size();
556-
ranges[i].include(marker.getSizeBy(subAxis).size());
567+
ranges[i].include(marker.getSizeBy(axisIndex).size());
557568
anyEnabled[i] = true;
558569
}
559570

@@ -562,21 +573,20 @@ void PlotBuilder::addSeparation(const Buckets &subBuckets,
562573
if (anyEnabled[i]) max = max + ranges[i];
563574

564575
auto splitSpace =
565-
plot->getStyle()
566-
.plot.getAxis(plot->getOptions()->subAxisType())
567-
.spacing->get(max.max, plot->getStyle().calculatedSize());
576+
plot->getStyle().plot.getAxis(axisIndex).spacing->get(max.max,
577+
plot->getStyle().calculatedSize());
568578

569579
for (auto i = 1U; i < ranges.size(); ++i)
570580
ranges[i] = ranges[i] + ranges[i - 1].max
571581
+ (anyEnabled[i - 1] ? splitSpace : 0);
572582

573-
for (auto &&bucket : subBuckets)
583+
for (auto &&bucket : buckets)
574584
for (std::size_t i{}, prIx{}; auto &&[marker, idx] : bucket) {
575585
(i += idx.itemId - std::exchange(prIx, idx.itemId)) %=
576586
ranges.size();
577-
marker.setSizeBy(subAxis,
587+
marker.setSizeBy(axisIndex,
578588
Base::Align{align, ranges[i]}.getAligned(
579-
marker.getSizeBy(subAxis)));
589+
marker.getSizeBy(axisIndex)));
580590
}
581591
}
582592

src/chart/generator/plotbuilder.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,28 @@ class PlotBuilder
3535
};
3636

3737
void initDimensionTrackers();
38-
Buckets generateMarkers(std::size_t &mainBucketSize);
39-
void linkMarkers(Buckets &subBuckets);
38+
Buckets generateMarkers(std::size_t &mainBucketSize,
39+
std::size_t &subBucketSize);
40+
void linkMarkers(Buckets &buckets,
41+
const std::size_t &mainBucketSize,
42+
const std::size_t &subBucketSize);
4043
[[nodiscard]] bool linkMarkers(const Buckets &buckets,
4144
AxisId axisIndex) const;
4245
void calcAxises(const Data::DataTable &dataTable);
4346
void calcLegendAndLabel(const Data::DataTable &dataTable);
4447
void calcAxis(const Data::DataTable &dataTable, AxisId type);
45-
void addAlignment(const Buckets &subBuckets) const;
46-
void addSeparation(const Buckets &subBuckets,
47-
const std::size_t &mainBucketSize) const;
48+
void addAlignment(const Buckets &buckets, AxisId axisIndex) const;
49+
void addSeparation(const Buckets &buckets,
50+
AxisId axisIndex,
51+
const std::size_t &otherBucketSize) const;
4852
void normalizeSizes();
4953
void normalizeColors();
5054
[[nodiscard]] std::vector<BucketInfo>
5155
sortedBuckets(const Buckets &buckets, AxisId axisIndex) const;
5256
void addSpecLayout(Buckets &buckets);
53-
void addAxisLayout(Buckets &subBuckets,
57+
void addAxisLayout(Buckets &buckets,
5458
const std::size_t &mainBucketSize,
59+
const std::size_t &subBucketSize,
5560
const Data::DataTable &dataTable);
5661
};
5762
}

src/chart/options/options.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ std::optional<ChannelId> Options::secondaryStackType() const
9191
return std::nullopt;
9292
}
9393

94-
bool Options::hasDimensionToSplit() const
94+
bool Options::hasDimensionToSplit(AxisId at) const
9595
{
96-
auto dims = subAxis().dimensions();
97-
dims.split_by(mainAxis().dimensions());
96+
auto dims = getChannels().at(at).dimensions();
97+
dims.split_by(getChannels().at(!at).dimensions());
9898
return !dims.empty();
9999
}
100100

@@ -111,7 +111,8 @@ Channels Options::shadowChannels() const
111111
&ch2 = shadow.at(ChannelId::noop);
112112
auto &&stacker : shadow.getDimensions({data(stackChannels),
113113
std::size_t{1} + secondary.has_value()})) {
114-
if (stackChannelType() != subAxisType() || !isSplit())
114+
if (stackChannelType() != subAxisType()
115+
|| !isSplit(subAxisType()))
115116
ch1.removeSeries(stacker);
116117
ch2.removeSeries(stacker);
117118
}
@@ -123,8 +124,9 @@ void Options::drilldownTo(const Options &other)
123124
{
124125
auto &stackChannel = this->stackChannel();
125126

126-
if (!isSplit() || !other.isSplit())
127-
getChannels().axisPropsAt(subAxisType()).split = {};
127+
for (auto &&axis : Refl::enum_values<AxisId>())
128+
if (!isSplit(axis) || !other.isSplit(axis))
129+
getChannels().axisPropsAt(axis).split = {};
128130

129131
for (auto &&dim : other.getChannels().getDimensions())
130132
if (!getChannels().isSeriesUsed(dim))
@@ -138,6 +140,7 @@ void Options::intersection(const Options &other)
138140
getChannels().removeSeries(dim);
139141

140142
getChannels().axisPropsAt(subAxisType()).split = {};
143+
getChannels().axisPropsAt(mainAxisType()).split = {};
141144
}
142145

143146
bool Options::looksTheSame(const Options &other) const
@@ -158,7 +161,7 @@ bool Options::looksTheSame(const Options &other) const
158161

159162
void Options::simplify()
160163
{
161-
if (isSplit()) return;
164+
if (isSplit(subAxisType())) return;
162165

163166
// remove all dimensions, only used at the end of stack
164167
auto &stackChannel = this->stackChannel();
@@ -197,7 +200,8 @@ bool Options::sameShadowAttribs(const Options &other) const
197200

198201
return shape == shapeOther && coordSystem == other.coordSystem
199202
&& angle == other.angle && orientation == other.orientation
200-
&& isSplit() == other.isSplit()
203+
&& isSplit(mainAxisType()) == other.isSplit(mainAxisType())
204+
&& isSplit(subAxisType()) == other.isSplit(subAxisType())
201205
&& dataFilter == other.dataFilter;
202206
}
203207

src/chart/options/options.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ class Options : public OptionProperties
140140
return channels.at(stackChannelType());
141141
}
142142

143-
[[nodiscard]] bool hasDimensionToSplit() const;
144-
[[nodiscard]] bool isSplit() const
143+
[[nodiscard]] bool hasDimensionToSplit(AxisId at) const;
144+
[[nodiscard]] bool isSplit(AxisId byAxis) const
145145
{
146-
return getChannels().axisPropsAt(subAxisType()).split
147-
&& hasDimensionToSplit();
146+
return getChannels().axisPropsAt(byAxis).split
147+
&& hasDimensionToSplit(byAxis);
148148
}
149149
Data::Filter dataFilter;
150150
std::optional<MarkerIndex> tooltip;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const testSteps = [
2+
(chart) => {
3+
const data = {
4+
series: [
5+
{ name: 'Foo', values: ['Alice', 'Bob', 'Ted'] },
6+
{ name: 'Bar', values: ['Happy', 'Happy', 'Sad'] },
7+
{ name: 'Baz', values: [1, 2, 3] },
8+
{ name: 'Bau', values: [4, 3, 2] }
9+
]
10+
}
11+
12+
return chart.animate({ data })
13+
},
14+
(chart) =>
15+
chart.animate({
16+
config: {
17+
x: { set: 'Foo', split: true },
18+
y: { set: 'Bar' }
19+
}
20+
}),
21+
(chart) =>
22+
chart.animate({
23+
config: {
24+
x: { set: 'Foo' },
25+
y: { set: ['Bar', 'Bau'] }
26+
}
27+
})
28+
]
29+
30+
export default testSteps

test/e2e/tests/fixes.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"refs": ["1732a49"]
99
},
1010
"143": {
11-
"refs": ["95b9c83"]
11+
"refs": ["0775a8d"]
1212
},
1313
"144": {
1414
"refs": ["fde02e4"]

0 commit comments

Comments
 (0)