chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
CartesianGrid,
|
||||
Line,
|
||||
LineChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
type XAxisProps,
|
||||
type YAxisProps,
|
||||
} from "recharts";
|
||||
import { ChartTooltip, ChartTooltipContent } from "~/components/primitives/charts/Chart";
|
||||
import { CHART_MARGIN } from "./ChartBar";
|
||||
import { useChartContext } from "./ChartContext";
|
||||
import { ChartLineInvalid, ChartLineLoading, ChartLineNoData } from "./ChartLoading";
|
||||
import { useHasNoData } from "./ChartRoot";
|
||||
import { defaultYAxisTickFormatter, useYAxisWidth } from "./useYAxisWidth";
|
||||
// Legend is now rendered by ChartRoot outside the chart container
|
||||
|
||||
type CurveType =
|
||||
| "basis"
|
||||
| "basisClosed"
|
||||
| "basisOpen"
|
||||
| "linear"
|
||||
| "linearClosed"
|
||||
| "natural"
|
||||
| "monotoneX"
|
||||
| "monotoneY"
|
||||
| "monotone"
|
||||
| "step"
|
||||
| "stepBefore"
|
||||
| "stepAfter";
|
||||
|
||||
// ============================================================================
|
||||
// COMPOUND COMPONENT API
|
||||
// ============================================================================
|
||||
|
||||
export type ChartLineRendererProps = {
|
||||
/** Line curve type */
|
||||
lineType?: CurveType;
|
||||
/** Custom X-axis props to merge with defaults */
|
||||
xAxisProps?: Partial<XAxisProps>;
|
||||
/** Custom Y-axis props to merge with defaults */
|
||||
yAxisProps?: Partial<YAxisProps>;
|
||||
/** Render as stacked area chart instead of line chart */
|
||||
stacked?: boolean;
|
||||
/** Custom tooltip label formatter */
|
||||
tooltipLabelFormatter?: (label: string, payload: any[]) => string;
|
||||
/** Optional formatter for numeric tooltip values (e.g. bytes, duration) */
|
||||
tooltipValueFormatter?: (value: number) => string;
|
||||
/** Width injected by ResponsiveContainer */
|
||||
width?: number;
|
||||
/** Height injected by ResponsiveContainer */
|
||||
height?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Line chart renderer for the compound component system.
|
||||
* Must be used within a Chart.Root.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Chart.Root config={config} data={data} dataKey="day">
|
||||
* <Chart.Line type="step" />
|
||||
* <Chart.Legend simple />
|
||||
* </Chart.Root>
|
||||
* ```
|
||||
*/
|
||||
export function ChartLineRenderer({
|
||||
lineType = "step",
|
||||
xAxisProps: xAxisPropsProp,
|
||||
yAxisProps: yAxisPropsProp,
|
||||
stacked = false,
|
||||
tooltipLabelFormatter,
|
||||
tooltipValueFormatter,
|
||||
width,
|
||||
height,
|
||||
}: ChartLineRendererProps) {
|
||||
const {
|
||||
config,
|
||||
data,
|
||||
dataKey,
|
||||
dataKeys: _dataKeys,
|
||||
visibleSeries,
|
||||
state,
|
||||
highlight,
|
||||
setActivePayload,
|
||||
showLegend,
|
||||
} = useChartContext();
|
||||
const hasNoData = useHasNoData();
|
||||
const yAxisTickFormatter = yAxisPropsProp?.tickFormatter ?? defaultYAxisTickFormatter;
|
||||
const computedYAxisWidth = useYAxisWidth(data, visibleSeries, yAxisTickFormatter);
|
||||
|
||||
// Render loading/error states
|
||||
if (state === "loading") {
|
||||
return <ChartLineLoading />;
|
||||
} else if (state === "noData" || hasNoData) {
|
||||
return <ChartLineNoData />;
|
||||
} else if (state === "invalid") {
|
||||
return <ChartLineInvalid />;
|
||||
}
|
||||
|
||||
// Get the x-axis ticks based on tooltip state
|
||||
const xAxisTicks =
|
||||
highlight.tooltipActive && data.length > 2
|
||||
? [data[0]?.[dataKey], data[data.length - 1]?.[dataKey]]
|
||||
: undefined;
|
||||
|
||||
const xAxisConfig = {
|
||||
dataKey,
|
||||
tickLine: false,
|
||||
axisLine: false,
|
||||
tickMargin: 10,
|
||||
ticks: xAxisTicks,
|
||||
interval: "preserveStartEnd" as const,
|
||||
tick: {
|
||||
fill: "var(--color-text-dimmed)",
|
||||
fontSize: 11,
|
||||
style: { fontVariantNumeric: "tabular-nums" },
|
||||
},
|
||||
...xAxisPropsProp,
|
||||
};
|
||||
|
||||
const yAxisConfig = {
|
||||
axisLine: false,
|
||||
tickLine: false,
|
||||
tickMargin: 8,
|
||||
width: computedYAxisWidth,
|
||||
tick: {
|
||||
fill: "var(--color-text-dimmed)",
|
||||
fontSize: 11,
|
||||
style: { fontVariantNumeric: "tabular-nums" },
|
||||
},
|
||||
tickFormatter: yAxisTickFormatter,
|
||||
...yAxisPropsProp,
|
||||
};
|
||||
|
||||
// Handle mouse leave to also reset highlight
|
||||
const handleMouseLeave = () => {
|
||||
highlight.setTooltipActive(false);
|
||||
highlight.reset();
|
||||
};
|
||||
|
||||
// Render stacked area chart if stacked prop is true
|
||||
if (stacked && visibleSeries.length > 1) {
|
||||
return (
|
||||
<AreaChart
|
||||
data={data}
|
||||
width={width}
|
||||
height={height}
|
||||
stackOffset="none"
|
||||
margin={CHART_MARGIN}
|
||||
onMouseMove={(e: any) => {
|
||||
if (e?.activePayload?.length) {
|
||||
setActivePayload(e.activePayload, e.activeTooltipIndex);
|
||||
highlight.setTooltipActive(true);
|
||||
} else {
|
||||
highlight.setTooltipActive(false);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<CartesianGrid vertical={false} stroke="var(--color-grid-bright)" strokeDasharray="3 3" />
|
||||
<XAxis {...xAxisConfig} />
|
||||
<YAxis {...yAxisConfig} />
|
||||
{/* When legend is shown below, render tooltip with cursor only (no content popup) */}
|
||||
<ChartTooltip
|
||||
cursor={{ stroke: "rgba(255, 255, 255, 0.1)", strokeWidth: 1 }}
|
||||
content={
|
||||
showLegend ? (
|
||||
() => null
|
||||
) : (
|
||||
<ChartTooltipContent indicator="line" valueFormatter={tooltipValueFormatter} />
|
||||
)
|
||||
}
|
||||
labelFormatter={tooltipLabelFormatter}
|
||||
/>
|
||||
{/* Note: Legend is now rendered by ChartRoot outside the chart container */}
|
||||
{visibleSeries.map((key) => (
|
||||
<Area
|
||||
key={key}
|
||||
type={lineType}
|
||||
dataKey={key}
|
||||
stroke={config[key]?.color}
|
||||
fill={config[key]?.color}
|
||||
fillOpacity={0.6}
|
||||
strokeWidth={1}
|
||||
stackId="stack"
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
))}
|
||||
</AreaChart>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={data}
|
||||
width={width}
|
||||
height={height}
|
||||
margin={CHART_MARGIN}
|
||||
onMouseMove={(e: any) => {
|
||||
if (e?.activePayload?.length) {
|
||||
setActivePayload(e.activePayload, e.activeTooltipIndex);
|
||||
highlight.setTooltipActive(true);
|
||||
} else {
|
||||
highlight.setTooltipActive(false);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<CartesianGrid vertical={false} stroke="var(--color-grid-bright)" strokeDasharray="3 3" />
|
||||
<XAxis {...xAxisConfig} />
|
||||
<YAxis {...yAxisConfig} />
|
||||
{/* When legend is shown below, render tooltip with cursor only (no content popup) */}
|
||||
<ChartTooltip
|
||||
cursor={{ stroke: "rgba(255, 255, 255, 0.1)", strokeWidth: 1 }}
|
||||
content={
|
||||
showLegend ? () => null : <ChartTooltipContent valueFormatter={tooltipValueFormatter} />
|
||||
}
|
||||
labelFormatter={tooltipLabelFormatter}
|
||||
/>
|
||||
{/* Note: Legend is now rendered by ChartRoot outside the chart container */}
|
||||
{visibleSeries.map((key) => (
|
||||
<Line
|
||||
key={key}
|
||||
dataKey={key}
|
||||
type={lineType}
|
||||
stroke={config[key]?.color}
|
||||
strokeWidth={1}
|
||||
dot={{ r: 1.5, fill: config[key]?.color, strokeWidth: 0 }}
|
||||
activeDot={{ r: 4 }}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user