Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { AreaChartData, AreaChartVariant } from "./types";

Expand Down Expand Up @@ -83,6 +84,8 @@ const AreaChartComponent = <T extends AreaChartData>({
height,
width,
}: AreaChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { PaletteName, useChartPalette } from "../utils/PalletUtils";

Expand Down Expand Up @@ -79,6 +80,8 @@ const AreaChartCondensedComponent = <T extends AreaChartData>({
height = CHART_HEIGHT,
width,
}: AreaChartCondensedProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
3 changes: 3 additions & 0 deletions packages/react-ui/src/components/Charts/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { BarChartData, BarChartVariant } from "./types";
import {
Expand Down Expand Up @@ -97,6 +98,8 @@ const BarChartComponent = <T extends BarChartData>({
height,
width,
}: BarChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { PaletteName, useChartPalette } from "../utils/PalletUtils";

Expand Down Expand Up @@ -93,6 +94,8 @@ const BarChartCondensedComponent = <T extends BarChartData>({
width,
maxBarWidth = DEFAULT_MAX_BAR_WIDTH,
}: BarChartCondensedProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { numberTickFormatter } from "../utils/styleUtils";
import { CustomBarShape } from "./components/CustomBarShape";
Expand Down Expand Up @@ -90,6 +91,8 @@ const HorizontalBarChartComponent = <T extends HorizontalBarChartData>({
height,
width,
}: HorizontalBarChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { LineChartData, LineChartVariant } from "./types";

Expand Down Expand Up @@ -83,6 +84,8 @@ export const LineChart = <T extends LineChartData>({
width,
strokeWidth = 2,
}: LineChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getColorForDataKey,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { PaletteName, useChartPalette } from "../utils/PalletUtils";

Expand Down Expand Up @@ -81,6 +82,8 @@ const LineChartCondensedComponent = <T extends LineChartData>({
width,
strokeWidth = 2,
}: LineChartCondensedProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useExportChartData, useTransformedKeys } from "../hooks/index.js";
import { DefaultLegend } from "../shared/DefaultLegend/DefaultLegend.js";
import { StackedLegend } from "../shared/StackedLegend/StackedLegend.js";
import { LegendItem } from "../types/Legend.js";
import { getCategoricalChartConfig } from "../utils/dataUtils.js";
import { getCategoricalChartConfig, normalizeChartData } from "../utils/dataUtils.js";
import { PaletteName, useChartPalette } from "../utils/PalletUtils.js";
import { PieChartData } from "./types/index.js";
import {
Expand Down Expand Up @@ -74,6 +74,8 @@ const PieChartComponent = <T extends PieChartData>({
height,
width,
}: PieChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { useExportChartData, useTransformedKeys } from "../hooks";
import { ActiveDot, CustomTooltipContent, DefaultLegend } from "../shared";
import { LegendItem } from "../types";
import { useChartPalette } from "../utils/PalletUtils";
import { get2dChartConfig, getDataKeys, getLegendItems } from "../utils/dataUtils";
import {
get2dChartConfig,
getDataKeys,
getLegendItems,
normalizeChartData,
} from "../utils/dataUtils";
import { AxisLabel } from "./components/AxisLabel";
import { RadarChartData } from "./types";

Expand Down Expand Up @@ -52,6 +57,8 @@ const RadarChartComponent = <T extends RadarChartData>({
height,
width,
}: RadarChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useExportChartData, useTransformedKeys } from "../hooks";
import { DefaultLegend } from "../shared/DefaultLegend/DefaultLegend";
import { StackedLegend } from "../shared/StackedLegend/StackedLegend";
import { LegendItem } from "../types/Legend";
import { getCategoricalChartConfig } from "../utils/dataUtils";
import { getCategoricalChartConfig, normalizeChartData } from "../utils/dataUtils";
import { PaletteName, useChartPalette } from "../utils/PalletUtils";
import { RadialChartData } from "./types";
import {
Expand Down Expand Up @@ -68,6 +68,8 @@ export const RadialChart = <T extends RadialChartData>({
height,
width,
}: RadialChartProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
YAxisTick,
} from "../shared";
import { LegendItem } from "../types";
import { get2dChartConfig, getLegendItems } from "../utils/dataUtils";
import { get2dChartConfig, getLegendItems, normalizeChartData } from "../utils/dataUtils";
import { PaletteName, useChartPalette } from "../utils/PalletUtils";
import { numberTickFormatter } from "../utils/styleUtils";
import ScatterDot from "./components/ScatterDot";
Expand Down Expand Up @@ -61,6 +61,8 @@ export const ScatterChart = ({
width,
shape = "circle",
}: ScatterChartProps) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const printContext = usePrintContext();
isAnimationActive = printContext ? false : isAnimationActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DefaultLegend } from "../shared/DefaultLegend/DefaultLegend";
import { FloatingUIPortal } from "../shared/PortalTooltip";
import { StackedLegend } from "../shared/StackedLegend/StackedLegend";
import { LegendItem, StackedLegendItem } from "../types";
import { normalizeChartData } from "../utils/dataUtils";
import { PaletteName, useChartPalette } from "../utils/PalletUtils";
import { ToolTip } from "./components";
import { SingleStackedBarData } from "./types";
Expand Down Expand Up @@ -36,6 +37,8 @@ export const SingleStackedBar = <T extends SingleStackedBarData>({
style,
animated = true,
}: SingleStackedBarProps<T>) => {
// Guard against nullish `data` while generative UI is streaming.
data = normalizeChartData(data);
const [isLegendExpanded, setIsLegendExpanded] = useState(false);
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const [hoveredLegendKey, setHoveredLegendKey] = useState<string | null>(null);
Expand Down
25 changes: 23 additions & 2 deletions packages/react-ui/src/components/Charts/utils/dataUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,38 @@ import { PieChartData } from "../PieChart";
import { RadialChartData } from "../RadialChart";
import { LegendItem } from "../types";

// Shared empty array reference so that normalizing nullish data does not
// allocate a new array on every render (a fresh `[]` would change identity
// and defeat the charts' `useMemo`/`React.memo` optimizations).
const EMPTY_CHART_DATA: readonly never[] = [];

/**
* Normalizes a chart's `data` prop to always be an array.
*
* While generative UI is streaming, `data` can momentarily be `null` or
* `undefined` before the full payload arrives. Passing that straight into the
* chart internals throws (e.g. `getDataKeys`, `data.length`, `[...data]`).
* This guard falls back to a stable shared empty array so charts render an
* empty state instead of crashing.
*
* @param data - The raw `data` prop, which may be nullish mid-stream.
* @returns The original array, or a stable empty array when `data` is nullish.
*/
export const normalizeChartData = <T extends readonly unknown[]>(data: T | null | undefined): T => {
return (Array.isArray(data) ? data : EMPTY_CHART_DATA) as T;
};

/**
* This function returns the data keys for the chart, used for the data keys of the chart.
* @param data - The data to be displayed in the chart.
* @param categoryKey - The key of the category to be displayed in the chart.
* @returns The data keys for the chart.
*/
export const getDataKeys = (
data: Array<Record<string, string | number>>,
data: Array<Record<string, string | number>> | null | undefined,
categoryKey: string,
): string[] => {
return Object.keys(data[0] || {}).filter((key) => key !== categoryKey);
return Object.keys(data?.[0] || {}).filter((key) => key !== categoryKey);
};

/**
Expand Down