Skip to content

Commit 61377c6

Browse files
committed
Merge remote-tracking branch 'origin/main' into warning-icon-to-markers
# Conflicts: # src/core/chart-api/chart-extra-legend.tsx # src/core/components/core-tooltip.tsx
2 parents 58ab771 + 2e81e97 commit 61377c6

File tree

20 files changed

+669
-319
lines changed

20 files changed

+669
-319
lines changed

package-lock.json

Lines changed: 123 additions & 281 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@
119119
},
120120
"//": "ensure that typedoc uses latest typescript. It prints a warning, but works",
121121
"overrides": {
122-
"typescript": "^4.9.4"
122+
"typescript": "^4.9.4",
123+
"glob": "^10.5.0"
123124
},
124125
"lint-staged": {
125126
"*.{js,jsx,ts,tsx}": [

pages/03-core/core-line-chart.page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import Highcharts from "highcharts";
5-
import { omit } from "lodash";
65

76
import Button from "@cloudscape-design/components/button";
87
import Link from "@cloudscape-design/components/link";
@@ -99,12 +98,13 @@ export default function () {
9998
"showLegendTitle",
10099
"showLegendActions",
101100
"useFallback",
101+
"tooltipSeriesSorting",
102102
]}
103103
/>
104104
}
105105
>
106106
<CoreChart
107-
{...omit(chartProps.cartesian, "ref")}
107+
{...chartProps.core}
108108
highcharts={Highcharts}
109109
options={{
110110
lang: {
@@ -131,7 +131,6 @@ export default function () {
131131
status: id === "A" ? "warning" : "default",
132132
})}
133133
chartHeight={400}
134-
tooltip={{ placement: "outside" }}
135134
getTooltipContent={() => ({
136135
point({ item, hideTooltip }) {
137136
const value = item ? (item.point.y ?? null) : null;
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { useRef, useState } from "react";
5+
import Highcharts from "highcharts";
6+
import { omit } from "lodash";
7+
8+
import ColumnLayout from "@cloudscape-design/components/column-layout";
9+
import {
10+
colorChartsPaletteCategorical1,
11+
colorChartsPaletteCategorical2,
12+
colorChartsPaletteCategorical3,
13+
} from "@cloudscape-design/design-tokens";
14+
15+
import { ChartSeriesMarker } from "../../lib/components/internal/components/series-marker";
16+
import CoreChart from "../../lib/components/internal-do-not-use/core-chart";
17+
import { CoreLegend } from "../../lib/components/internal-do-not-use/core-legend";
18+
import { CoreChartProps } from "../../src/core/interfaces";
19+
import { LegendItem } from "../../src/internal/components/interfaces";
20+
import { dateFormatter } from "../common/formatters";
21+
import { useChartSettings } from "../common/page-settings";
22+
import { Page } from "../common/templates";
23+
import pseudoRandom from "../utils/pseudo-random";
24+
25+
function randomInt(min: number, max: number) {
26+
return min + Math.floor(pseudoRandom() * (max - min));
27+
}
28+
29+
const baseline = [
30+
{ x: 1600984800000, y: 58020 },
31+
{ x: 1600985700000, y: 102402 },
32+
{ x: 1600986600000, y: 104920 },
33+
{ x: 1600987500000, y: 94031 },
34+
{ x: 1600988400000, y: 125021 },
35+
{ x: 1600989300000, y: 159219 },
36+
{ x: 1600990200000, y: 193082 },
37+
{ x: 1600991100000, y: 162592 },
38+
{ x: 1600992000000, y: 274021 },
39+
{ x: 1600992900000, y: 264286 },
40+
];
41+
42+
const dataA = baseline.map(({ x, y }) => ({ x, y }));
43+
const dataB = baseline.map(({ x, y }) => ({ x, y: y === null ? null : y + randomInt(-100000, 100000) }));
44+
const dataC = baseline.map(({ x, y }) => ({ x, y: y === null ? null : y + randomInt(-150000, 50000) }));
45+
46+
const series = [
47+
{
48+
name: "Series A",
49+
type: "line" as const,
50+
data: dataA,
51+
color: colorChartsPaletteCategorical1,
52+
},
53+
{
54+
name: "Series B",
55+
type: "line" as const,
56+
data: dataB,
57+
color: colorChartsPaletteCategorical2,
58+
},
59+
{
60+
name: "Series C",
61+
type: "line" as const,
62+
data: dataC,
63+
color: colorChartsPaletteCategorical3,
64+
},
65+
];
66+
67+
export default function LegendEventsDemo() {
68+
const [visibleItems, setVisibleItems] = useState(new Set<string>(["Series A", "Series B", "Series C"]));
69+
const [highlightedItem, setHighlightedItem] = useState<null | string>(null);
70+
71+
const { chartProps } = useChartSettings();
72+
const chart1API = useRef<CoreChartProps.ChartAPI>(null) as React.MutableRefObject<CoreChartProps.ChartAPI>;
73+
const chart2API = useRef<CoreChartProps.ChartAPI>(null) as React.MutableRefObject<CoreChartProps.ChartAPI>;
74+
75+
const createOnItemHighlight =
76+
(referrer: "legend" | "chart1" | "chart2") =>
77+
({ detail }: { detail: { item: LegendItem } }) => {
78+
setHighlightedItem(detail.item.name);
79+
if (referrer !== "chart1") {
80+
chart1API.current.highlightItems([detail.item.name]);
81+
}
82+
if (referrer !== "chart2") {
83+
chart2API.current.highlightItems([detail.item.name]);
84+
}
85+
};
86+
const createOnClearHighlight =
87+
(referrer: "legend" | "chart1" | "chart2") =>
88+
({ detail }: { detail: { isApiCall: boolean } }) => {
89+
if (!detail.isApiCall) {
90+
setHighlightedItem(null);
91+
if (referrer !== "chart1") {
92+
chart1API.current.clearChartHighlight();
93+
}
94+
if (referrer !== "chart2") {
95+
chart2API.current.clearChartHighlight();
96+
}
97+
}
98+
};
99+
const createOnHighlight =
100+
(referrer: "chart1" | "chart2") =>
101+
({ detail }: { detail: { point: null | Highcharts.Point; isApiCall: boolean } }) => {
102+
if (detail.point && !detail.isApiCall) {
103+
setHighlightedItem(detail.point.name);
104+
if (referrer !== "chart1") {
105+
chart1API.current.highlightChartPoint(detail.point);
106+
}
107+
if (referrer !== "chart2") {
108+
chart2API.current.highlightChartPoint(detail.point);
109+
}
110+
}
111+
};
112+
113+
const legendItems: LegendItem[] = series.map((s) => ({
114+
id: s.name,
115+
name: s.name,
116+
marker: <ChartSeriesMarker color={s.color} type="line" />,
117+
visible: visibleItems.has(s.name),
118+
highlighted: highlightedItem === s.name,
119+
}));
120+
121+
return (
122+
<Page
123+
title="Events sync"
124+
subtitle="Demonstrates the highlight synchronization between standalone legends and charts"
125+
>
126+
<ColumnLayout columns={2}>
127+
<CoreLegend
128+
items={legendItems}
129+
title="Standalone legend 1"
130+
onItemHighlight={createOnItemHighlight("legend")}
131+
onClearHighlight={createOnClearHighlight("legend")?.bind(null, { detail: { isApiCall: false } })}
132+
onVisibleItemsChange={({ detail }) => setVisibleItems(new Set(detail.items))}
133+
/>
134+
135+
<CoreLegend
136+
items={legendItems}
137+
title="Standalone legend 2"
138+
onItemHighlight={createOnItemHighlight("legend")}
139+
onClearHighlight={createOnClearHighlight("legend")?.bind(null, { detail: { isApiCall: false } })}
140+
onVisibleItemsChange={({ detail }) => setVisibleItems(new Set(detail.items))}
141+
/>
142+
143+
<CoreChart
144+
{...omit(chartProps.cartesian, "ref")}
145+
highcharts={Highcharts}
146+
callback={(chartApi) => (chart1API.current = chartApi)}
147+
ariaLabel="Chart 1"
148+
options={{
149+
series: series,
150+
xAxis: [{ type: "datetime", title: { text: "Time (UTC)" }, valueFormatter: dateFormatter }],
151+
yAxis: [{ title: { text: "Events" } }],
152+
}}
153+
legend={{ title: "Chart legend 1" }}
154+
onLegendItemHighlight={createOnItemHighlight("chart1")}
155+
onClearHighlight={createOnClearHighlight("chart1")}
156+
onHighlight={createOnHighlight("chart1")}
157+
visibleItems={[...visibleItems]}
158+
onVisibleItemsChange={({ detail }) =>
159+
setVisibleItems(new Set(detail.items.filter((i) => i.visible).map((i) => i.name)))
160+
}
161+
/>
162+
163+
<CoreChart
164+
{...omit(chartProps.cartesian, "ref")}
165+
highcharts={Highcharts}
166+
callback={(chartApi) => (chart2API.current = chartApi)}
167+
ariaLabel="Chart 2"
168+
options={{
169+
series: series,
170+
xAxis: [{ type: "datetime", title: { text: "Time (UTC)" }, valueFormatter: dateFormatter }],
171+
yAxis: [{ title: { text: "Events" } }],
172+
}}
173+
legend={{ title: "Chart legend 2" }}
174+
onLegendItemHighlight={createOnItemHighlight("chart2")}
175+
onClearHighlight={createOnClearHighlight("chart2")}
176+
onHighlight={createOnHighlight("chart2")}
177+
visibleItems={[...visibleItems]}
178+
onVisibleItemsChange={({ detail }) =>
179+
setVisibleItems(new Set(detail.items.filter((i) => i.visible).map((i) => i.name)))
180+
}
181+
/>
182+
</ColumnLayout>
183+
</Page>
184+
);
185+
}

pages/common/page-settings.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface PageSettings {
3232
emphasizeBaseline: boolean;
3333
tooltipPlacement: "default" | "middle" | "outside";
3434
tooltipSize: "small" | "medium" | "large";
35+
tooltipSeriesSorting: CoreChartProps.TooltipOptions["seriesSorting"];
3536
showLegend: boolean;
3637
showLegendTitle: boolean;
3738
showLegendActions: boolean;
@@ -68,6 +69,7 @@ const DEFAULT_SETTINGS: PageSettings = {
6869
showHeaderFilter: false,
6970
showCustomFooter: false,
7071
useFallback: false,
72+
tooltipSeriesSorting: "as-added",
7173
};
7274

7375
export const PageSettingsContext = createContext<PageSettings>(DEFAULT_SETTINGS);
@@ -85,6 +87,7 @@ export function useChartSettings<SettingsType extends PageSettings = PageSetting
8587
chartProps: {
8688
cartesian: Omit<CartesianChartProps, "series"> & { ref: React.Ref<CartesianChartProps.Ref> };
8789
pie: Omit<PieChartProps, "series"> & { ref: React.Ref<PieChartProps.Ref> };
90+
core: Omit<CoreChartProps, "series" | "options">;
8891
};
8992
isEmpty: boolean;
9093
} {
@@ -180,6 +183,18 @@ export function useChartSettings<SettingsType extends PageSettings = PageSetting
180183
tooltip: { size: settings.tooltipSize },
181184
legend,
182185
},
186+
core: {
187+
highcharts,
188+
noData,
189+
tooltip: {
190+
placement: settings.tooltipPlacement === "default" ? undefined : settings.tooltipPlacement,
191+
size: settings.tooltipSize,
192+
seriesSorting: settings.tooltipSeriesSorting,
193+
},
194+
legend,
195+
emphasizeBaseline: settings.emphasizeBaseline,
196+
verticalAxisTitlePlacement: settings.verticalAxisTitlePlacement,
197+
},
183198
},
184199
isEmpty: settings.emptySeries || settings.seriesLoading || settings.seriesError,
185200
};
@@ -191,6 +206,11 @@ const tooltipSizeOptions = [{ value: "small" }, { value: "medium" }, { value: "l
191206

192207
const verticalAxisTitlePlacementOptions = [{ value: "top" }, { value: "side" }];
193208

209+
const tooltipSeriesSortingOptions = [
210+
{ id: "as-added", text: "as-added" },
211+
{ id: "by-value-desc", text: "by-value-desc" },
212+
];
213+
194214
export function PageSettingsForm({
195215
selectedSettings,
196216
}: {
@@ -341,6 +361,19 @@ export function PageSettingsForm({
341361
/>
342362
</FormField>
343363
);
364+
case "tooltipSeriesSorting":
365+
return (
366+
<SegmentedControl
367+
label="Tooltip Series Sorting"
368+
selectedId={settings.tooltipSeriesSorting ?? null}
369+
options={tooltipSeriesSortingOptions}
370+
onChange={({ detail }) =>
371+
setSettings({
372+
tooltipSeriesSorting: detail.selectedId as CoreChartProps.TooltipOptions["seriesSorting"],
373+
})
374+
}
375+
/>
376+
);
344377
case "showLegend":
345378
return (
346379
<Checkbox

src/__tests__/__snapshots__/documenter.test.ts.snap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,20 @@ for each visited { x, y } point.",
18191819
"optional": true,
18201820
"type": "string",
18211821
},
1822+
{
1823+
"inlineType": {
1824+
"name": ""as-added" | "by-value-desc"",
1825+
"type": "union",
1826+
"valueDescriptions": undefined,
1827+
"values": [
1828+
"as-added",
1829+
"by-value-desc",
1830+
],
1831+
},
1832+
"name": "seriesSorting",
1833+
"optional": true,
1834+
"type": "string",
1835+
},
18221836
{
18231837
"inlineType": {
18241838
"name": ""small" | "medium" | "large"",

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import { CoreChartProps } from "../../../lib/components/core/interfaces";
99
import { renderChart, selectLegendItem } from "./common";
1010
import { HighchartsTestHelper } from "./highcharts-utils";
1111

12-
const clearHighlightPause = () => new Promise((resolve) => setTimeout(resolve, 100));
13-
1412
const hc = new HighchartsTestHelper(highcharts);
1513

1614
const series: Highcharts.SeriesOptionsType[] = [
@@ -68,7 +66,7 @@ describe("CoreChart: API tests", () => {
6866

6967
act(() => hc.highlightChartPoint(0, 0));
7068
act(() => hc.leaveChartPoint(0, 0));
71-
await clearHighlightPause();
69+
await hc.clearHighlightPause();
7270

7371
expect(onClearHighlight).toHaveBeenCalledWith(expect.objectContaining({ detail: { isApiCall: false } }));
7472
});

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import { HighchartsTestHelper } from "./highcharts-utils";
1010

1111
const hc = new HighchartsTestHelper(highcharts);
1212

13-
const clearHighlightPause = () => new Promise((resolve) => setTimeout(resolve, 100));
14-
1513
describe("CoreChart: highlight", () => {
1614
test("highlights linked errorbar when target series is highlighted", async () => {
1715
renderChart({
@@ -55,7 +53,7 @@ describe("CoreChart: highlight", () => {
5553
expect(hc.getChartSeries(3).state).toBe("hover");
5654

5755
act(() => hc.leaveChartPoint(1, 0));
58-
await clearHighlightPause();
56+
await hc.clearHighlightPause();
5957

6058
expect(hc.getChartSeries(0).state).toBe("");
6159
expect(hc.getChartSeries(1).state).toBe("");
@@ -90,7 +88,7 @@ describe("CoreChart: highlight", () => {
9088
expect(hc.getChartPoint(1, 1).state).toBe("inactive");
9189

9290
act(() => hc.leaveChartPoint(1, 0));
93-
await clearHighlightPause();
91+
await hc.clearHighlightPause();
9492

9593
expect(hc.getChartSeries(0).state).toBe("");
9694
expect(hc.getChartPoint(0, 0).state).toBe("");

0 commit comments

Comments
 (0)