Skip to content

Commit 06a3900

Browse files
committed
[IMP] chart: highlight children of hovered item in sunburst
With this commit, the children of the hovered item in the sunburst chart are highlighted in addition to the hovered item, and the other elements are dimmed. This is done by adding a new plugin to the `ChartJS`. The plugin `sunburstHoverPlugin` make the children of the hovered item `active`, so they get the hovered offset and color. Task: 4575651
1 parent bef2290 commit 06a3900

File tree

5 files changed

+98
-1
lines changed

5 files changed

+98
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { ActiveDataPoint, ChartType, Plugin } from "chart.js";
2+
import { lightenColor } from "../../../../helpers";
3+
import { GHOST_SUNBURST_VALUE } from "../../../../helpers/figures/charts/runtime";
4+
import { SunburstChartRawData } from "../../../../types/chart";
5+
6+
export interface ChartSunburstHoverPluginOptions {
7+
enabled: boolean;
8+
}
9+
10+
declare module "chart.js" {
11+
interface PluginOptionsByType<TType extends ChartType> {
12+
sunburstHoverPlugin?: ChartSunburstHoverPluginOptions;
13+
}
14+
}
15+
16+
/**
17+
* When a chart element is hovered (active), this plugin also activates all of its child elements and
18+
* lightens the color of the other elements.
19+
*/
20+
export const sunburstHoverPlugin: Plugin = {
21+
id: "sunburstHoverPlugin",
22+
afterEvent(chart, args, options: ChartSunburstHoverPluginOptions) {
23+
if (!options.enabled) {
24+
return;
25+
}
26+
const chartActiveElements = chart.getActiveElements();
27+
let activeDataPoints: ActiveDataPoint[] = chartActiveElements.map((el) => ({
28+
datasetIndex: el.datasetIndex,
29+
index: el.index,
30+
}));
31+
32+
for (const activeEl of chartActiveElements) {
33+
const activeDataset = chart.data.datasets[activeEl.datasetIndex];
34+
const activeData = activeDataset.data[activeEl.index] as unknown as SunburstChartRawData;
35+
36+
for (let datasetIndex = 0; datasetIndex < chart.data.datasets.length; datasetIndex++) {
37+
const dataset = chart.data.datasets[datasetIndex];
38+
for (let index = 0; index < dataset.data.length; index++) {
39+
const data = dataset.data[index] as unknown as SunburstChartRawData;
40+
if (isChildGroup(activeData.groups, data.groups)) {
41+
activeDataPoints.push({ datasetIndex, index });
42+
}
43+
}
44+
}
45+
}
46+
47+
activeDataPoints = activeDataPoints.filter((point) => {
48+
const { datasetIndex, index } = point;
49+
const data = chart.data.datasets[datasetIndex].data[index] as unknown as SunburstChartRawData;
50+
return data.label !== GHOST_SUNBURST_VALUE;
51+
});
52+
53+
chart.setActiveElements(activeDataPoints);
54+
55+
for (const metaSet of chart.getSortedVisibleDatasetMetas()) {
56+
for (const arcElement of metaSet.data) {
57+
const context = arcElement["$context"];
58+
const { datasetIndex, index, dataset, raw } = context;
59+
if (raw.label === GHOST_SUNBURST_VALUE) {
60+
continue;
61+
}
62+
63+
const originalBackgroundColor =
64+
typeof dataset.backgroundColor === "function"
65+
? dataset.backgroundColor(context)
66+
: dataset.backgroundColor;
67+
if (
68+
activeDataPoints.length &&
69+
!activeDataPoints.some((el) => el.datasetIndex === datasetIndex && el.index === index)
70+
) {
71+
arcElement.options.backgroundColor = lightenColor(originalBackgroundColor, 0.5);
72+
} else {
73+
arcElement.options.backgroundColor = originalBackgroundColor;
74+
}
75+
}
76+
}
77+
},
78+
};
79+
80+
function isChildGroup(parentGroup: string[], childGroup: string[]) {
81+
return (
82+
childGroup.length > parentGroup.length &&
83+
parentGroup.every((group, i) => group === childGroup[i])
84+
);
85+
}

src/helpers/figures/charts/chart_ui_common.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getFunnelChartElement,
66
} from "../../../components/figures/chart/chartJs/chartjs_funnel_chart";
77
import { chartShowValuesPlugin } from "../../../components/figures/chart/chartJs/chartjs_show_values_plugin";
8+
import { sunburstHoverPlugin } from "../../../components/figures/chart/chartJs/chartjs_sunburst_hover_plugin";
89
import { sunburstLabelsPlugin } from "../../../components/figures/chart/chartJs/chartjs_sunburst_labels_plugin";
910
import { waterfallLinesPlugin } from "../../../components/figures/chart/chartJs/chartjs_waterfall_plugin";
1011
import { Figure } from "../../../types";
@@ -125,7 +126,8 @@ export function getChartJSConstructor() {
125126
waterfallLinesPlugin,
126127
getFunnelChartController(),
127128
getFunnelChartElement(),
128-
sunburstLabelsPlugin
129+
sunburstLabelsPlugin,
130+
sunburstHoverPlugin
129131
);
130132
window.Chart.Tooltip.positioners.funnelTooltipPositioner = funnelTooltipPositioner;
131133
}

src/helpers/figures/charts/sunburst_chart.ts

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ export function createSunburstChartRuntime(
203203
legend: getSunburstChartLegend(definition, chartData),
204204
tooltip: getSunburstChartTooltip(definition, chartData),
205205
sunburstLabelsPlugin: getSunburstShowValues(definition, chartData),
206+
sunburstHoverPlugin: { enabled: true },
206207
},
207208
},
208209
};

tests/figures/chart/charts_component.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,7 @@ test("ChartJS charts extensions are loaded when mounting a chart, and are only l
21582158
"funnel",
21592159
"funnel",
21602160
"sunburstLabelsPlugin",
2161+
"sunburstHoverPlugin",
21612162
]);
21622163

21632164
createChart(model, { type: "line" }, "chart2");

tests/figures/chart/sunburst/sunburst_chart_plugin.test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,12 @@ describe("Sunburst chart chart", () => {
390390
});
391391
expect(config.options?.plugins?.sunburstLabelsPlugin?.callback?.(10, "y")).toBe("10 ( •⩊• )");
392392
});
393+
394+
test("Sunburst hover plugin is enabled", () => {
395+
const chartId = createSunburstChart(model);
396+
const config = getSunburstRuntime(chartId).chartJsConfig;
397+
expect(config.options?.plugins?.sunburstHoverPlugin).toMatchObject({
398+
enabled: true,
399+
});
400+
});
393401
});

0 commit comments

Comments
 (0)