diff --git a/src/cartesian-chart/__tests__/cartesian-chart-series.test.tsx b/src/cartesian-chart/__tests__/cartesian-chart-series.test.tsx index 1c85a573..95d4bd8c 100644 --- a/src/cartesian-chart/__tests__/cartesian-chart-series.test.tsx +++ b/src/cartesian-chart/__tests__/cartesian-chart-series.test.tsx @@ -18,7 +18,7 @@ const allSeries: CartesianChartProps.SeriesOptions[] = [ { type: "line", name: "Line", data: [{ x: 1, y: 6 }], color: "5" }, { type: "scatter", name: "Scatter", data: [{ x: 1, y: 7 }], color: "6" }, { type: "spline", name: "Spline", data: [{ x: 1, y: 8 }], color: "7" }, - { type: "bubble", name: "Bubble", data: [{ x: 1, y: 9, z: 5 }], color: "8" }, + { type: "bubble", name: "Bubble", data: [{ x: 1, y: 9, size: 5 }], color: "8" }, { type: "x-threshold", name: "X threshold", value: 1, color: "9" }, { type: "y-threshold", name: "Y threshold", value: 9, color: "10" }, ]; diff --git a/src/core/__tests__/chart-core-navigation-cartesian.test.tsx b/src/core/__tests__/chart-core-navigation-cartesian.test.tsx index 1baea946..680c6a5a 100644 --- a/src/core/__tests__/chart-core-navigation-cartesian.test.tsx +++ b/src/core/__tests__/chart-core-navigation-cartesian.test.tsx @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import highcharts from "highcharts"; +import Highcharts from "highcharts"; import { range } from "lodash"; import { vi } from "vitest"; import { KeyCode } from "@cloudscape-design/component-toolkit/internal"; +import "highcharts/highcharts-more"; import "highcharts/modules/accessibility"; import { CoreChartTestProps, createChartWrapper, renderChart } from "./common"; @@ -56,6 +58,18 @@ const seriesShort3: Highcharts.SeriesOptionsType[] = [ ], }, ]; +const seriesShort4: Highcharts.SeriesOptionsType[] = [ + ...seriesShort3, + { + type: "bubble", + name: "Bubble series 1", + data: [ + { x: 1, y: 21, z: 31 }, + { x: 3, y: 23, z: 32 }, + { x: 4, y: 24, z: 33 }, + ], + }, +]; const seriesLong1: Highcharts.SeriesOptionsType[] = [ { type: "line", @@ -601,21 +615,26 @@ describe("CoreChart: navigation, cartesian charts", () => { const { wrapper } = renderChart({ ...commonProps(), options: { - series: seriesShort2, + series: seriesShort4, xAxis: { title: { text: "X" }, valueFormatter: (value) => `x${value}` }, yAxis: { title: { text: "Y" }, valueFormatter: (value) => `y${value}` }, }, + sizeAxis: { title: "Size axis", valueFormatter: (value) => `size${value}` }, }); focusApplication(); keyDown(KeyCode.home); expect(describeFocusedElement()).toBe("button:x1[true,false]"); - expect(wrapper.findTooltip()!.getElement().textContent).toBe("x1Line series 1y11Line series 2y21"); + expect(wrapper.findTooltip()!.getElement().textContent).toBe( + "x1Line series 1y11Line series 2y21Line series 3y31Bubble series 1Yy21Size axissize31", + ); keyDown(KeyCode.down); expect(describeFocusedElement()).toBe("button:x1 y11, Line series 1[true,false]"); - expect(wrapper.findTooltip()!.getElement().textContent).toBe("x1Line series 1y11Line series 2y21"); + expect(wrapper.findTooltip()!.getElement().textContent).toBe( + "x1Line series 1y11Line series 2y21Line series 3y31Bubble series 1Yy21Size axissize31", + ); }); }); diff --git a/src/core/chart-core.tsx b/src/core/chart-core.tsx index 4a3bd816..adf28c27 100644 --- a/src/core/chart-core.tsx +++ b/src/core/chart-core.tsx @@ -213,7 +213,10 @@ export function InternalCoreChart({ keyboardNavigation: options.accessibility?.keyboardNavigation ?? { enabled: !keyboardNavigation }, point: { // Point description formatter is overridden to respect custom axes value formatters. - descriptionFormatter: (point) => getPointAccessibleDescription(point, labels), + descriptionFormatter: (point) => + getPointAccessibleDescription(point, labels, { + sizeAxis: castArray(rest.sizeAxis as CoreChartProps.SizeAxisOptions[]), + }), ...options.accessibility?.point, }, }, diff --git a/src/core/components/core-tooltip.tsx b/src/core/components/core-tooltip.tsx index f2df00fe..877ccb19 100644 --- a/src/core/components/core-tooltip.tsx +++ b/src/core/components/core-tooltip.tsx @@ -16,7 +16,6 @@ import { ChartAPI } from "../chart-api"; import { getFormatter } from "../formatters"; import { BaseI18nStrings, CoreChartProps } from "../interfaces"; import { - getBubbleSeriesSizeAxis, getPointColor, getPointId, getSeriesColor, @@ -24,6 +23,7 @@ import { getSeriesMarkerType, isPointVisible, isXThreshold, + matchSizeAxis, } from "../utils"; import styles from "../styles.css.js"; @@ -318,7 +318,7 @@ function getBubblePointDetails(item: MatchedItem, sizeAxis: readonly CoreChartPr // Size axis is a custom abstraction built to support bubble series title and formatter for z (size) values. // We match size axes by ID if provided, or take the first defined axis instead. - const matchedSizeAxis = sizeAxis.find((a) => a.id === getBubbleSeriesSizeAxis(item.point.series)) ?? sizeAxis[0]; + const matchedSizeAxis = matchSizeAxis(sizeAxis, item.point.series); if (matchedSizeAxis) { // Highcharts bubble size is represented by point.z value, which is however not present in the internal point type - // so we take it from point's options instead. diff --git a/src/core/utils.ts b/src/core/utils.ts index 0c773a6f..887cae4a 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -368,11 +368,28 @@ export function getChartAccessibleDescription(chart: Highcharts.Chart) { return chart.options.lang?.accessibility?.chartContainerLabel ?? ""; } -export function getPointAccessibleDescription(point: Highcharts.Point, labels: ChartLabels) { +export function matchSizeAxis(sizeAxis: readonly CoreChartProps.SizeAxisOptions[], series: Highcharts.Series) { + return sizeAxis.find((a) => a.id === getBubbleSeriesSizeAxis(series)) ?? sizeAxis[0]; +} + +export function getPointAccessibleDescription( + point: Highcharts.Point, + labels: ChartLabels, + additionalProps?: { + sizeAxis?: readonly CoreChartProps.SizeAxisOptions[]; + }, +) { if (point.series.xAxis && point.series.yAxis) { const formattedX = getFormatter(point.series.xAxis)(point.x); const formattedY = getFormatter(point.series.yAxis)(point.y); - return `${formattedX} ${formattedY}, ${point.series.name}`; + let formattedSize = ""; + if (additionalProps?.sizeAxis && point.series.type === "bubble") { + const matchedSizeAxis = matchSizeAxis(additionalProps.sizeAxis, point.series); + const size = point.options.z ?? null; + const sizeFormatter = getFormatter(matchedSizeAxis); + formattedSize = sizeFormatter(size); + } + return `${formattedX} ${formattedY}${formattedSize ? " " + formattedSize : ""}, ${point.series.name}`; } else if (point.series.type === "pie") { const segmentLabel = labels.chartSegmentLabel ? `${labels.chartSegmentLabel} ` : ""; return `${segmentLabel}${point.name}, ${point.y}. ${point.series.name}`;