Skip to content

Commit 56bbe77

Browse files
authored
fix: Fixes cartesian chart series visibility (WIP) (#158)
* fix: Fixes series visibility in legend on 2nd render * more tests
1 parent 668220c commit 56bbe77

6 files changed

Lines changed: 75 additions & 7 deletions

File tree

pages/01-cartesian-chart/controlled-visibility.page.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import Checkbox from "@cloudscape-design/components/checkbox";
45
import Link from "@cloudscape-design/components/link";
56

67
import { CartesianChart, CartesianChartProps } from "../../lib/components";
@@ -9,6 +10,7 @@ import { PageSettings, PageSettingsForm, SeriesFilter, useChartSettings } from "
910
import { Page, PageSection } from "../common/templates";
1011

1112
interface ThisPageSettings extends PageSettings {
13+
removeHidden: boolean;
1214
visibleItems: string;
1315
}
1416

@@ -43,6 +45,7 @@ const defaultVisibleItems = "Costs,Costs last year,Peak cost";
4345

4446
export default function () {
4547
const { settings, setSettings } = useChartSettings<ThisPageSettings>();
48+
const removeHidden = settings.removeHidden ?? false;
4649
const visibleSeries = (settings.visibleItems ?? defaultVisibleItems).split(",");
4750
return (
4851
<Page
@@ -53,6 +56,16 @@ export default function () {
5356
<PageSettingsForm
5457
selectedSettings={[
5558
"showLegend",
59+
{
60+
content: (
61+
<Checkbox
62+
checked={removeHidden}
63+
onChange={({ detail }) => setSettings({ removeHidden: detail.checked })}
64+
>
65+
Remove hidden
66+
</Checkbox>
67+
),
68+
},
5669
{
5770
content: (
5871
<SeriesFilter
@@ -76,12 +89,15 @@ export default function () {
7689
function ExampleMixedChart() {
7790
const { settings, setSettings, chartProps } = useChartSettings<ThisPageSettings>();
7891
const visibleSeries = (settings.visibleItems ?? defaultVisibleItems).split(",");
92+
const filteredSeries = settings.removeHidden
93+
? mixedChartSeries.filter((s) => visibleSeries.includes(s.id!))
94+
: mixedChartSeries;
7995
return (
8096
<CartesianChart
8197
{...chartProps.cartesian}
8298
chartHeight={379}
8399
ariaLabel="Mixed bar chart"
84-
series={mixedChartSeries}
100+
series={filteredSeries}
85101
tooltip={{
86102
point: ({ item }) => {
87103
return {

pages/02-pie-chart/controlled-visibility.page.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33

44
import { sum } from "lodash";
55

6+
import Checkbox from "@cloudscape-design/components/checkbox";
7+
68
import { PieChart, PieChartProps } from "../../lib/components";
79
import { PageSettings, PageSettingsForm, SeriesFilter, useChartSettings } from "../common/page-settings";
810
import { Page, PageSection } from "../common/templates";
911

1012
interface ThisPageSettings extends PageSettings {
13+
removeHidden: boolean;
1114
visibleItems: string;
1215
}
1316

@@ -25,6 +28,7 @@ const defaultVisibleItems = "Costs,Costs last year,Peak cost";
2528

2629
export default function () {
2730
const { settings, setSettings } = useChartSettings<ThisPageSettings>();
31+
const removeHidden = settings.removeHidden ?? false;
2832
const visibleSeries = (settings.visibleItems ?? defaultVisibleItems).split(",");
2933
return (
3034
<Page
@@ -35,6 +39,16 @@ export default function () {
3539
<PageSettingsForm
3640
selectedSettings={[
3741
"showLegend",
42+
{
43+
content: (
44+
<Checkbox
45+
checked={removeHidden}
46+
onChange={({ detail }) => setSettings({ removeHidden: detail.checked })}
47+
>
48+
Remove hidden
49+
</Checkbox>
50+
),
51+
},
3852
{
3953
content: (
4054
<SeriesFilter
@@ -58,12 +72,15 @@ export default function () {
5872
function ExamplePieChart() {
5973
const { settings, setSettings, chartProps } = useChartSettings<ThisPageSettings>();
6074
const visibleSegments = (settings.visibleItems ?? defaultVisibleItems).split(",");
75+
const filteredSeries = settings.removeHidden
76+
? { ...pieChartSeries, data: pieChartSeries.data.filter((i) => visibleSegments.includes(i.id!)) }
77+
: pieChartSeries;
6178
return (
6279
<PieChart
6380
{...chartProps.pie}
6481
chartHeight={500}
6582
ariaLabel="Pie chart"
66-
series={pieChartSeries}
83+
series={filteredSeries}
6784
tooltip={{
6885
details({ segmentValue, totalValue }) {
6986
return [

src/cartesian-chart/__tests__/cartesian-chart-visibility.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,30 @@ describe("CartesianChart: visibility", () => {
103103
hiddenSeries: ["L1", "L2"],
104104
});
105105
});
106+
107+
test.each<CartesianChartProps.SeriesOptions>([
108+
{ type: "area", name: "new", data: [] },
109+
{ type: "line", name: "new", data: [] },
110+
{ type: "column", name: "new", data: [] },
111+
{ type: "scatter", name: "new", data: [] },
112+
{ type: "x-threshold", name: "new", value: 0 },
113+
{ type: "y-threshold", name: "new", value: 0 },
114+
])("new series become visible in the legend, type=$type", (newSeries) => {
115+
const { rerender } = renderCartesianChart({ ...defaultProps, series: [] });
116+
expect(getVisibilityState().allLegendItems).toEqual([]);
117+
118+
rerender({ highcharts, series: [newSeries] });
119+
expect(getVisibilityState().allLegendItems).toEqual(["new"]);
120+
121+
rerender({ highcharts, series: [newSeries, { ...newSeries, name: "new-2" }] });
122+
expect(getVisibilityState().allLegendItems).toEqual(["new", "new-2"]);
123+
});
124+
125+
test("errorbar series do not become visible in the legend", () => {
126+
const { rerender } = renderCartesianChart({ ...defaultProps, series: lineSeries });
127+
expect(getVisibilityState().allLegendItems).toEqual(["L1", "L2"]);
128+
129+
rerender({ highcharts, series: [...lineSeries, { type: "errorbar", name: "EB", linkedTo: "L2", data: [] }] });
130+
expect(getVisibilityState().allLegendItems).toEqual(["L1", "L2"]);
131+
});
106132
});

src/cartesian-chart/chart-series-cartesian.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export const transformCartesianSeries = (
3737
const thresholdX = getThresholdX(originalSeries, visibleSeries);
3838
const thresholdY = getThresholdY(originalSeries, visibleSeries);
3939
function transformSeriesToHighcharts(s: CartesianChartProps.SeriesOptions): Highcharts.SeriesOptionsType {
40+
// Highcharts does not properly update data on re-render. When the fake empty series is replaced with the real series,
41+
// the properties are merged together, so the showInLegend=false is not overridden.
42+
// That is why we explicitly override this property for all real series.
43+
const shared = { showInLegend: true };
4044
if (s.type === "x-threshold" || s.type === "y-threshold") {
4145
const data =
4246
s.type === "x-threshold"
@@ -49,7 +53,7 @@ export const transformCartesianSeries = (
4953
color: s.color ?? Styles.thresholdSeries.color,
5054
dashStyle: s.dashStyle ?? Styles.thresholdSeries.dashStyle,
5155
};
52-
return { type: "line", id: s.id, name: s.name, data, custom, enableMouseTracking, ...style };
56+
return { type: "line", id: s.id, name: s.name, data, custom, enableMouseTracking, ...style, ...shared };
5357
}
5458
if (s.type === "errorbar") {
5559
const color = s.color ?? colorChartsErrorBarMarker;
@@ -58,7 +62,7 @@ export const transformCartesianSeries = (
5862
const colors = { stemColor: color, whiskerColor: color };
5963
return { ...s, data: s.data as Writeable<RangeDataItemOptions[]>, ...colors };
6064
}
61-
return { ...s, data: s.data as Writeable<PointDataItemType[]> };
65+
return { ...s, data: s.data as Writeable<PointDataItemType[]>, ...shared };
6266
}
6367
const series = originalSeries.map(transformSeriesToHighcharts);
6468
// We inject a fake empty series so that the empty state still shows axes, if defined.

src/core/__tests__/chart-core-legend.test.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ const series: Highcharts.SeriesOptionsType[] = [
4949
{ name: "P2", y: 30 },
5050
{ id: "P3", name: "Pie 3", y: 60 },
5151
],
52-
showInLegend: true,
5352
},
5453
];
5554

@@ -116,6 +115,14 @@ describe("CoreChart: legend", () => {
116115
expect(getItems({ active: false }).map((w) => w.getElement().textContent)).toEqual(["L2", "Line 3", "P2", "Pie 3"]);
117116
});
118117

118+
test("does not render series with showInLegend=false", () => {
119+
const sVisible = series.filter((s) => s.name === "L1" || s.name === "L2");
120+
const sHidden = series.filter((s) => !sVisible.includes(s)).map((s) => ({ ...s, showInLegend: false }));
121+
122+
renderChart({ highcharts, options: { series: [...sVisible, ...sHidden] } });
123+
expect(getItems().map((w) => w.getElement().textContent)).toEqual(["L1", "L2", "P1", "P2", "Pie 3"]);
124+
});
125+
119126
test("does not render title by default", () => {
120127
renderChart({ highcharts, options: { series: series } });
121128

src/core/__tests__/chart-core-visibility.test.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const pieSeries: Highcharts.SeriesOptionsType[] = [
5252
{ name: "B", y: 20 },
5353
{ name: "C", y: 70 },
5454
],
55-
showInLegend: true,
5655
},
5756
];
5857

@@ -377,7 +376,6 @@ describe("CoreChart: visibility", () => {
377376
{ id: "1", name: "Segment", y: 20 },
378377
{ id: "2", name: "Segment", y: 80 },
379378
],
380-
showInLegend: true,
381379
},
382380
];
383381
const { rerender } = renderChart({

0 commit comments

Comments
 (0)