Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
224 changes: 224 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,224 @@
// 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));
}

function shuffleArray<T>(array: T[]): void {
let currentIndex = array.length;
while (currentIndex !== 0) {
const randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
}
}

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

const dashStyles: Highcharts.DashStyleValue[] = [
"Dash",
"DashDot",
"Dot",
"LongDash",
"LongDashDot",
"LongDashDotDot",
"ShortDash",
"ShortDashDot",
"ShortDashDotDot",
"ShortDot",
"Solid",
];

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.entries(primarySeriesData).forEach(([, data], index) => {
series.push({
name: data[0].name,
type: "line",
data: data,
yAxis: 0,
color: colors[index],
});
});

Object.entries(secondarySeriesData).forEach(([, data], index) => {
series.push({
name: data[0].name,
type: "line",
data: data,
yAxis: 1,
color: colors[index],
dashStyle: dashStyles[index % dashStyles.length],
});
});

shuffleArray(series);

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",
"legendType",
"legendPosition",
"legendBottomMaxHeight",
"showLegendTitle",
"showOppositeLegendTitle",
"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>
<div style={{ display: "flex" }}>
{legendItem.marker}
{legendItem.name}
</div>
</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>
);
}
29 changes: 29 additions & 0 deletions pages/common/page-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export interface PageSettings {
tooltipSize: "small" | "medium" | "large";
showLegend: boolean;
showLegendTitle: boolean;
showOppositeLegendTitle: boolean;
showLegendActions: boolean;
legendType: "single" | "dual";
legendBottomMaxHeight?: number;
legendPosition: "bottom" | "side";
showCustomHeader: boolean;
Expand All @@ -59,6 +61,8 @@ const DEFAULT_SETTINGS: PageSettings = {
tooltipSize: "medium",
showLegend: true,
showLegendTitle: false,
showOppositeLegendTitle: false,
legendType: "single",
legendPosition: "bottom",
showLegendActions: false,
showCustomHeader: false,
Expand Down Expand Up @@ -146,7 +150,9 @@ export function useChartSettings<SettingsType extends PageSettings = PageSetting
const legend = {
enabled: settings.showLegend,
title: settings.showLegendTitle ? "Legend title" : undefined,
oppositeLegendTitle: settings.showOppositeLegendTitle ? "Opposite Legend title" : undefined,
actions: settings.showLegendActions ? <Button variant="icon" iconName="search" /> : undefined,
type: settings.legendType,
position: settings.legendPosition,
bottomMaxHeight: settings.legendBottomMaxHeight,
};
Expand Down Expand Up @@ -343,6 +349,20 @@ export function PageSettingsForm({
Show legend
</Checkbox>
);
case "legendType":
return (
<SegmentedControl
label="Legend Type"
selectedId={settings.legendType}
options={[
{ text: "Single", id: "single", disabled: !settings.showLegend },
{ text: "Dual", id: "dual", disabled: !settings.showLegend },
]}
onChange={({ detail }) =>
setSettings({ legendType: detail.selectedId as string as "single" | "dual" })
}
/>
);
case "showLegendTitle":
return (
<Checkbox
Expand All @@ -352,6 +372,15 @@ export function PageSettingsForm({
Show legend title
</Checkbox>
);
case "showOppositeLegendTitle":
return (
<Checkbox
checked={settings.showOppositeLegendTitle}
onChange={({ detail }) => setSettings({ showOppositeLegendTitle: detail.checked })}
>
Show opposite legend title
</Checkbox>
);
case "showLegendActions":
return (
<Checkbox
Expand Down
4 changes: 2 additions & 2 deletions src/core/chart-api/chart-extra-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export class ChartExtraLegend extends AsyncStore<ReactiveLegendState> {

private initLegend = () => {
const itemSpecs = getChartLegendItems(this.context.chart());
const legendItems = itemSpecs.map(({ id, name, color, markerType, visible }) => {
const legendItems = itemSpecs.map(({ id, name, color, markerType, visible, oppositeAxis }) => {
const marker = this.renderMarker(markerType, color, visible);
return { id, name, marker, visible, highlighted: false };
return { id, name, marker, visible, oppositeAxis, highlighted: false };
});
this.updateLegendItems(legendItems);
};
Expand Down
6 changes: 1 addition & 5 deletions src/core/chart-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface ChartContainerProps {
filter?: React.ReactNode;
navigator?: React.ReactNode;
legend?: React.ReactNode;
legendBottomMaxHeight?: number;
legendPosition: "bottom" | "side";
footer?: React.ReactNode;
noData?: React.ReactNode;
Expand All @@ -48,7 +47,6 @@ export function ChartContainer({
footer,
legend,
legendPosition,
legendBottomMaxHeight,
navigator,
noData,
fitHeight,
Expand Down Expand Up @@ -106,9 +104,7 @@ export function ChartContainer({

<div ref={refs.footer} style={chartMinWidth !== undefined ? { minInlineSize: chartMinWidth } : {}}>
{navigator && <div className={testClasses["chart-navigator"]}>{navigator}</div>}
{legend &&
legendPosition === "bottom" &&
(legendBottomMaxHeight ? <div style={{ maxHeight: `${legendBottomMaxHeight}px` }}>{legend}</div> : legend)}
{legendPosition === "bottom" && legend}
{footer}
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/core/chart-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export function InternalCoreChart({
const rootRef = useRef<HTMLDivElement>(null);
const mergedRootRef = useMergeRefs(rootRef, __internalRootRef);
const rootProps = { ref: mergedRootRef, className: rootClassName, ...getDataAttributes(rest) };
const legendType = legendOptions?.type ?? "single";
const legendPosition = legendOptions?.position ?? "bottom";
const containerProps = {
fitHeight,
Expand All @@ -91,7 +92,6 @@ export function InternalCoreChart({
chartMinWidth,
legendPosition,
verticalAxisTitlePlacement,
legendBottomMaxHeight: legendOptions?.bottomMaxHeight,
};

// Render fallback using the same root and container props as for the chart to ensure consistent
Expand Down Expand Up @@ -305,6 +305,7 @@ export function InternalCoreChart({
context.legendEnabled && hasVisibleLegendItems(options) ? (
<ChartLegend
{...legendOptions}
type={legendType}
position={legendPosition}
api={api}
i18nStrings={i18nStrings}
Expand Down
11 changes: 10 additions & 1 deletion src/core/components/core-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ export function ChartLegend({
api,
title,
actions,
type,
position,
i18nStrings,
bottomMaxHeight,
oppositeLegendTitle,
onItemHighlight,
getLegendTooltipContent,
}: {
api: ChartAPI;
title?: string;
bottomMaxHeight?: number;
oppositeLegendTitle?: string;
actions?: React.ReactNode;
type: "single" | "dual";
position: "bottom" | "side";
i18nStrings?: BaseI18nStrings;
onItemHighlight?: NonCancelableEventHandler<CoreChartProps.LegendItemHighlightDetail>;
Expand All @@ -37,10 +43,13 @@ export function ChartLegend({
return (
<ChartLegendComponent
ariaLabel={ariaLabel}
legendTitle={title}
type={type}
title={title}
oppositeTitle={oppositeLegendTitle}
items={legendItems}
actions={actions}
position={position}
bottomMaxHeight={bottomMaxHeight}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we implement the legend layout inside the legend itself? Instead of passing the bottomMaxHeight down to the legend, you can give extra slot in the chart-container, so that it can render either a single or a double legend.

onItemVisibilityChange={api.onItemVisibilityChange}
onItemHighlightExit={api.onClearChartItemsHighlight}
onItemHighlightEnter={(item) => {
Expand Down
2 changes: 2 additions & 0 deletions src/core/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,9 @@ export namespace CoreChartProps {

export interface LegendOptions extends BaseLegendOptions {
bottomMaxHeight?: number;
type?: "single" | "dual";
position?: "bottom" | "side";
oppositeLegendTitle?: string;
}
export type LegendItem = InternalComponentTypes.LegendItem;
export type LegendTooltipContent = InternalComponentTypes.LegendTooltipContent;
Expand Down
Loading