diff --git a/.eslintrc b/.eslintrc index 036ba08d..9337e345 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,14 +7,14 @@ "plugin:react/jsx-runtime", "plugin:prettier/recommended", "plugin:no-unsanitized/DOM", - "plugin:@vitest/legacy-recommended" + "plugin:@vitest/legacy-recommended", ], "parserOptions": { "ecmaVersion": "latest", "sourceType": "module", "ecmaFeatures": { - "jsx": true - } + "jsx": true, + }, }, "plugins": ["unicorn", "react-hooks", "no-unsanitized", "header", "import", "simple-import-sort", "@vitest"], "rules": { @@ -29,22 +29,22 @@ "react/no-unstable-nested-components": [ "error", { - "allowAsProps": true - } + "allowAsProps": true, + }, ], "react/forbid-component-props": [ "warn", { - "forbid": ["className", "id"] - } + "forbid": ["className", "id"], + }, ], "react/jsx-boolean-value": ["error", "always"], "@typescript-eslint/naming-convention": [ "error", { "selector": "typeLike", - "format": ["PascalCase"] - } + "format": ["PascalCase"], + }, ], "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": ["error"], @@ -57,7 +57,8 @@ "header/header": [ "error", "line", - [" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.", " SPDX-License-Identifier: Apache-2.0"] + [" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.", " SPDX-License-Identifier: Apache-2.0"], + 2, ], "no-restricted-imports": [ "error", @@ -66,32 +67,32 @@ { "name": "react", "importNames": ["default"], - "message": "Prefer named imports." + "message": "Prefer named imports.", }, { "name": "@cloudscape-design/components", - "message": "Prefer subpath imports." - } - ] - } + "message": "Prefer subpath imports.", + }, + ], + }, ], "import/no-useless-path-segments": [ "warn", { - "noUselessIndex": true - } + "noUselessIndex": true, + }, ], "simple-import-sort/imports": "warn", - "@vitest/no-focused-tests": "error" + "@vitest/no-focused-tests": "error", }, "settings": { "react": { - "version": "detect" - } + "version": "detect", + }, }, "env": { "browser": true, - "es6": true + "es6": true, }, "overrides": [ { @@ -99,11 +100,11 @@ "rules": { // useBrowser is not a React hook "react-hooks/rules-of-hooks": "off", - "react-hooks/exhaustive-deps": "off" + "react-hooks/exhaustive-deps": "off", }, "env": { - "jest": true - } + "jest": true, + }, }, { "files": ["src/**", "pages/**", "test/**", "scripts/**"], @@ -121,13 +122,11 @@ // Anything not matched in another group. ["^"], // Styles come last. - [ - "^.+\\.?(css)$","^.+\\.?(css.js)$", "^.+\\.?(scss)$", "^.+\\.?(selectors.js)$" - ] - ] - } - ] - } - } - ] + ["^.+\\.?(css)$", "^.+\\.?(css.js)$", "^.+\\.?(scss)$", "^.+\\.?(selectors.js)$"], + ], + }, + ], + }, + }, + ], } diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 150a0006..9549b5f2 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -24,6 +24,7 @@ jobs: with: artifact-path: dist artifact-name: dev-pages + skip-codeql: true deploy: needs: build uses: cloudscape-design/actions/.github/workflows/deploy.yml@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 086266d9..9a23f87b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: push: branches: - main + - charts-0.1 - "dev-v3-*" permissions: @@ -57,4 +58,4 @@ jobs: AWSUI_GITHUB_COMMIT_SHA: ${{ github.sha }} AWSUI_GITHUB_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} AWSUI_GITHUB_REPOSITORY_NAME: ${{ github.event.repository.full_name }} - AWSUI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + AWSUI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 153f40fb..6884dfe8 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -19,30 +19,30 @@ jobs: if: github.event.ref != 'refs/heads/main' steps: - uses: actions/checkout@v4 - # - uses: actions/setup-node@v4 - # with: - # node-version: 20 - # cache: "npm" - # - name: "Get reference run id" - # run: | - # echo "RUN_ID=`gh run --repo $GITHUB_REPOSITORY --branch main list --workflow "Visual Regressions" --json databaseId --jq .[0].databaseId`" >> $GITHUB_ENV - # echo "Reference snapshots created in run ${RUN_ID}" - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # - uses: actions/download-artifact@v4 - # with: - # name: visual-regression-snapshots - # github-token: ${{ secrets.GITHUB_TOKEN }} - # run-id: ${{ env.RUN_ID }} - # path: ${{ env.VISUAL_REGRESSION_SNAPSHOT_DIRECTORY }} - # - run: npm install - # - run: npm run build - # - run: npm run test:visual - # - uses: actions/upload-artifact@v4 - # if: always() - # with: - # name: visual-regression-snapshots-results - # path: ${{ env.VISUAL_REGRESSION_SNAPSHOT_DIRECTORY }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + - name: "Get reference run id" + run: | + echo "RUN_ID=`gh run --repo $GITHUB_REPOSITORY --branch main list --workflow "Visual Regressions" --json databaseId --jq .[0].databaseId`" >> $GITHUB_ENV + echo "Reference snapshots created in run ${RUN_ID}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/download-artifact@v4 + with: + name: visual-regression-snapshots + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ env.RUN_ID }} + path: ${{ env.VISUAL_REGRESSION_SNAPSHOT_DIRECTORY }} + - run: npm install + - run: npm run build + - run: npm run test:visual + - uses: actions/upload-artifact@v4 + if: always() + with: + name: visual-regression-snapshots-results + path: ${{ env.VISUAL_REGRESSION_SNAPSHOT_DIRECTORY }} update: name: Update Snapshots runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 407b3ae7..88d9d0ec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,6 @@ coverage lib /__image_snapshots__ dist -/src/test-utils/selectors -/src/test-utils/dom/index.ts +/src/test-utils/selectors/**/*.ts +!/src/test-utils/selectors/index.ts .DS_STORE diff --git a/package.json b/package.json index f01d3674..2bf26590 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "exports": { ".": "./index.js", "./test-utils/dom": "./test-utils/dom/index.js", + "./test-utils/dom/internal/core": "./test-utils/dom/internal/core.js", "./test-utils/selectors": "./test-utils/selectors/index.js", + "./test-utils/selectors/internal/core": "./test-utils/selectors/internal/core.js", "./internal/api-docs/*.js": "./internal/api-docs/*.js", "./internal-do-not-use/core-chart": "./internal-do-not-use/core-chart.js" }, @@ -53,7 +55,7 @@ "@cloudscape-design/components": "^3", "@cloudscape-design/design-tokens": "^3", "highcharts": "^12.2.0", - "react": ">=18.2.0" + "react": ">=16.0.0" }, "devDependencies": { "@cloudscape-design/browser-test-tools": "^3.0.4", diff --git a/pages/01-cartesian-chart/axes-and-thresholds.page.tsx b/pages/01-cartesian-chart/axes-and-thresholds.page.tsx new file mode 100644 index 00000000..b0950209 --- /dev/null +++ b/pages/01-cartesian-chart/axes-and-thresholds.page.tsx @@ -0,0 +1,681 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { addDays, subYears } from "date-fns"; +import { range } from "lodash"; + +import ColumnLayout from "@cloudscape-design/components/column-layout"; + +import { CartesianChart } from "../../lib/components"; +import { dateFormatter } from "../common/formatters"; +import { useChartSettings } from "../common/page-settings"; +import { Page, PageSection } from "../common/templates"; +import pseudoRandom from "../utils/pseudo-random"; + +export default function () { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +const defaultSettings = { + height: 400, + legend: { enabled: false }, +}; + +function LinearLinear() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i * 10, y: Math.floor((pseudoRandom() + i / 25) * 50) })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 250, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 75, + }, + ]} + xAxis={{ + title: "X values", + type: "linear", + min: 0, + max: 500, + }} + yAxis={{ + title: "Y values", + type: "linear", + min: 0, + max: 150, + }} + /> + ); +} + +function LinearLog() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i * 10, y: i * i })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 100, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 100, + }, + ]} + xAxis={{ + title: "X values", + type: "linear", + min: 0, + max: 500, + }} + yAxis={{ + title: "Y values", + type: "logarithmic", + }} + /> + ); +} + +function LogLinear() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i * i, y: i * 20 })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 100, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 500, + }, + ]} + xAxis={{ + title: "X values", + type: "logarithmic", + }} + yAxis={{ + title: "Y values", + type: "linear", + min: 0, + max: 1000, + }} + /> + ); +} + +function LogLog() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i * i, y: Math.pow(2, i / 2) })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 100, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 5000, + }, + ]} + xAxis={{ + title: "X values", + type: "logarithmic", + }} + yAxis={{ + title: "Y values", + type: "logarithmic", + }} + /> + ); +} + +function CategoryLinear() { + const { chartProps } = useChartSettings(); + return ( + Math.floor((pseudoRandom() + i / 25) * 50)), + }, + { + type: "x-threshold", + name: "X threshold", + value: 4, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 50, + }, + ]} + xAxis={{ + title: "X values", + type: "category", + categories: range(0, 10).map((i) => "ABCDEFGHIJ"[i]), + }} + yAxis={{ + title: "Y values", + type: "linear", + min: 0, + max: 100, + }} + /> + ); +} + +function LinearCategory() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i * 2, y: Math.floor(pseudoRandom() * 10) })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 50, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 4, + }, + ]} + xAxis={{ + title: "X values", + type: "linear", + min: 0, + max: 100, + }} + yAxis={{ + title: "Y values", + type: "category", + categories: range(0, 10).map((i) => "ABCDEFGHIJ"[i]), + }} + /> + ); +} + +function CategoryCategory() { + const { chartProps } = useChartSettings(); + return ( + ({ + type: "scatter" as const, + name: `Demo ${seriesIndex + 1}`, + data: range(0, 15).map((i) => ({ x: i, y: Math.floor(pseudoRandom() * 15) })), + })), + { + type: "x-threshold", + name: "X threshold", + value: 8, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 8, + }, + ]} + xAxis={{ + title: "X values", + type: "linear", + categories: range(0, 15).map((i) => "ABCDEFGHIJKLMNO"[i]), + }} + yAxis={{ + title: "Y values", + type: "category", + categories: range(0, 15).map((i) => "ABCDEFGHIJKLMNO"[i]), + }} + /> + ); +} + +function DatetimeLinear() { + const { chartProps } = useChartSettings(); + return ( + ({ + x: addDays(new Date(), i).getTime(), + y: Math.floor((pseudoRandom() + i / 25) * 50), + })), + }, + { + type: "x-threshold", + name: "X threshold", + value: addDays(new Date(), 25).getTime(), + }, + { + type: "y-threshold", + name: "Y threshold", + value: 75, + }, + ]} + xAxis={{ + title: "X values", + type: "datetime", + }} + yAxis={{ + title: "Y values", + type: "linear", + min: 0, + max: 150, + }} + /> + ); +} + +function LinearDatetime() { + const { chartProps } = useChartSettings(); + return ( + ({ + x: Math.floor((pseudoRandom() + i / 25) * 50), + y: addDays(new Date(), i).getTime(), + })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 75, + }, + { + type: "y-threshold", + name: "Y threshold", + value: addDays(new Date(), 25).getTime(), + }, + ]} + xAxis={{ + title: "X values", + type: "linear", + min: 0, + max: 150, + }} + yAxis={{ + title: "Y values", + type: "datetime", + }} + /> + ); +} + +function DatetimeDatetime() { + const { chartProps } = useChartSettings({ more: true }); + return ( + i % 2) + .flatMap((x) => + range(0, 10) + .filter((i) => !(i % 2)) + .map((y) => ({ + x: addDays(new Date(), x).getTime(), + y: subYears(addDays(new Date(), y), 1).getTime(), + })), + ), + }, + { + type: "x-threshold", + name: "X threshold", + value: addDays(new Date(), 5).getTime(), + }, + { + type: "y-threshold", + name: "Y threshold", + value: subYears(addDays(new Date(), 5), 1).getTime(), + }, + ]} + xAxis={{ + title: "X values", + type: "datetime", + valueFormatter: dateFormatter, + }} + yAxis={{ + title: "Y values", + type: "datetime", + valueFormatter: dateFormatter, + }} + /> + ); +} + +function LogCategory() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i * 5, y: (i - i * 0.2) % 5 })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 100, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 3, + }, + ]} + xAxis={{ + title: "X values", + type: "logarithmic", + }} + yAxis={{ + title: "Y values", + categories: ["A", "B", "C", "D", "E"], + }} + /> + ); +} + +function CategoryLog() { + const { chartProps } = useChartSettings(); + return ( + ({ x: i, y: 13 + i * 2 * (i + i) })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 3, + }, + { + type: "y-threshold", + name: "Y threshold", + value: 50, + }, + ]} + xAxis={{ + title: "X values", + categories: ["A", "B", "C", "D", "E"], + }} + yAxis={{ + title: "Y values", + type: "logarithmic", + }} + /> + ); +} + +function DatetimeLog() { + const { chartProps } = useChartSettings(); + return ( + ({ + x: addDays(new Date(), i).getTime(), + y: Math.floor((pseudoRandom() + i / 25) * 50), + })), + }, + { + type: "x-threshold", + name: "X threshold", + value: addDays(new Date(), 25).getTime(), + }, + { + type: "y-threshold", + name: "Y threshold", + value: 45, + }, + ]} + xAxis={{ + title: "X values", + type: "datetime", + }} + yAxis={{ + title: "Y values", + type: "logarithmic", + }} + /> + ); +} + +function LogDatetime() { + const { chartProps } = useChartSettings(); + return ( + ({ + x: Math.floor((pseudoRandom() + i / 25) * 50), + y: addDays(new Date(), i).getTime(), + })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 45, + }, + { + type: "y-threshold", + name: "Y threshold", + value: addDays(new Date(), 25).getTime(), + }, + ]} + xAxis={{ + title: "X values", + type: "logarithmic", + }} + yAxis={{ + title: "Y values", + type: "datetime", + }} + /> + ); +} + +function DatetimeCategory() { + const { chartProps } = useChartSettings(); + return ( + ({ x: addDays(new Date(), i).getTime(), y: Math.abs(i - 5) })), + }, + { + type: "x-threshold", + name: "X threshold", + value: addDays(new Date(), 5).getTime(), + }, + { + type: "y-threshold", + name: "Y threshold", + value: 3, + }, + ]} + xAxis={{ + title: "X values", + type: "datetime", + }} + yAxis={{ + title: "Y values", + categories: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], + }} + /> + ); +} + +function CategoryDatetime() { + const { chartProps } = useChartSettings(); + return ( + ({ x: Math.abs(i - 5), y: addDays(new Date(), i).getTime() })), + }, + { + type: "x-threshold", + name: "X threshold", + value: 3, + }, + { + type: "y-threshold", + name: "Y threshold", + value: addDays(new Date(), 5).getTime(), + }, + ]} + xAxis={{ + title: "X values", + categories: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], + }} + yAxis={{ + title: "Y values", + type: "datetime", + }} + /> + ); +} diff --git a/pages/01-cartesian-chart/chart-size.page.tsx b/pages/01-cartesian-chart/chart-size.page.tsx new file mode 100644 index 00000000..bed6924a --- /dev/null +++ b/pages/01-cartesian-chart/chart-size.page.tsx @@ -0,0 +1,103 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { range } from "lodash"; + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { FitSizeDemo, Page, PageSection } from "../common/templates"; +import pseudoRandom from "../utils/pseudo-random"; + +const splineSeries: CartesianChartProps.SeriesOptions[] = [ + { + type: "spline", + name: "Demo spline fifty", + data: range(0, 100).map((i) => ({ x: i * 10, y: Math.floor((pseudoRandom() + i / 25) * 50) })), + }, + { + type: "spline", + name: "Demo spline seventy-five", + data: range(0, 100).map((i) => ({ x: i * 10, y: Math.floor((pseudoRandom() + i / 25) * 75) })), + }, + { + type: "spline", + name: "Demo spline one hundred", + data: range(0, 100).map((i) => ({ x: i * 10, y: Math.floor((pseudoRandom() + i / 25) * 100) })), + }, + { + type: "spline", + name: "Demo spline one hundred twenty-five", + data: range(0, 100).map((i) => ({ x: i * 10, y: Math.floor((pseudoRandom() + i / 25) * 125) })), + }, + { + type: "spline", + name: "Demo spline one hundred fifty", + data: range(0, 100).map((i) => ({ x: i * 10, y: Math.floor((pseudoRandom() + i / 25) * 150) })), + }, +]; + +export default function () { + const { settings, chartProps } = useChartSettings(); + const commonChartProps: CartesianChartProps = { + ...chartProps.cartesian, + series: splineSeries, + xAxis: { + title: "X values", + type: "linear", + }, + yAxis: { + title: "Y values", + type: "linear", + }, + }; + return ( + + } + > + + + + + + + + + + + + + + + + + + + ); +} diff --git a/pages/01-cartesian-chart/controlled-visibility.page.tsx b/pages/01-cartesian-chart/controlled-visibility.page.tsx new file mode 100644 index 00000000..9e1753f9 --- /dev/null +++ b/pages/01-cartesian-chart/controlled-visibility.page.tsx @@ -0,0 +1,115 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Link from "@cloudscape-design/components/link"; + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { moneyFormatter, numberFormatter } from "../common/formatters"; +import { PageSettings, PageSettingsForm, SeriesFilter, useChartSettings } from "../common/page-settings"; +import { Page, PageSection } from "../common/templates"; + +interface ThisPageSettings extends PageSettings { + visibleItems: string; +} + +const mixedChartSeries: CartesianChartProps.SeriesOptions[] = [ + { + id: "Costs", + name: "Costs", + type: "column", + data: [6562, 8768, 9742, 10464, 16777, 9956, 5876], + }, + { + id: "Costs last year", + name: "Costs last year", + type: "line", + data: [5373, 7563, 7900, 12342, 14311, 11830, 8505], + }, + { + type: "x-threshold", + id: "Peak cost", + name: "Peak cost", + value: 3, + }, + { + type: "y-threshold", + id: "Budget", + name: "Budget", + value: 12000, + }, +]; + +const defaultVisibleItems = "Costs,Costs last year,Peak cost"; + +export default function () { + const { settings, setSettings } = useChartSettings(); + const visibleSeries = (settings.visibleItems ?? defaultVisibleItems).split(","); + return ( + setSettings({ visibleItems: visibleSeries.join(",") })} + /> + ), + }, + ]} + /> + } + > + + + + + ); +} + +function ExampleMixedChart() { + const { settings, setSettings, chartProps } = useChartSettings(); + const visibleSeries = (settings.visibleItems ?? defaultVisibleItems).split(","); + return ( + { + return { + key: item.series.name, + value: ( + + {item.y !== null ? moneyFormatter(item.y) : null} + + ), + }; + }, + }} + xAxis={{ + type: "category", + title: "Budget month", + categories: ["Jun 2019", "Jul 2019", "Aug 2019", "Sep 2019", "Oct 2019", "Nov 2019", "Dec 2019"], + min: 0, + max: 6, + }} + yAxis={{ + title: "Costs (USD)", + min: 0, + max: 20000, + valueFormatter: numberFormatter, + }} + visibleSeries={visibleSeries} + onChangeVisibleSeries={({ detail: { visibleSeries } }) => setSettings({ visibleItems: visibleSeries.join(",") })} + emphasizeBaseline={settings.emphasizeBaseline} + /> + ); +} diff --git a/pages/01-cartesian-chart/error-bars.page.tsx b/pages/01-cartesian-chart/error-bars.page.tsx new file mode 100644 index 00000000..fe8185e5 --- /dev/null +++ b/pages/01-cartesian-chart/error-bars.page.tsx @@ -0,0 +1,336 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Box from "@cloudscape-design/components/box"; +import Checkbox from "@cloudscape-design/components/checkbox"; +import ColumnLayout from "@cloudscape-design/components/column-layout"; +import FormField from "@cloudscape-design/components/form-field"; +import Input from "@cloudscape-design/components/input"; +import Link from "@cloudscape-design/components/link"; +import SpaceBetween from "@cloudscape-design/components/space-between"; + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { moneyFormatter, numberFormatter } from "../common/formatters"; +import { PageSettings, useChartSettings } from "../common/page-settings"; +import { Page, PageSection } from "../common/templates"; +import pseudoRandom from "../utils/pseudo-random"; + +const CHART_HEIGHT = 400; + +interface ThisPageSettings extends PageSettings { + customTooltipContent: boolean; + errorsGroupedInFooter: boolean; + errorColor?: string; + errorSize: number; + inverted?: boolean; + tooltipSeriesSubItems: boolean; + tooltipSeriesSubItemsExpandable: boolean; +} + +interface ChartProps { + inverted: boolean; + errorSize: number; + errorColor?: string; + customTooltipContent: boolean; +} + +function errorRange(value: number, delta: number) { + return { low: value - delta, high: value + delta }; +} + +const categories = ["Jun 2019", "Jul 2019", "Aug 2019", "Sep 2019", "Oct 2019", "Nov 2019", "Dec 2019"]; +const costsData = [6562, 8768, 9742, 10464, 16777, 9956, 5876]; +const costsErrorData = (size: number) => costsData.map((value) => ({ ...errorRange(value, size / 2) })); +const costsLastYearData = [5373, 7563, 7900, 12342, 14311, 11830, 8505]; +const costsLastYearErrorData = (size: number) => costsLastYearData.map((value) => ({ ...errorRange(value, size / 2) })); + +const costsSeries = { id: "c", name: "Costs", type: "column", data: costsData } as const; +const costsLastYearSeries = { id: "c-1", name: "Costs last year", type: "spline", data: costsLastYearData } as const; + +function getCostsErrorSeries({ errorSize, errorColor }: { errorSize: number; errorColor?: string }) { + return { + linkedTo: "c", + type: "errorbar", + name: "Error range", + color: errorColor, + data: costsErrorData(errorSize), + } as const; +} + +function getLastYearCostsSeries({ errorSize, errorColor }: { errorSize: number; errorColor?: string }) { + return { + linkedTo: "c-1", + type: "errorbar", + name: "Error range", + color: errorColor, + data: costsLastYearErrorData(errorSize), + } as const; +} + +function getCostSeriesWithError({ + type = "column", + errorSize, + errorColor, +}: { + errorSize: number; + errorColor?: string; + type?: "column"; +}) { + return [{ ...costsSeries, type }, getCostsErrorSeries({ errorColor, errorSize })]; +} + +function getLastYearCostsSeriesWithError({ + type = "spline", + errorSize, + errorColor, +}: { + errorSize: number; + errorColor?: string; + type?: "column" | "spline"; +}) { + return [{ ...costsLastYearSeries, type }, getLastYearCostsSeries({ errorColor, errorSize })]; +} + +export default function () { + const { settings, setSettings } = useChartSettings(); + const chartProps = { + errorSize: settings.errorSize ?? 800, + customTooltipContent: settings.customTooltipContent ?? false, + errorColor: settings.errorColor || undefined, + errorsGroupedInFooter: settings.errorsGroupedInFooter ?? false, + inverted: settings.inverted ?? false, + tooltipSeriesSubItems: settings.tooltipSeriesSubItems ?? false, + tooltipSeriesSubItemsExpandable: settings.tooltipSeriesSubItemsExpandable ?? false, + }; + return ( + + setSettings({ customTooltipContent: detail.checked })} + > + Custom tooltip content + + {chartProps.customTooltipContent && ( + <> + setSettings({ errorsGroupedInFooter: detail.checked })} + > + Group error ranges in footer + + setSettings({ tooltipSeriesSubItems: detail.checked })} + > + Sub-items + + {chartProps.tooltipSeriesSubItems && ( + setSettings({ tooltipSeriesSubItemsExpandable: detail.checked })} + > + Expandable + + )} + + )} + setSettings({ inverted: detail.checked })}> + Invert chart + + + setSettings({ errorSize: parseInt(detail.value) })} + /> + + + setSettings({ errorColor: detail.value })} + /> + + + } + > + + + + + + + + + + + + + + + + + + + + + + ); +} + +function tooltipContent(settings: ThisPageSettings): CartesianChartProps.TooltipOptions | undefined { + return settings.customTooltipContent + ? { + point: ({ item }) => ({ + key: item.series.name, + value: ( + + {item.y !== null ? moneyFormatter(item.y) : null} + + ), + subItems: settings.tooltipSeriesSubItems + ? [ + { key: "sub-item 1", value: (item.y ?? 0) / 2 }, + { key: "sub-item 2", value: (item.y ?? 0) / 2 }, + ] + : undefined, + expandable: settings.tooltipSeriesSubItemsExpandable, + description: + item.errorRanges.length && !settings.errorsGroupedInFooter ? ( +
+ {item.errorRanges.map((errorRange, index) => ( +
+ {moneyFormatter(errorRange.low)} - {moneyFormatter(errorRange.high)}* +
+ ))} +
+ ) : null, + }), + footer: ({ items }) => + items.some((item) => item.errorRanges.length > 0) ? ( + + {settings.errorsGroupedInFooter + ? `Error range: ±${moneyFormatter(items[0].errorRanges[0].high - items[0].errorRanges[0].low)}` + : "*Error range"} + + ) : null, + } + : undefined; +} + +function ColumnChart({ inverted, errorSize, errorColor }: ChartProps) { + const { chartProps, settings } = useChartSettings({ more: true }); + return ( + + ); +} + +function MixedChart({ inverted, errorSize, errorColor }: ChartProps) { + const { chartProps, settings } = useChartSettings({ more: true }); + return ( + + ); +} + +function GroupedColumnChart({ inverted, errorSize, errorColor }: ChartProps) { + const { chartProps, settings } = useChartSettings({ more: true }); + return ( + + ); +} + +function LineChart({ inverted, errorSize, errorColor, numberOfSeries = 3 }: ChartProps & { numberOfSeries?: number }) { + const { chartProps, settings } = useChartSettings({ more: true }); + const createSeries = (index: number) => { + const data = costsData.map((y) => y + Math.floor(pseudoRandom() * 10000) - 5000); + const lineSeries: CartesianChartProps.LineSeriesOptions = { + id: `c-${index}`, + name: `Costs ${index + 1}`, + type: "line", + data: data, + }; + const errorSeries: CartesianChartProps.ErrorBarSeriesOptions = { + linkedTo: `c-${index}`, + type: "errorbar", + name: "Error range", + color: errorColor, + data: data.map((value) => ({ ...errorRange(value, errorSize / 2) })), + }; + return [lineSeries, errorSeries]; + }; + return ( + createSeries(index)) + .flatMap((s) => s), + ]} + tooltip={tooltipContent(settings)} + xAxis={{ type: "category", title: "Budget month", categories }} + yAxis={{ title: "Costs (USD)", valueFormatter: numberFormatter }} + /> + ); +} + +function MultipleErrorBars({ inverted, errorSize }: ChartProps) { + const { chartProps, settings } = useChartSettings({ more: true }); + return ( + + ); +} diff --git a/pages/01-cartesian-chart/large-series.page.tsx b/pages/01-cartesian-chart/large-series.page.tsx new file mode 100644 index 00000000..507b1e14 --- /dev/null +++ b/pages/01-cartesian-chart/large-series.page.tsx @@ -0,0 +1,72 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { dateFormatter } from "../common/formatters"; +import { PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { Page } from "../common/templates"; +import pseudoRandom from "../utils/pseudo-random"; + +export default function () { + return ( + } + > + + + ); +} + +const domain: number[] = []; +for ( + let time = new Date("2015-01-01").getTime(); + time < new Date("2025-01-01").getTime(); + time += 12 * 60 * 60 * 1000 +) { + domain.push(time); +} + +const series: CartesianChartProps.SeriesOptions[] = [ + { + name: "Site 1", + type: "areaspline", + data: domain.map((x, index) => ({ + x, + y: Math.round(1000 + pseudoRandom() * 5000 + pseudoRandom() * 10000 + pseudoRandom() * 10 * index), + })), + }, + { + name: "Site 2", + type: "areaspline", + data: domain.map((x, index) => ({ + x, + y: Math.round(1000 + pseudoRandom() * 5000 + pseudoRandom() * 10000 + pseudoRandom() * 20 * index), + })), + }, +]; + +function Component() { + const { chartProps } = useChartSettings(); + return ( + + ); +} diff --git a/pages/01-cartesian-chart/many-series.page.tsx b/pages/01-cartesian-chart/many-series.page.tsx new file mode 100644 index 00000000..dc9fad10 --- /dev/null +++ b/pages/01-cartesian-chart/many-series.page.tsx @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { dateFormatter } from "../common/formatters"; +import { PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { Page, PageSection } from "../common/templates"; +import pseudoRandom from "../utils/pseudo-random"; + +const loremIpsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris congue aliquet feugiat. Integer gravida efficitur elementum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur et nisi iaculis diam laoreet pharetra. Phasellus nec nibh dapibus, feugiat leo sodales, feugiat mi. Vivamus a orci mattis, fringilla nisl et, pretium metus. Nunc leo nisi, blandit et sodales ut, sodales id leo. Vestibulum consectetur ante et sapien pretium vestibulum. Donec cursus arcu vitae nunc convallis molestie. Nunc est elit, maximus eu odio eget, commodo fermentum felis. Nullam sit amet sem lectus. Quisque eu rhoncus libero, nec lacinia ipsum. Mauris libero nulla, placerat vitae nisi et, luctus placerat turpis. Donec vitae faucibus neque, eu accumsan enim. Cras nec arcu lacus. Aenean vehicula, mauris in vestibulum maximus, sem nisi rutrum mi, in lobortis lorem elit quis est."; + +export default function () { + return ( + + } + > + + + + + ); +} + +const domain = [ + new Date(1600984800000), + new Date(1600985700000), + new Date(1600986600000), + new Date(1600987500000), + new Date(1600988400000), + new Date(1600989300000), + new Date(1600990200000), + new Date(1600991100000), + new Date(1600992000000), + new Date(1600992900000), + new Date(1600993800000), + new Date(1600994700000), + new Date(1600995600000), + new Date(1600996500000), + new Date(1600997400000), + new Date(1600998300000), + new Date(1600999200000), + new Date(1601000100000), + new Date(1601001000000), + new Date(1601001900000), + new Date(1601002800000), + new Date(1601003700000), + new Date(1601004600000), + new Date(1601005500000), + new Date(1601006400000), + new Date(1601007300000), + new Date(1601008200000), + new Date(1601009100000), + new Date(1601010000000), + new Date(1601010900000), + new Date(1601011800000), + new Date(1601012700000), + new Date(1601013600000), +]; +const site1Data = [ + 58020, 102402, 104920, 94031, 125021, 159219, 193082, 162592, 274021, 264286, 289210, 256362, 257306, 186776, 294020, + 385975, 486039, 490447, 361845, 339058, 298028, 231902, 224558, 253901, 102839, 234943, 204405, 190391, 183570, + 162592, 148910, 229492, 293910, +].map((v) => v * 0.9); +const site2Data = [ + 151023, 169975, 176980, 168852, 149130, 147299, 169552, 163401, 154091, 199516, 195503, 189953, 181635, 192975, + 205951, 218958, 220516, 213557, 165899, 173557, 172331, 186492, 131541, 142262, 194091, 185899, 173401, 171635, + 179130, 185951, 144091, 152975, 157299, +].map((v) => v * 0.9); + +const series: CartesianChartProps.SeriesOptions[] = []; +for (let index = 0; index < 20; index++) { + const data = index < 10 ? site1Data : site2Data; + series.push({ + name: loremIpsum + .slice(index * 20, (index + 1) * 20) + .replace(/[\W]+/g, "-") + .replace(/(^-)|(-$)/g, "") + .toLowerCase(), + type: "areaspline", + data: data.map((y, index) => ({ + x: domain[index].getTime(), + y: y + Math.round((pseudoRandom() - 0.5) * (index + 1) * 5000), + })), + }); +} +series.push({ type: "x-threshold", name: "X Threshold", value: 1601000100000 }); +series.push({ type: "y-threshold", name: "Y Threshold", value: 1_500_000 }); + +function Component() { + const { chartProps } = useChartSettings(); + return ( + + ); +} diff --git a/pages/01-cartesian-chart/no-data-states.page.tsx b/pages/01-cartesian-chart/no-data-states.page.tsx new file mode 100644 index 00000000..72579714 --- /dev/null +++ b/pages/01-cartesian-chart/no-data-states.page.tsx @@ -0,0 +1,240 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Alert from "@cloudscape-design/components/alert"; +import Box from "@cloudscape-design/components/box"; +import Button from "@cloudscape-design/components/button"; +import SpaceBetween from "@cloudscape-design/components/space-between"; +import StatusIndicator from "@cloudscape-design/components/status-indicator"; + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { percentageFormatter } from "../common/formatters"; +import { PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { FramedDemo, Page, PageSection } from "../common/templates"; + +export default function () { + const { settings, chartProps } = useChartSettings(); + + const defaultProps: CartesianChartProps = { + ...chartProps.cartesian, + series: [], + chartHeight: settings.height, + xAxis: { title: "X axis", min: 0, max: 1000 }, + yAxis: { title: "Y axis", min: 0, max: 1, valueFormatter: percentageFormatter }, + }; + + return ( + } + > + + + + + No data available + + There is no available data to display + + + + ), + }} + /> + + + + + + + + No data available + + There is no available data to display + + + + ), + }} + visibleSeries={["Load"]} + /> + + + + + + + + No matching data + + There is no matching data to display + + + + + ), + }} + visibleSeries={[]} + /> + + + + + + Loading data..., + }} + /> + + + + + + Loading data..., + }} + visibleSeries={["Load"]} + /> + + + + +
Series and series data are partially available or are not up to date.
+
Note: this is achieved by indicating the loading state outside of the chart component.
+ + } + > + + Refreshing data + + + + +
+ + + + An error occurred, + }} + /> + + + + + + An error occurred, + }} + /> + + + + +
Some series or data failed to load or refresh.
+
Note: this is achieved by indicating the error state outside of the chart component.
+ + } + > + + An error occurred + + + + +
+
+ ); +} diff --git a/pages/01-cartesian-chart/scatter-chart.page.tsx b/pages/01-cartesian-chart/scatter-chart.page.tsx new file mode 100644 index 00000000..87f0161f --- /dev/null +++ b/pages/01-cartesian-chart/scatter-chart.page.tsx @@ -0,0 +1,339 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + colorChartsPaletteCategorical1, + colorChartsPaletteCategorical2, + colorChartsPaletteCategorical3, + colorTextBodyDefault, +} from "@cloudscape-design/design-tokens"; + +import { CartesianChart, CartesianChartProps } from "../../lib/components"; +import { dateFormatter } from "../common/formatters"; +import { PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { Page, PageSection } from "../common/templates"; +import pseudoRandom from "../utils/pseudo-random"; + +export default function () { + return ( + + } + > + + + ); +} + +function randomInt(min: number, max: number) { + return min + Math.floor(pseudoRandom() * (max - min)); +} + +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 }, + { x: 1601012700000, y: 229492 }, + { x: 1601013600000, y: 293910 }, +]; + +const randX = (x: number) => x - randomInt(0, 900000); + +const series: CartesianChartProps.SeriesOptions[] = [ + { + name: "A", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "A" + i, x: randX(x), y })), + }, + { + name: "B", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "B" + i, x: randX(x), y: y + randomInt(-100000, 100000) })), + }, + { + name: "C", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "C" + i, x: randX(x), y: y + randomInt(-150000, 50000) })), + }, + { + name: "D", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "D" + i, x: randX(x), y: y + randomInt(-200000, -100000) })), + }, + { + name: "E", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "E" + i, x: randX(x), y: y + randomInt(50000, 75000) })), + }, + { + name: "Threshold", + type: "x-threshold", + value: 1600993000000, + }, +]; + +const matchingSeries: CartesianChartProps.ScatterSeriesOptions[] = [ + { + name: "A", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "A" + i, x, y })), + }, + { + name: "B", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "B" + i, x, y: y + randomInt(-100000, 100000) })), + }, + { + name: "C", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "C" + i, x, y: y + randomInt(-150000, 50000) })), + }, + { + name: "D", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "D" + i, x, y: y + randomInt(-200000, -100000) })), + }, + { + name: "E", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "E" + i, x, y: y + randomInt(50000, 75000) })), + }, +]; + +const seriesWithCustomMarkers: CartesianChartProps.SeriesOptions[] = [ + { + name: "A", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "A" + i, x: randX(x), y })), + color: "#ed1b76", + marker: { symbol: "triangle" }, + }, + { + name: "B", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "B" + i, x: randX(x), y: y + randomInt(-100000, 100000) })), + color: "#249f9c", + marker: { symbol: "circle" }, + }, + { + name: "C", + type: "scatter", + data: baseline.map(({ x, y }, i) => ({ name: "C" + i, x: randX(x), y: y + randomInt(-150000, 50000) })), + color: colorTextBodyDefault, + marker: { symbol: "square" }, + }, +]; + +const dataA = [ + { x: baseline[0].x, y: 1000 }, + { x: baseline[1].x, y: 1400 }, + { x: baseline[2].x, y: 1800 }, + { x: baseline[3].x, y: 1600 }, + { x: baseline[4].x, y: 1200 }, + { x: baseline[5].x, y: 1730 }, + { x: baseline[6].x, y: 1800 }, + { x: baseline[7].x, y: 1820 }, + { x: baseline[8].x, y: 1900 }, + { x: baseline[9].x, y: 2020 }, + { x: baseline[10].x, y: 2000 }, + { x: baseline[10].x, y: 2000 }, + { x: baseline[11].x, y: 2050 }, + { x: baseline[11].x, y: 2025 }, + { x: baseline[12].x, y: 2080 }, + { x: baseline[13].x, y: 2200 }, + { x: baseline[14].x, y: 2160 }, +]; +const dataB = [ + { x: dataA[0].x + 500_000, y: dataA[0].y + 500 }, + { x: dataA[1].x + 450_000, y: dataA[1].y + 480 }, + { x: dataA[2].x + 420_000, y: dataA[2].y + 440 }, + { x: dataA[3].x + 390_000, y: dataA[3].y + 410 }, + { x: dataA[4].x + 320_000, y: dataA[4].y + 340 }, + { x: dataA[5].x + 360_000, y: dataA[5].y + 300 }, + { x: dataA[6].x + 310_000, y: dataA[6].y + 260 }, + { x: dataA[7].x + 255_000, y: dataA[7].y + 230 }, + { x: dataA[8].x + 205_000, y: dataA[8].y + 180 }, + { x: dataA[9].x + 140_000, y: dataA[9].y + 140 }, + { x: dataA[10].x + 100_000, y: dataA[10].y + 0 }, + { x: dataA[11].x + 80_000, y: dataA[11].y + 50 }, + { x: dataA[12].x + 0, y: dataA[12].y + 20 }, + { x: dataA[13].x + 0, y: dataA[13].y + 0 }, + { x: dataA[14].x - 1_000, y: dataA[14].y + 10 }, +]; +const dataC = [ + { x: dataA[6].x + 150_000, y: dataA[6].y - 300 }, + { x: dataA[7].x + 150_000, y: dataA[7].y - 300 }, + { x: dataA[7].x + 150_000, y: dataA[7].y - 300 }, + { x: dataA[8].x + 150_000, y: dataA[8].y - 300 }, +]; +const seriesWithDuplicatePoints: CartesianChartProps.SeriesOptions[] = [ + { + name: "A", + type: "scatter", + data: dataA, + color: colorChartsPaletteCategorical1, + }, + { + name: "B", + type: "scatter", + data: dataB, + color: colorChartsPaletteCategorical2, + }, + { + name: "C", + type: "scatter", + data: dataC, + color: colorChartsPaletteCategorical3, + }, + { + name: "A trend", + type: "line", + data: computeTrendLine(dataA), + color: colorChartsPaletteCategorical1, + }, + { + name: "B trend", + type: "line", + data: computeTrendLine(dataB), + color: colorChartsPaletteCategorical2, + }, + { + name: "C trend", + type: "line", + data: computeTrendLine(dataC), + color: colorChartsPaletteCategorical3, + }, +]; + +const largeDomain: number[] = []; +for ( + let time = new Date("2015-01-01").getTime(); + time < new Date("2025-01-01").getTime(); + time += 48 * 60 * 60 * 1000 +) { + largeDomain.push(time); +} +const largeDataA = largeDomain.map((x, index) => ({ + x, + y: Math.round(1000 + pseudoRandom() * 5000 + pseudoRandom() * 10000 + pseudoRandom() * 10 * index), +})); +const largeDataB = largeDomain + .slice(Math.round(largeDomain.length * 0.3), Math.round(largeDomain.length * 0.7)) + .map((x, index) => ({ + x, + y: Math.round(1000 + pseudoRandom() * 5000 + pseudoRandom() * 10000 + pseudoRandom() * 10 * index), + })); +const largeSeries: CartesianChartProps.SeriesOptions[] = [ + { + name: "A", + type: "scatter", + data: largeDataA, + color: `rgba(0,205,0,0.6)`, + }, + { + name: "B", + type: "scatter", + data: largeDataB, + color: `rgba(0,155,155,0.6)`, + }, + { + name: "A trend", + type: "spline", + data: computeTrendLine(largeDataA), + color: `rgba(0,125,0,1.0)`, + }, + { + name: "B trend", + type: "spline", + data: computeTrendLine(largeDataB), + color: `rgba(0,105,105,1.0)`, + }, +]; + +function ExampleScatterSimple() { + const { chartProps } = useChartSettings(); + const commonProps = { + ...chartProps.cartesian, + chartHeight: 400, + ariaLabel: "Scatter chart", + xAxis: { type: "datetime", title: "Time (UTC)", valueFormatter: dateFormatter }, + yAxis: { title: "Events" }, + emphasizeBaseline: false, + } as const; + return ( + <> + + + + + + + + + + + + + + + + + + + + + ); +} + +function computeTrendLine(data: { x: number; y: number }[]): { x: number; y: number }[] { + const n = data.length; + const sumX = data.reduce((sum, p) => sum + p.x, 0); + const sumY = data.reduce((sum, p) => sum + p.y, 0); + const sumXY = data.reduce((sum, p) => sum + p.x * p.y, 0); + const sumX2 = data.reduce((sum, p) => sum + p.x * p.x, 0); + + const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + const intercept = (sumY - slope * sumX) / n; + + const xMin = Math.min(...data.map((p) => p.x)); + const xMax = Math.max(...data.map((p) => p.x)); + + return [ + { x: xMin, y: slope * xMin + intercept }, + { x: xMax, y: slope * xMax + intercept }, + ]; +} diff --git a/pages/02-pie-chart/chart-size.page.tsx b/pages/02-pie-chart/chart-size.page.tsx new file mode 100644 index 00000000..749f4d03 --- /dev/null +++ b/pages/02-pie-chart/chart-size.page.tsx @@ -0,0 +1,88 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { PieChart, PieChartProps } from "../../lib/components"; +import { PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { FitSizeDemo, Page, PageSection } from "../common/templates"; + +const pieSeries: PieChartProps.SeriesOptions = { + name: "Resource count", + type: "pie", + data: [ + { + name: "Running", + y: 60, + }, + { + name: "Failed", + y: 30, + }, + { + name: "In-progress", + y: 10, + }, + { + name: "Pending", + y: null, + }, + ], +}; + +export default function () { + const { settings, chartProps } = useChartSettings(); + const commonChartProps: PieChartProps = { + ...chartProps.pie, + series: pieSeries, + segmentDescription: ({ segmentValue, totalValue }) => + `${segmentValue} units, ${((segmentValue / totalValue) * 100).toFixed(0)}%`, + }; + return ( + + } + > + + + + + + + + + + + + + + + + + + + ); +} diff --git a/pages/02-pie-chart/controlled-visibility.page.tsx b/pages/02-pie-chart/controlled-visibility.page.tsx new file mode 100644 index 00000000..1d7d7efb --- /dev/null +++ b/pages/02-pie-chart/controlled-visibility.page.tsx @@ -0,0 +1,84 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { sum } from "lodash"; + +import { PieChart, PieChartProps } from "../../lib/components"; +import { PageSettings, PageSettingsForm, SeriesFilter, useChartSettings } from "../common/page-settings"; +import { Page, PageSection } from "../common/templates"; + +interface ThisPageSettings extends PageSettings { + visibleItems: string; +} + +const pieChartSeries: PieChartProps.SeriesOptions = { + name: "Value", + type: "pie", + data: [ + { id: "Costs", name: "Costs", y: sum([6562, 8768, 9742, 10464, 16777, 9956, 5876]) }, + { id: "Costs last year", name: "Costs last year", y: sum([5373, 7563, 7900, 12342, 14311, 11830, 8505]) }, + { id: "Budget", name: "Budget", y: 12000 * 7 }, + ], +}; + +const defaultVisibleItems = "Costs,Costs last year,Peak cost"; + +export default function () { + const { settings, setSettings } = useChartSettings(); + const visibleSeries = (settings.visibleItems ?? defaultVisibleItems).split(","); + return ( + setSettings({ visibleItems: visibleSeries.join(",") })} + /> + ), + }, + ]} + /> + } + > + + + + + ); +} + +function ExamplePieChart() { + const { settings, setSettings, chartProps } = useChartSettings(); + const visibleSegments = (settings.visibleItems ?? defaultVisibleItems).split(","); + return ( + + `${segmentValue} units, ${((segmentValue / totalValue) * 100).toFixed(0)}%` + } + visibleSegments={visibleSegments} + onChangeVisibleSegments={({ detail: { visibleSegments } }) => + setSettings({ visibleItems: visibleSegments.join(",") }) + } + /> + ); +} diff --git a/pages/02-pie-chart/no-data-states.page.tsx b/pages/02-pie-chart/no-data-states.page.tsx new file mode 100644 index 00000000..3c2f4c18 --- /dev/null +++ b/pages/02-pie-chart/no-data-states.page.tsx @@ -0,0 +1,263 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Alert from "@cloudscape-design/components/alert"; +import Box from "@cloudscape-design/components/box"; +import Button from "@cloudscape-design/components/button"; +import FormField from "@cloudscape-design/components/form-field"; +import Select from "@cloudscape-design/components/select"; +import SpaceBetween from "@cloudscape-design/components/space-between"; +import StatusIndicator from "@cloudscape-design/components/status-indicator"; + +import { PieChart, PieChartProps } from "../../lib/components"; +import { PageSettings, PageSettingsForm, useChartSettings } from "../common/page-settings"; +import { FramedDemo, Page, PageSection } from "../common/templates"; + +interface ThisPageSettings extends PageSettings { + chartType: "pie" | "donut"; +} + +const chartTypeOptions = [{ value: "pie" }, { value: "donut" }]; + +export default function () { + const { settings, setSettings, chartProps } = useChartSettings(); + const { chartType = "pie" } = settings; + const defaultProps: PieChartProps = { ...chartProps.pie, series: null, chartHeight: settings.height }; + return ( + + chartType === o.value) ?? chartTypeOptions[0]} + onChange={({ detail }) => + setSettings({ + chartType: detail.selectedOption.value as ThisPageSettings["chartType"], + }) + } + /> + + ), + }, + { + content: ( + + + + ), + } + : undefined + } + tooltip={{ placement: "middle" }} + visibleSeries={visibleSeries} + onChangeVisibleSeries={({ detail }) => setVisibleSeries(detail.visibleSeries)} + /> + ); +} + +export function ComponentOld({ hideFilter = false }: { hideFilter?: boolean }) { + const { chartProps } = useChartSettings(); + return ( + + + + ), + } + : undefined + } + legend={{ + ...chartProps.pie.legend, + actions: legendFilter ? ( + +