diff --git a/studio/frontend/src/components/ui/chart.tsx b/studio/frontend/src/components/ui/chart.tsx index 071148982f..427cede136 100644 --- a/studio/frontend/src/components/ui/chart.tsx +++ b/studio/frontend/src/components/ui/chart.tsx @@ -46,22 +46,94 @@ function ChartContainer({ }) { const uniqueId = React.useId(); const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + const containerRef = React.useRef(null); + const [containerSize, setContainerSize] = React.useState<{ + width: number; + height: number; + } | null>(null); + + React.useEffect(() => { + const element = containerRef.current; + if (!element) return; + + const updateSizeState = () => { + const { width, height } = element.getBoundingClientRect(); + const nextSize = + width > 0 && height > 0 + ? { + width: Math.round(width), + height: Math.round(height), + } + : null; + + setContainerSize((currentSize) => { + if (!nextSize) { + // Keep the last valid size once mounted to avoid unmount/remount thrash. + return currentSize; + } + if ( + currentSize && + currentSize.width === nextSize.width && + currentSize.height === nextSize.height + ) { + return currentSize; + } + return nextSize; + }); + }; + + updateSizeState(); + + if (typeof ResizeObserver === "undefined") { + const recheckSize = () => { + if (document.visibilityState === "visible") { + updateSizeState(); + } + }; + + window.addEventListener("resize", recheckSize); + window.addEventListener("orientationchange", recheckSize); + document.addEventListener("visibilitychange", recheckSize); + + return () => { + window.removeEventListener("resize", recheckSize); + window.removeEventListener("orientationchange", recheckSize); + document.removeEventListener("visibilitychange", recheckSize); + }; + } + + const observer = new ResizeObserver(() => { + updateSizeState(); + }); + observer.observe(element); + + return () => observer.disconnect(); + }, []); return (
- - {children} - + {containerSize ? ( + + {children} + + ) : null}
); @@ -100,30 +172,30 @@ ${colorConfig ); }; -const ChartTooltip = RechartsPrimitive.Tooltip; - -function ChartTooltipContent({ - active, - payload, - className, +const ChartTooltip = RechartsPrimitive.Tooltip; + +function ChartTooltipContent({ + active, + payload, + className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, - formatter, - color, - nameKey, - labelKey, -}: Partial> & - React.ComponentProps<"div"> & { - hideLabel?: boolean; - hideIndicator?: boolean; - indicator?: "line" | "dot" | "dashed"; - nameKey?: string; - labelKey?: string; - }) { + formatter, + color, + nameKey, + labelKey, +}: Partial> & + React.ComponentProps<"div"> & { + hideLabel?: boolean; + hideIndicator?: boolean; + indicator?: "line" | "dot" | "dashed"; + nameKey?: string; + labelKey?: string; + }) { const { config } = useChart(); const tooltipLabel = React.useMemo(() => { @@ -248,20 +320,20 @@ function ChartTooltipContent({ ); } -const ChartLegend = RechartsPrimitive.Legend; - -function ChartLegendContent({ - className, - hideIcon = false, - payload, - verticalAlign = "bottom", - nameKey, -}: React.ComponentProps<"div"> & - Pick & { - hideIcon?: boolean; - nameKey?: string; - }) { - const { config } = useChart(); +const ChartLegend = RechartsPrimitive.Legend; + +function ChartLegendContent({ + className, + hideIcon = false, + payload, + verticalAlign = "bottom", + nameKey, +}: React.ComponentProps<"div"> & + Pick & { + hideIcon?: boolean; + nameKey?: string; + }) { + const { config } = useChart(); if (!payload?.length) { return null; diff --git a/studio/frontend/src/features/studio/sections/training-section.tsx b/studio/frontend/src/features/studio/sections/training-section.tsx index af6a16fc44..2f99bea2b7 100644 --- a/studio/frontend/src/features/studio/sections/training-section.tsx +++ b/studio/frontend/src/features/studio/sections/training-section.tsx @@ -105,7 +105,7 @@ export function TrainingSection() {