Skip to content

Commit 1afa2c4

Browse files
authored
fix(partition): allow custom sorting for the legend items (#1959)
The legendSort prop is now applied to partition charts allowing the user to custom sort (flattened) legend items.
1 parent 0a998a5 commit 1afa2c4

File tree

7 files changed

+53
-7
lines changed

7 files changed

+53
-7
lines changed
Loading

packages/charts/src/chart_types/partition_chart/layout/utils/legend.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { CategoryKey } from '../../../../common/category';
1010
import { map } from '../../../../common/iterables';
1111
import { LegendItem } from '../../../../common/legend';
12-
import { LegendPositionConfig } from '../../../../specs/settings';
12+
import { LegendPositionConfig, SettingsSpec } from '../../../../specs/settings';
1313
import { isHierarchicalLegend } from '../../../../utils/legend';
1414
import { Layer } from '../../specs';
1515
import { PartitionLayout } from '../types/config_types';
@@ -34,6 +34,10 @@ function compareTreePaths(
3434
return a.length - b.length; // if one path is fully contained in the other, then parent (shorter) goes first
3535
}
3636

37+
function createPartitionIdentifier(id: string, item: QuadViewModel) {
38+
return { specId: id, key: item.dataName, smAccessorValue: item.smAccessorValue };
39+
}
40+
3741
/** @internal */
3842
export function getLegendItems(
3943
id: string,
@@ -43,9 +47,16 @@ export function getLegendItems(
4347
legendPosition: LegendPositionConfig,
4448
quadViewModel: QuadViewModel[],
4549
partitionLayout: PartitionLayout | undefined,
50+
settingsSpec: SettingsSpec,
4651
): LegendItem[] {
4752
const uniqueNames = new Set(map(quadViewModel, ({ dataName, fillColor }) => makeKey(dataName, fillColor)));
4853
const useHierarchicalLegend = isHierarchicalLegend(flatLegend, legendPosition);
54+
const sortingFn = flatLegend && settingsSpec.legendSort;
55+
56+
const customSortingFn = sortingFn
57+
? (aItem: QuadViewModel, bItem: QuadViewModel) =>
58+
sortingFn(createPartitionIdentifier(id, aItem), createPartitionIdentifier(id, bItem))
59+
: null;
4960

5061
const formattedLabel = ({ dataName, depth }: QuadViewModel) => {
5162
const formatter = layers[depth - 1]?.nodeLabel;
@@ -77,11 +88,14 @@ export function getLegendItems(
7788
return true;
7889
});
7990

91+
const waffleSortingFn = customSortingFn ?? descendingValues;
92+
const flatLegendSortingFn = customSortingFn ?? compareNames;
93+
8094
items.sort(
8195
partitionLayout === PartitionLayout.waffle // waffle has inherent top to bottom descending order
82-
? descendingValues
96+
? waffleSortingFn
8397
: flatLegend
84-
? compareNames
98+
? flatLegendSortingFn
8599
: compareTreePaths,
86100
);
87101

packages/charts/src/chart_types/partition_chart/state/selectors/compute_legend.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import { getPartitionSpecs } from './get_partition_specs';
1111
import { LegendItem } from '../../../../common/legend';
1212
import { createCustomCachedSelector } from '../../../../state/create_selector';
1313
import { getLegendConfigSelector } from '../../../../state/selectors/get_legend_config_selector';
14+
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec';
1415
import { getLegendItems } from '../../layout/utils/legend';
1516

1617
/** @internal */
1718
export const computeLegendSelector = createCustomCachedSelector(
18-
[getPartitionSpecs, getLegendConfigSelector, partitionMultiGeometries],
19-
(specs, { flatLegend, legendMaxDepth, legendPosition }, geometries): LegendItem[] =>
19+
[getPartitionSpecs, getLegendConfigSelector, partitionMultiGeometries, getSettingsSpecSelector],
20+
(specs, { flatLegend, legendMaxDepth, legendPosition }, geometries, settings): LegendItem[] =>
2021
specs.flatMap((partitionSpec, i) => {
2122
const quadViewModel = geometries.filter((g) => g.index === i).flatMap((g) => g.quadViewModel);
2223
return getLegendItems(
@@ -27,6 +28,7 @@ export const computeLegendSelector = createCustomCachedSelector(
2728
legendPosition,
2829
quadViewModel,
2930
partitionSpec.layout,
31+
settings,
3032
);
3133
}),
3234
);

storybook/stories/legend/10_sunburst.story.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
PartitionLayout,
2020
Settings,
2121
defaultPartitionValueFormatter,
22+
SeriesIdentifier,
2223
} from '@elastic/charts';
2324
import { ShapeTreeNode } from '@elastic/charts/src/chart_types/partition_chart/layout/types/viewmodel_types';
2425
import { mocks } from '@elastic/charts/src/mocks/hierarchical';
@@ -38,6 +39,8 @@ export const Example = () => {
3839
{
3940
treemap: PartitionLayout.treemap,
4041
sunburst: PartitionLayout.sunburst,
42+
mosaic: PartitionLayout.mosaic,
43+
waffle: PartitionLayout.waffle,
4144
},
4245
PartitionLayout.sunburst,
4346
);
@@ -50,6 +53,26 @@ export const Example = () => {
5053
});
5154
const legendStrategy = select('legendStrategy', LegendStrategy, LegendStrategy.Key as LegendStrategy);
5255
const maxLines = number('max legend label lines', 1, { min: 0, step: 1 });
56+
57+
const legendSortStrategy = select(
58+
'Custom legend sorting',
59+
{ RegionsFirst: 'regionsFirst', ProductsFirst: 'productsFirst', DefaultSort: 'default' },
60+
'regionsFirst',
61+
);
62+
63+
const customLegendSort = (a: SeriesIdentifier, b: SeriesIdentifier) => {
64+
if (legendSortStrategy === 'regionsFirst') {
65+
if (a.key in regionLookup && b.key in regionLookup) {
66+
return a.key.localeCompare(b.key);
67+
}
68+
return a.key in regionLookup ? -1 : b.key in regionLookup ? 1 : a.key.localeCompare(b.key);
69+
}
70+
if (a.key in productLookup && b.key in productLookup) {
71+
return a.key.localeCompare(b.key);
72+
}
73+
return a.key in productLookup ? -1 : b.key in productLookup ? 1 : a.key.localeCompare(b.key);
74+
};
75+
5376
const partitionTheme: PartialTheme['partition'] = {
5477
linkLabel: {
5578
maxCount: 0,
@@ -72,14 +95,18 @@ export const Example = () => {
7295
circlePadding: 4,
7396
};
7497

98+
const isFlatLegendSupported =
99+
partitionLayout === PartitionLayout.treemap || partitionLayout === PartitionLayout.sunburst;
100+
75101
return (
76102
<Chart>
77103
<Settings
78104
showLegend
79105
showLegendExtra={showLegendExtra}
80-
flatLegend={flatLegend}
106+
flatLegend={isFlatLegendSupported ? flatLegend : true}
81107
legendStrategy={legendStrategy}
82108
legendMaxDepth={legendMaxDepth}
109+
legendSort={legendSortStrategy !== 'default' ? customLegendSort : undefined}
83110
baseTheme={useBaseTheme()}
84111
theme={{
85112
partition: partitionTheme,
@@ -127,5 +154,8 @@ Example.parameters = {
127154
markdown: `To flatten a hierarchical legend (like the rendered in a pie chart or a treemap when using a multi-layer configuration) you can
128155
add the \`flatLegend\` prop into the \`<Settings baseTheme={useBaseTheme()} />\` component.
129156
130-
To limit displayed hierarchy to a specific depth, you can use the \`legendMaxDepth\` prop. The first layer will have a depth of \`1\`.`,
157+
To limit displayed hierarchy to a specific depth, you can use the \`legendMaxDepth\` prop. The first layer will have a depth of \`1\`.
158+
159+
It is possible to provide a custom sorting logic for a legend when flattened, when not flattened the \`legendSort\` function is ignored.
160+
`,
131161
};

0 commit comments

Comments
 (0)