From 614e085e1c77a25369b4c49fc8ab99567d662e9a Mon Sep 17 00:00:00 2001 From: Andrei Zhaleznichenka Date: Fri, 24 Apr 2026 12:11:50 +0200 Subject: [PATCH] chore: Delayed measure experiment --- src/core/chart-api/index.tsx | 14 +++++++++++++- src/core/chart-container.tsx | 31 ++++++++++++++++++++++++------- src/core/chart-core.tsx | 1 + 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/core/chart-api/index.tsx b/src/core/chart-api/index.tsx index 294afa2e..807a5f52 100644 --- a/src/core/chart-api/index.tsx +++ b/src/core/chart-api/index.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef } from "react"; import type Highcharts from "highcharts"; import { fireNonCancelableEvent } from "../../internal/events"; -import { ReadonlyAsyncStore } from "../../internal/utils/async-store"; +import AsyncStore, { ReadonlyAsyncStore } from "../../internal/utils/async-store"; import { getChartSeries } from "../../internal/utils/chart-series"; import { getSeriesData } from "../../internal/utils/series-data"; import { Writeable } from "../../internal/utils/utils"; @@ -65,6 +65,8 @@ export function useChartAPI( // Run cleanup code when the component unmounts. useEffect(() => () => api.onChartDestroy(), [api]); + api.startRendering(); + return api; } @@ -82,6 +84,7 @@ export class ChartAPI { private chartExtraLegend = new ChartExtraLegend(this.context); private chartExtraNodata = new ChartExtraNodata(this.context); private chartExtraAxisTitles = new ChartExtraAxisTitles(this.context); + private renderingReadyStore = new AsyncStore(false); constructor( settings: ChartExtraContext.Settings, @@ -99,6 +102,14 @@ export class ChartAPI { return !!this.context.chartOrNull; } + public get renderingReady() { + return this.renderingReadyStore as ReadonlyAsyncStore; + } + + public startRendering() { + this.renderingReadyStore.set(() => false); + } + // The Highcharts options to be merged with the rest of the configuration defined in the chart-core. public getOptions() { // eslint-disable-next-line @typescript-eslint/no-this-alias @@ -116,6 +127,7 @@ export class ChartAPI { chartAPI.handleDestroyedPoints(); chartAPI.resetColorCounter(); chartAPI.showMarkersForIsolatedPoints(); + chartAPI.renderingReadyStore.set(() => true); }; const onChartClick: Highcharts.ChartClickCallbackFunction = function () { chartAPI.chartExtraPointer.onChartClick(this.hoverPoint); diff --git a/src/core/chart-container.tsx b/src/core/chart-container.tsx index 08e6a5cc..48bd61c4 100644 --- a/src/core/chart-container.tsx +++ b/src/core/chart-container.tsx @@ -1,12 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import clsx from "clsx"; -import { useResizeObserver } from "@cloudscape-design/component-toolkit/internal"; +import { useResizeObserver, useStableCallback } from "@cloudscape-design/component-toolkit/internal"; import * as Styles from "../internal/chart-styles"; +import { ReadonlyAsyncStore } from "../internal/utils/async-store"; import { DebouncedCall } from "../internal/utils/utils"; import styles from "./styles.css.js"; @@ -38,6 +39,7 @@ interface ChartContainerProps { chartHeight?: number; chartMinHeight?: number; chartMinWidth?: number; + renderingReady: ReadonlyAsyncStore; } export function ChartContainer({ @@ -57,8 +59,9 @@ export function ChartContainer({ chartHeight, chartMinHeight, chartMinWidth, + renderingReady, }: ChartContainerProps) { - const { refs, measures } = useContainerQueries(); + const { refs, measures } = useContainerQueries(renderingReady); // The vertical axis title is rendered above the chart, and is technically not a part of the chart plot. // However, we want to include it to the chart's height computations as it does belong to the chart logically. @@ -125,15 +128,29 @@ export function ChartContainer({ ); } +interface MeasuresState { + chart: number; + header: number; + footer: number; +} + // This hook combines 3 resize observer and does a small optimization to batch their updates in a single set-state. -function useContainerQueries() { - const [measuresState, setMeasuresState] = useState({ ready: false, chart: 0, header: 0, footer: 0 }); - const measuresRef = useRef({ ready: false, chart: 0, header: 0, footer: 0 }); +function useContainerQueries(renderingReady: ReadonlyAsyncStore) { + const [measuresState, setMeasuresState] = useState({ chart: 0, header: 0, footer: 0 }); + const measuresRef = useRef({ chart: 0, header: 0, footer: 0 }); const measureDebounce = useRef(new DebouncedCall()).current; + const queueMeasure = useStableCallback(() => + measureDebounce.call(() => { + if (renderingReady.get()) { + setMeasuresState({ ...measuresRef.current }); + } + }, 0), + ); const setMeasure = (type: "chart" | "header" | "footer", value: number) => { measuresRef.current[type] = value; - measureDebounce.call(() => setMeasuresState({ ...measuresRef.current }), 0); + queueMeasure(); }; + useEffect(() => renderingReady.subscribe((state) => state, queueMeasure), [renderingReady, queueMeasure]); const chartMeasureRef = useRef(null); const getChart = useCallback(() => chartMeasureRef.current, []); diff --git a/src/core/chart-core.tsx b/src/core/chart-core.tsx index adf28c27..4a345361 100644 --- a/src/core/chart-core.tsx +++ b/src/core/chart-core.tsx @@ -106,6 +106,7 @@ export function InternalCoreChart({ legendPosition, verticalAxisTitlePlacement, legendBottomMaxHeight: legendOptions?.bottomMaxHeight, + renderingReady: api.renderingReady, }; // AWSUI-61678