Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e9f60bf
feat: dual axis support in legend
jsilll Nov 21, 2025
ce12289
fix: update snapshots
jsilll Nov 21, 2025
66c9e74
chore: add tests
jsilll Nov 24, 2025
5d79545
fix: add test for secondary axis-only series scenario
jsilll Nov 24, 2025
e68975f
refactor: use real Highcharts instances in chart utils tests
jsilll Nov 24, 2025
60a4902
fix: add gauge chart support to legend utils test
jsilll Nov 24, 2025
67e13d0
refactor: improve aria label computation logic
jsilll Nov 24, 2025
e0a0d75
refactor(chart-core): extract legend configuration into utility function
jsilll Nov 24, 2025
a086456
refactor(core): extract CoreI18nStrings interface from BaseI18nStrings
jsilll Nov 24, 2025
633d78d
docs: expand shouldShowSecondaryLegend function documentation
jsilll Nov 24, 2025
86ec4d4
chore: update test snapshots
jsilll Nov 25, 2025
ae56c33
feat: add support for inverted charts in secondary legend
jsilll Nov 25, 2025
2f7bb2c
feat(core-legend): add i18n support for legend aria label
jsilll Nov 25, 2025
610ac87
refactor(chart-legend): extract and improve type definitions
jsilll Nov 25, 2025
65d7129
fix(legend): remove auto i18n for secondary legend ARIA label
jsilll Nov 25, 2025
6f4c45b
fix: always render secondary series through secondary legend
jsilll Nov 25, 2025
7289b0f
fix: distinguish primary and secondary legends with unique selectors
jsilll Nov 25, 2025
4767800
docs: update secondary legend comments to reflect current constraints
jsilll Nov 25, 2025
79f5fe1
refactor(utils): hasVisibleLegendItems to getVisibleLegendSeries
jsilll Nov 25, 2025
82c5016
fix: secondary axis detection in chart legend items
jsilll Nov 25, 2025
7a8135c
chore: improve chart legend test coverage and descriptions
jsilll Nov 25, 2025
5fd90b8
fix(chart): include secondaryLegend in noData display condition
jsilll Nov 25, 2025
fdfca63
refactor: simplify and document isSecondaryLegendItem logic
jsilll Nov 25, 2025
0342770
refactor: address comments
jsilll Nov 26, 2025
c757224
fix: return defaultOpposite in isSecondaryLegendItem
jsilll Nov 27, 2025
d77197a
fix: typos in code comments
jsilll Nov 27, 2025
45da0c2
refactor: isSecondary axis tests to use parameterized testing
jsilll Nov 27, 2025
7dc87d1
docs: fix typo in core-legend component comment
jsilll Nov 27, 2025
8548e99
fix: remove unused legendHorizontalAlign from dual-axis chart settings
jsilll Nov 27, 2025
4ecf545
fix: account for right-to-left configurations in getLegendProps
jsilll Nov 27, 2025
d3f7512
fix: remove array shuffling for more consistency when running screens…
jsilll Nov 27, 2025
fbe5c04
refactor(chart-core): axis configuration logic
jsilll Dec 4, 2025
69b20fd
doc(chart-core): explain why passing rtl-adjusted axes is needed
jsilll Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions pages/03-core/core-dual-axis-chart.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { omit } from "lodash";

import Link from "@cloudscape-design/components/link";

import CoreChart from "../../lib/components/internal-do-not-use/core-chart";
import { dateFormatter } from "../common/formatters";
import { PageSettingsForm, useChartSettings } from "../common/page-settings";
import { Page } from "../common/templates";
import pseudoRandom from "../utils/pseudo-random";

function randomInt(min: number, max: number) {
return min + Math.floor(pseudoRandom() * (max - min));
}

const colors = [
"#F15C80",
"#2B908F",
"#F45B5B",
"#91E8E1",
"#8085E9",
"#E4D354",
"#8D4654",
"#7798BF",
"#AAEEEE",
"#FF9655",
];

const baseline = [
{ x: 1600984800000, y: 58020 },
{ x: 1600985700000, y: 102402 },
{ x: 1600986600000, y: 104920 },
{ x: 1600987500000, y: 94031 },
{ x: 1600988400000, y: 125021 },
{ x: 1600989300000, y: 159219 },
{ x: 1600990200000, y: 193082 },
{ x: 1600991100000, y: 162592 },
{ x: 1600992000000, y: 274021 },
{ x: 1600992900000, y: 264286 },
{ x: 1600993800000, y: 289210 },
{ x: 1600994700000, y: 256362 },
{ x: 1600995600000, y: 257306 },
{ x: 1600996500000, y: 186776 },
{ x: 1600997400000, y: 294020 },
{ x: 1600998300000, y: 385975 },
{ x: 1600999200000, y: 486039 },
{ x: 1601000100000, y: 490447 },
{ x: 1601001000000, y: 361845 },
{ x: 1601001900000, y: 339058 },
{ x: 1601002800000, y: 298028 },
{ x: 1601003400000, y: 255555 },
{ x: 1601003700000, y: 231902 },
{ x: 1601004600000, y: 224558 },
{ x: 1601005500000, y: 253901 },
{ x: 1601006400000, y: 102839 },
{ x: 1601007300000, y: 234943 },
{ x: 1601008200000, y: 204405 },
{ x: 1601009100000, y: 190391 },
{ x: 1601010000000, y: 183570 },
{ x: 1601010900000, y: 162592 },
{ x: 1601011800000, y: 148910 },
];

const generatePrimaryAxisData = (letter: string, index: number) => {
return baseline.map(({ x, y }) => ({
name: `Events ${letter}`,
x,
y: y === null ? null : y + randomInt(-100000 * ((index % 3) + 1), 100000 * ((index % 3) + 1)),
}));
};

const generateSecondaryAxisData = (letter: string, index: number) => {
return baseline.map(({ x, y }) => ({
name: `Percentage ${letter}`,
x,
y: y === null ? null : (y / 10000) * randomInt(3 + (index % 5), 10 + (index % 10)),
}));
};

const primarySeriesData: Record<string, ReturnType<typeof generatePrimaryAxisData>> = {};
for (let i = 0; i < 10; i++) {
const letter = String.fromCharCode(65 + i);
primarySeriesData[`data${letter}`] = generatePrimaryAxisData(letter, i);
}

const secondarySeriesData: Record<string, ReturnType<typeof generatePrimaryAxisData>> = {};
for (let i = 0; i < 10; i++) {
const letter = String.fromCharCode(65 + i);
secondarySeriesData[`data${letter}`] = generateSecondaryAxisData(letter, i);
}

const series: Highcharts.SeriesOptionsType[] = [
...Object.values(primarySeriesData),
...Object.values(secondarySeriesData),
].map((data, index) => {
const name = data[0].name;
const isPercentage = name.startsWith("Percentage");
return {
name,
type: "line",
data: data,
yAxis: isPercentage ? 1 : 0,
color: colors[index % colors.length],
dashStyle: isPercentage ? "Dash" : "Solid",
};
});

export default function () {
const { chartProps } = useChartSettings();
return (
<Page
title="Core dual-axis chart demo"
subtitle="This page demonstrates the use of the core chart with two Y axes for displaying data with different scales."
settings={
<PageSettingsForm
selectedSettings={[
"showLegend",
"legendPosition",
"legendBottomMaxHeight",
"showLegendTitle",
"showLegendActions",
]}
/>
}
>
<CoreChart
{...omit(chartProps.cartesian, "ref")}
chartHeight={400}
ariaLabel="Dual axis line chart"
tooltip={{ placement: "outside" }}
options={{
series: series,
xAxis: [
{
type: "datetime",
title: { text: "Time (UTC)" },
valueFormatter: dateFormatter,
},
],
yAxis: [
{
title: { text: "Events" },
},
{
opposite: true,
title: { text: "Percentage (%)" },
},
],
}}
getLegendTooltipContent={({ legendItem }) => ({
header: (
<div style={{ display: "flex" }}>
{legendItem.marker}
{legendItem.name}
</div>
),
body: (
<table>
<tbody style={{ textAlign: "left" }}>
<tr>
<th scope="row">Period</th>
<td>15 min</td>
</tr>
<tr>
<th scope="row">Statistic</th>
<td>Average</td>
</tr>
<tr>
<th scope="row">Unit</th>
<td>Count</td>
</tr>
</tbody>
</table>
),
footer: (
<Link external={true} href="https://example.com/" variant="primary">
Learn more
</Link>
),
})}
/>
</Page>
);
}
2 changes: 2 additions & 0 deletions pages/common/page-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
useFallback: parseBoolean(defaultSettings.useFallback, urlParams.useFallback),
} as PageSettings as SettingsType;
const setSettings = (partial: Partial<SettingsType>) => {
setUrlParams(partial as any);

Check warning on line 113 in pages/common/page-settings.tsx

View workflow job for this annotation

GitHub Actions / build / build

Unexpected any. Specify a different type

Check warning on line 113 in pages/common/page-settings.tsx

View workflow job for this annotation

GitHub Actions / dry-run / Build chart components

Unexpected any. Specify a different type
};

const cartesianChartRef = useRef<CartesianChartProps.Ref>(null);
Expand Down Expand Up @@ -150,7 +150,9 @@
const legend = {
enabled: settings.showLegend,
title: settings.showLegendTitle ? "Legend title" : undefined,
secondaryLegendTitle: settings.showLegendTitle ? "Secondary Legend title" : undefined,
actions: settings.showLegendActions ? <Button variant="icon" iconName="search" /> : undefined,
secondaryLegendActions: settings.showLegendActions ? <Button variant="icon" iconName="calendar" /> : undefined,
position: settings.legendPosition,
bottomMaxHeight: settings.legendBottomMaxHeight,
horizontalAlignment: settings.legendHorizontalAlign,
Expand Down
20 changes: 18 additions & 2 deletions src/__tests__/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,11 @@ exports[`internal core API matches snapshot > internal-core-chart 1`] = `
"optional": false,
"type": "string",
},
{
"name": "isSecondary",
"optional": true,
"type": "boolean",
},
{
"name": "marker",
"optional": true,
Expand Down Expand Up @@ -1538,18 +1543,19 @@ Supported Highcharts versions: 12.",
"description": "An object that contains all of the localized strings required by the component.",
"i18nTag": true,
"inlineType": {
"name": "CartesianI18nStrings & PieI18nStrings",
"name": "CartesianI18nStrings & PieI18nStrings & CoreI18nStrings",
"type": "union",
"valueDescriptions": undefined,
"values": [
"CartesianI18nStrings",
"PieI18nStrings",
"CoreI18nStrings",
],
},
"name": "i18nStrings",
"optional": true,
"systemTags": undefined,
"type": "CartesianI18nStrings & PieI18nStrings",
"type": "CartesianI18nStrings & PieI18nStrings & CoreI18nStrings",
"visualRefreshTag": undefined,
},
{
Expand Down Expand Up @@ -1617,6 +1623,16 @@ Supported Highcharts versions: 12.",
"optional": true,
"type": "string",
},
{
"name": "secondaryLegendActions",
"optional": true,
"type": "React.ReactNode",
},
{
"name": "secondaryLegendTitle",
"optional": true,
"type": "string",
},
{
"name": "title",
"optional": true,
Expand Down
Loading
Loading