Skip to content

Commit 7ba137d

Browse files
authored
Merge branch 'main' into change-chart-type-page
2 parents 4820303 + a764511 commit 7ba137d

File tree

9 files changed

+112
-10
lines changed

9 files changed

+112
-10
lines changed

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

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { act } from "react";
55
import highcharts from "highcharts";
66
import { vi } from "vitest";
77

8-
import { CoreChartAPI } from "../../../lib/components/core/interfaces";
8+
import { CoreChartProps } from "../../../lib/components/core/interfaces";
99
import { renderChart, selectLegendItem } from "./common";
1010
import { HighchartsTestHelper } from "./highcharts-utils";
1111

@@ -18,10 +18,28 @@ const series: Highcharts.SeriesOptionsType[] = [
1818
{ type: "line", name: "Line 2", data: [4, 5, 6] },
1919
];
2020

21+
const threeSeries: Highcharts.SeriesOptionsType[] = [
22+
{ type: "line", name: "Line 1", data: [1, 2, 3] },
23+
{ type: "line", name: "Line 2", data: [4, 5, 6] },
24+
{ type: "line", name: "Line 3", data: [7, 8, 9] },
25+
];
26+
27+
const pieSeries: Highcharts.SeriesOptionsType[] = [
28+
{
29+
type: "pie",
30+
name: "Data",
31+
data: [
32+
{ name: "Segment 1", y: 33.3 },
33+
{ name: "Segment 2", y: 33.3 },
34+
{ name: "Segment 3", y: 33.4 },
35+
],
36+
},
37+
];
38+
2139
describe("CoreChart: API tests", () => {
2240
test("passes isApiCall=false to onHighlight when triggered by an user interaction", () => {
2341
const onHighlight = vi.fn();
24-
renderChart({ highcharts, options: { series }, onHighlight });
42+
renderChart({ highcharts, onHighlight, options: { series } });
2543

2644
act(() => hc.highlightChartPoint(0, 0));
2745

@@ -32,7 +50,7 @@ describe("CoreChart: API tests", () => {
3250

3351
test("passes isApiCall=true to onHighlight when triggered programmatically through API", () => {
3452
const onHighlight = vi.fn();
35-
let chartApi: CoreChartAPI | null = null;
53+
let chartApi: CoreChartProps.ChartAPI | null = null;
3654

3755
renderChart({ highcharts, onHighlight, options: { series }, callback: (api) => (chartApi = api) });
3856

@@ -55,7 +73,7 @@ describe("CoreChart: API tests", () => {
5573

5674
test("passes isApiCall=true to onClearHighlight when triggered programmatically through API", () => {
5775
const onClearHighlight = vi.fn();
58-
let chartApi: CoreChartAPI | null = null;
76+
let chartApi: CoreChartProps.ChartAPI | null = null;
5977

6078
renderChart({ highcharts, onClearHighlight, options: { series }, callback: (api) => (chartApi = api) });
6179

@@ -74,11 +92,49 @@ describe("CoreChart: API tests", () => {
7492

7593
test("passes isApiCall=true to onVisibleItemsChange when triggered programmatically through API", () => {
7694
const onVisibleItemsChange = vi.fn();
77-
let chartApi: CoreChartAPI | null = null;
95+
let chartApi: CoreChartProps.ChartAPI | null = null;
7896

7997
renderChart({ highcharts, options: { series }, onVisibleItemsChange, callback: (api) => (chartApi = api) });
8098

8199
act(() => chartApi!.setItemsVisible(["Line 1"]));
82100
expect(onVisibleItemsChange).toHaveBeenCalledWith({ items: expect.any(Array), isApiCall: true });
83101
});
102+
103+
test("highlightItems should only highlight the specified series in a line chart", () => {
104+
let chartApi: CoreChartProps.ChartAPI | null = null;
105+
renderChart({ highcharts, options: { series: threeSeries }, callback: (api) => (chartApi = api) });
106+
107+
act(() => chartApi!.highlightItems([hc.getChartSeries(1).name]));
108+
109+
expect(hc.getChartSeries(0).state).toBe("inactive");
110+
expect(hc.getChartSeries(1).state).toBe("");
111+
expect(hc.getChartSeries(2).state).toBe("inactive");
112+
113+
act(() => chartApi!.clearChartHighlight());
114+
act(() => chartApi!.highlightItems([hc.getChartSeries(0).name, hc.getChartSeries(2).name]));
115+
116+
expect(hc.getChartSeries(0).state).toBe("");
117+
expect(hc.getChartSeries(1).state).toBe("inactive");
118+
expect(hc.getChartSeries(2).state).toBe("");
119+
});
120+
121+
test("highlightItems should only highlight the specified point in a pie chart", () => {
122+
let chartApi: CoreChartProps.ChartAPI | null = null;
123+
renderChart({ highcharts, options: { series: pieSeries }, callback: (api) => (chartApi = api) });
124+
125+
const series = hc.getChartSeries(0);
126+
127+
act(() => chartApi!.highlightItems([series.points[1].name]));
128+
129+
expect(hc.getChartPoint(0, 0).state).toBe(undefined);
130+
expect(hc.getChartPoint(0, 1).state).toBe("hover");
131+
expect(hc.getChartPoint(0, 2).state).toBe(undefined);
132+
133+
act(() => chartApi!.clearChartHighlight());
134+
act(() => chartApi!.highlightItems([series.points[0].name, series.points[2].name]));
135+
136+
expect(hc.getChartPoint(0, 0).state).toBe("hover");
137+
expect(hc.getChartPoint(0, 1).state).toBe("");
138+
expect(hc.getChartPoint(0, 2).state).toBe("hover");
139+
});
84140
});

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

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

44
import { act } from "react";
55
import highcharts from "highcharts";
6+
import { vi } from "vitest";
67

78
import { KeyCode } from "@cloudscape-design/component-toolkit/internal";
89

9-
import { createChartWrapper, renderChart } from "./common";
10+
import { createChartWrapper, hoverLegendItem, renderChart } from "./common";
1011
import { HighchartsTestHelper } from "./highcharts-utils";
1112

1213
import legendTestClasses from "../../../lib/components/internal/components/chart-legend/test-classes/styles.selectors.js";
@@ -451,4 +452,23 @@ describe("CoreChart: legend", () => {
451452
expect(wrapper.findLegend()!.findItemTooltip()!.findHeader()!.getElement().textContent).toBe("P2");
452453
});
453454
});
455+
456+
test("calls onLegendItemHighlight when hovering over a legend item", () => {
457+
const onLegendItemHighlight = vi.fn();
458+
const { wrapper } = renderChart({ highcharts, options: { series }, onLegendItemHighlight });
459+
460+
hoverLegendItem(0, wrapper);
461+
462+
expect(onLegendItemHighlight).toHaveBeenCalledWith(
463+
expect.objectContaining({
464+
item: expect.objectContaining({
465+
id: "L1",
466+
name: "L1",
467+
marker: expect.any(Object),
468+
visible: expect.any(Boolean),
469+
highlighted: expect.any(Boolean),
470+
}),
471+
}),
472+
);
473+
});
454474
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe("CoreChart: rendering", () => {
3838
expect(callback).toHaveBeenCalledWith({
3939
chart: hc.getChart(),
4040
highcharts,
41+
highlightItems: expect.any(Function),
4142
setItemsVisible: expect.any(Function),
4243
highlightChartPoint: expect.any(Function),
4344
highlightChartGroup: expect.any(Function),

src/core/__tests__/common.tsx

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

44
import { act, useState } from "react";
5-
import { render } from "@testing-library/react";
5+
import { fireEvent, render } from "@testing-library/react";
66

77
import "@cloudscape-design/components/test-utils/dom";
88
import { CoreChartProps } from "../../../lib/components/core/interfaces";
@@ -33,6 +33,7 @@ export function StatefulChart(props: CoreChartProps) {
3333
}
3434

3535
type TestProps = Partial<CoreChartProps> & {
36+
onLegendItemHighlight?: () => void;
3637
i18nProvider?: Record<string, Record<string, string>>;
3738
};
3839

@@ -86,3 +87,8 @@ export function toggleLegendItem(index: number, wrapper: BaseChartWrapper = crea
8687
const modifier = Math.random() > 0.5 ? { metaKey: true } : { ctrlKey: true };
8788
act(() => wrapper.findLegend()!.findItems()[index].click(modifier));
8889
}
90+
export function hoverLegendItem(index: number, wrapper: BaseChartWrapper = createChartWrapper()) {
91+
act(() => {
92+
fireEvent.mouseOver(wrapper.findLegend()!.findItems()[index].getElement());
93+
});
94+
}

src/core/chart-api/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ export class ChartAPI {
208208

209209
// Callbacks used for hover and keyboard navigation, and also exposed to the public API to give the ability
210210
// to highlight and show tooltip for the given point or group manually.
211+
public highlightChartItems = (itemIds: readonly string[]) => {
212+
this.chartExtraHighlight.highlightChartItems(itemIds);
213+
};
211214
public setItemsVisible = (visibleItemsIds: readonly string[], { isApiCall }: { isApiCall: boolean }) => {
212215
this.chartExtraLegend.onItemVisibilityChange(visibleItemsIds, { isApiCall });
213216
};
@@ -228,6 +231,7 @@ export class ChartAPI {
228231
};
229232
public get publicApi() {
230233
return {
234+
highlightItems: (itemIds: readonly string[]) => this.highlightChartItems(itemIds),
231235
setItemsVisible: (visibleItemIds: readonly string[]) => this.setItemsVisible(visibleItemIds, { isApiCall: true }),
232236
highlightChartPoint: (point: Highcharts.Point) => this.highlightChartPoint(point, { isApiCall: true }),
233237
highlightChartGroup: (group: readonly Highcharts.Point[]) => this.highlightChartGroup(group, { isApiCall: true }),

src/core/chart-core.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function InternalCoreChart({
5858
filter,
5959
keyboardNavigation = true,
6060
onHighlight,
61+
onLegendItemHighlight,
6162
onClearHighlight,
6263
onVisibleItemsChange,
6364
visibleItems,
@@ -307,6 +308,7 @@ export function InternalCoreChart({
307308
position={legendPosition}
308309
api={api}
309310
i18nStrings={i18nStrings}
311+
onItemHighlight={onLegendItemHighlight}
310312
getLegendTooltipContent={rest.getLegendTooltipContent}
311313
/>
312314
) : null

src/core/components/core-legend.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ export function ChartLegend({
1414
actions,
1515
position,
1616
i18nStrings,
17+
onItemHighlight,
1718
getLegendTooltipContent,
1819
}: {
1920
api: ChartAPI;
2021
title?: string;
2122
actions?: React.ReactNode;
2223
position: "bottom" | "side";
2324
i18nStrings?: BaseI18nStrings;
25+
onItemHighlight?: (detail: CoreChartProps.LegendItemHighlightDetail) => void;
2426
getLegendTooltipContent?: CoreChartProps.GetLegendTooltipContent;
2527
}) {
2628
const i18n = useInternalI18n("[charts]");
@@ -39,8 +41,11 @@ export function ChartLegend({
3941
actions={actions}
4042
position={position}
4143
onItemVisibilityChange={api.onItemVisibilityChange}
42-
onItemHighlightEnter={(itemId) => api.onHighlightChartItems([itemId])}
4344
onItemHighlightExit={api.onClearChartItemsHighlight}
45+
onItemHighlightEnter={(item) => {
46+
api.onHighlightChartItems([item.id]);
47+
onItemHighlight?.({ item });
48+
}}
4449
getTooltipContent={(props) => {
4550
if (isChartTooltipPinned) {
4651
return null;

src/core/interfaces.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,10 @@ export interface CoreChartProps
341341
* IDs of visible series or points.
342342
*/
343343
visibleItems?: readonly string[];
344+
/**
345+
* Called when a legend item is highlighted.
346+
*/
347+
onLegendItemHighlight?: (detail: CoreChartProps.LegendItemHighlightDetail) => void;
344348
/**
345349
* Called when series/points visibility changes due to user interaction with legend or filter.
346350
*/
@@ -379,6 +383,7 @@ export namespace CoreChartProps {
379383
export interface ChartAPI {
380384
chart: Highcharts.Chart;
381385
highcharts: typeof Highcharts;
386+
highlightItems(itemIds: readonly string[]): void;
382387
setItemsVisible(itemIds: readonly string[]): void;
383388
highlightChartPoint(point: Highcharts.Point): void;
384389
highlightChartGroup(group: readonly Highcharts.Point[]): void;
@@ -440,6 +445,9 @@ export namespace CoreChartProps {
440445
items: TooltipContentItem[];
441446
}
442447

448+
export interface LegendItemHighlightDetail {
449+
item: LegendItem;
450+
}
443451
export interface VisibleItemsChangeDetail {
444452
items: readonly LegendItem[];
445453
isApiCall: boolean;

src/internal/components/chart-legend/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export interface ChartLegendProps {
3232
ariaLabel?: string;
3333
actions?: React.ReactNode;
3434
position: "bottom" | "side";
35-
onItemHighlightEnter: (itemId: string) => void;
35+
onItemHighlightEnter: (item: LegendItem) => void;
3636
onItemHighlightExit: () => void;
3737
onItemVisibilityChange: (hiddenItems: string[]) => void;
3838
getTooltipContent: (props: GetLegendTooltipContentProps) => null | LegendTooltipContent;
@@ -120,7 +120,7 @@ export const ChartLegend = ({
120120
const item = items.find((item) => item.id === itemId);
121121
if (item?.visible) {
122122
highlightControl.cancelPrevious();
123-
onItemHighlightEnter(itemId);
123+
onItemHighlightEnter(item);
124124
}
125125
};
126126
const clearHighlight = () => {

0 commit comments

Comments
 (0)