LineChart
A line chart for visualizing trends over time with multi-series, sparkline, and custom tooltip support.
Import
import { LineChart } from 'heroui-native-pro';Anatomy
<LineChart data={...} xKey="..." yKeys={[...]}>
{({ points, chartBounds }) => (
<>
<LineChart.Line points={...} />
<LineChart.AnimatedLine points={...} />
<LineChart.Tooltip x={...} y={...} />
<LineChart.Crosshair x={...} top={...} bottom={...} />
</>
)}
</LineChart>- LineChart: Root container that wraps
victory-nativeCartesianChartin a themed outerView. Accepts ananimationprop for cascading"disable-all"to animated compound parts throughAnimationSettingsProvider. Forwardsrefto the underlying chart for access to the Skia canvas and press-actions handle. - LineChart.Line: Themed static line series. Renders a Uniwind-wrapped Skia line path whose stroke color is driven by
colorClassName. Respects cascadedisAllAnimationsDisabled: when disabled, theanimateprop is dropped so data-change path interpolation is skipped. - LineChart.AnimatedLine: Replayable draw-on line. Sweeps the Skia
Path.endtrim fromanimation.progress[0]toanimation.progress[1](default[0, 1]) on mount and wheneverresetKeyidentity changes, using the provided timing or spring config. - LineChart.Tooltip: Themed Skia
Circlepositioned at a chart-press coordinate. Acceptsx/yshared values (typically fromuseChartPressState) and draws an indicator dot. TheisActivevisibility gate stays with the caller. - LineChart.Crosshair: Themed Skia vertical rule tracking a chart-press x-coordinate across
top/bottompixel bounds. Dashed by default via thevariantprop.
Usage
Basic usage
Provide data, xKey, and yKeys, then render a LineChart.Line for each series in the children render function.
<LineChart data={DATA} xKey="month" yKeys={['revenue']} wrapperClassName="h-48">
{({ points }) => <LineChart.Line points={points.revenue} />}
</LineChart>Multiple series
Render a separate LineChart.Line per key. Pass a distinct colorClassName to each so the curves are visually separable.
<LineChart
data={DATA}
xKey="month"
yKeys={['organic', 'paid']}
wrapperClassName="h-48"
>
{({ points }) => (
<>
<LineChart.Line points={points.organic} colorClassName="accent-chart-3" />
<LineChart.Line points={points.paid} colorClassName="accent-chart-1" />
</>
)}
</LineChart>Curve type
Switch the line interpolation with curveType. natural produces a smoother cubic spline, linear draws straight segments.
<LineChart.Line points={points.revenue} curveType="natural" />
<LineChart.Line points={points.revenue} curveType="linear" />Dashed line
Nest a Skia DashPathEffect as a child of LineChart.Line for dashed strokes.
import { DashPathEffect } from '@shopify/react-native-skia';
<LineChart.Line points={points.target} colorClassName="accent-chart-1">
<DashPathEffect intervals={[5, 5]} />
</LineChart.Line>;Custom axis font
Axis tick labels default to platform-native sans-serifs — Helvetica on iOS and sans-serif on Android, both at fontSize: 11. Override with any Skia SkFont via the xAxis.font / yAxis[i].font props.
Build the font from a bundled .ttf asset with useFont:
import { useFont } from '@shopify/react-native-skia';
import InterMedium from './assets/fonts/Inter-Medium.ttf';
function MyChart() {
const font = useFont(InterMedium, 12);
return (
<LineChart
data={DATA}
xKey="month"
yKeys={['revenue']}
xAxis={{ font }}
yAxis={[{ font }]}
wrapperClassName="h-48"
>
{({ points }) => <LineChart.Line points={points.revenue} />}
</LineChart>
);
}Draw-on animation
Use LineChart.AnimatedLine with an animation config to play a draw-on reveal.
<LineChart data={DATA} xKey="month" yKeys={['revenue']} wrapperClassName="h-48">
{({ points }) => (
<LineChart.AnimatedLine
points={points.revenue}
curveType="natural"
animation={{ type: 'timing', duration: 1200 }}
/>
)}
</LineChart>Animate data transitions
Pass an animate config to LineChart.Line to morph between datasets when the underlying points change. Each time data updates, victory-native's useAnimatedPath interpolates the old path into the new one using the provided Reanimated config — useful for timeframe toggles, filter swaps, or live-updating series.
Skia can only interpolate (and hence animate) paths with the same number of points. If the number of samples changes between renders (e.g. swapping datasets of different lengths), the path snaps instead of animating. Keep each dataset's point count consistent when driving
Line.animatefrom variable-length data.
const [timeframe, setTimeframe] = useState<'month' | 'year'>('month');
<LineChart
data={DATA[timeframe]}
xKey="index"
yKeys={['value']}
wrapperClassName="h-48"
>
{({ points }) => (
<LineChart.Line
points={points.value}
curveType="natural"
animate={{ type: 'timing', duration: 250 }}
/>
)}
</LineChart>;Replay animation on demand
Bump resetKey with any fresh value (typically a counter) to replay the draw-on animation.
const [replayCount, setReplayCount] = useState(0);
<LineChart data={DATA} xKey="month" yKeys={['revenue']} wrapperClassName="h-48">
{({ points }) => (
<LineChart.AnimatedLine
points={points.revenue}
animation={{ type: 'spring', damping: 18, stiffness: 120 }}
resetKey={replayCount}
/>
)}
</LineChart>
<Button onPress={() => setReplayCount((n) => n + 1)}>Replay</Button>;Custom sweep range
Use animation.progress to customize the [from, to] range bound to the Path.end trim. [1, 0] reverses the sweep for a fade-out.
<LineChart.AnimatedLine
points={points.revenue}
animation={{ type: 'timing', duration: 800, progress: [0, 1] }}
/>
<LineChart.AnimatedLine
points={points.revenue}
animation={{ type: 'timing', duration: 500, progress: [1, 0] }}
/>Chart-press tooltip
Wire useChartPressState to the chart via chartPressState and render LineChart.Tooltip gated by isActive.
const { state, isActive } = useChartPressState({
x: '' as string,
y: { revenue: 0 },
});
<LineChart
data={DATA}
xKey="month"
yKeys={['revenue']}
chartPressState={state}
wrapperClassName="h-48"
>
{({ points }) => (
<>
<LineChart.Line points={points.revenue} />
{isActive ? (
<LineChart.Tooltip x={state.x.position} y={state.y.revenue.position} />
) : null}
</>
)}
</LineChart>;Chart-press crosshair
Pair with LineChart.Crosshair for the classic hover-guide look. top and bottom come from chartBounds. The variant prop defaults to "dashed"; pass "solid" for an unbroken rule.
{
isActive ? (
<>
<LineChart.Crosshair
x={state.x.position}
top={chartBounds.top}
bottom={chartBounds.bottom}
/>
<LineChart.Crosshair
variant="solid"
x={state.x.position}
top={chartBounds.top}
bottom={chartBounds.bottom}
/>
</>
) : null;
}Example
import { Card } from 'heroui-native';
import { LineChart } from 'heroui-native-pro';
import { View } from 'react-native';
import { useChartPressState } from 'victory-native';
const REVENUE_DATA = [
{ month: 'Jan', revenue: 4200 },
{ month: 'Feb', revenue: 5800 },
{ month: 'Mar', revenue: 4900 },
{ month: 'Apr', revenue: 7200 },
{ month: 'May', revenue: 6100 },
{ month: 'Jun', revenue: 8400 },
{ month: 'Jul', revenue: 7800 },
{ month: 'Aug', revenue: 9200 },
{ month: 'Sep', revenue: 8600 },
{ month: 'Oct', revenue: 10200 },
{ month: 'Nov', revenue: 9800 },
{ month: 'Dec', revenue: 11500 },
];
const formatThousandsCurrency = (value: number): string =>
`$${(value / 1000).toFixed(0)}k`;
export default function MonthlyRevenueChart() {
const { state, isActive } = useChartPressState({
x: '' as string,
y: { revenue: 0 },
});
return (
<View className="flex-1 w-full px-5 justify-center">
<Card>
<Card.Header className="mb-4">
<Card.Title className="text-sm">Monthly Revenue</Card.Title>
</Card.Header>
<Card.Body>
<LineChart
data={REVENUE_DATA}
xKey="month"
yKeys={['revenue']}
chartPressState={state}
yAxis={[{ formatYLabel: formatThousandsCurrency }]}
wrapperClassName="h-[200px]"
>
{({ points, chartBounds }) => (
<>
<LineChart.Line points={points.revenue} curveType="monotoneX" />
{isActive ? (
<>
<LineChart.Tooltip
x={state.x.position}
y={state.y.revenue.position}
/>
<LineChart.Crosshair
x={state.x.position}
top={chartBounds.top}
bottom={chartBounds.bottom}
/>
</>
) : null}
</>
)}
</LineChart>
</Card.Body>
</Card>
</View>
);
}API Reference
LineChart
| prop | type | default | description |
|---|---|---|---|
wrapperClassName | string | - | Additional Tailwind classes for the outer View that wraps the chart. Required for chart height (e.g. h-48) |
animation | LineChartRootAnimation | - | Animation configuration for the chart root. Accepts "disable-all" to cascade animation skipping to all animated compound parts |
Extends victory-native CartesianChart — all CartesianChart props (data, xKey, yKeys, children, xAxis, yAxis, domainPadding, chartPressState, axisOptions, ref, etc.) are supported in addition to the LineChart-specific props above.
LineChartRootAnimation
Animation configuration for the root component. Can be:
falseor"disabled": Disable only root animations"disable-all": Disable all animations including animated compound partstrueorundefined: Use default animationsobject: Custom animation configuration with astatefield for the same disabling semantics
The root does not drive any of its own animated styles; its sole animation responsibility is cascading isAllAnimationsDisabled to compound parts that do animate.
LineChart.Line
| prop | type | default | description |
|---|---|---|---|
colorClassName | string | 'accent-chart-3' | Uniwind accent-* class for the stroke color |
strokeWidth | number | 2 | Stroke width in logical pixels |
animate | PathAnimationConfig | - | victory-native path-interpolation config applied when points change. Dropped when cascaded isAllAnimationsDisabled is true |
className | string | - | Uniwind class forwarded to the underlying Skia Line |
Extends victory-native Line — points, curveType, connectMissingData, children, and all Skia paint props (color, opacity, blendMode, strokeJoin, strokeCap, strokeMiter, antiAlias, start, end) flow through. colorClassName is added by Uniwind's withUniwind wrapper and resolves to the Skia color prop automatically; pass color directly to bypass Uniwind and supply a raw Skia color.
LineChart.AnimatedLine
| prop | type | default | description |
|---|---|---|---|
points | PointsArray | - | Points for a single series, sourced from CartesianChart's render callback |
curveType | CurveType | 'linear' | d3-shape curve factory name |
connectMissingData | boolean | false | Whether to visually connect across null / missing y values |
color | Color | theme chart-3 | Skia stroke color. Falls back to the --color-chart-3 CSS variable when omitted |
strokeWidth | number | 2 | Stroke width in logical pixels |
animation | LineChartAnimatedLineAnimation | { type: 'timing', duration: 700 } | Reanimated config for the draw-on animation. Captured via a ref so inline objects on every render do not re-trigger replay |
resetKey | number | string | boolean | null | - | Opaque identity value that re-triggers the draw-on animation when changed. Behaves like a React key for the animation only |
...SkiaPathProps | Omit<ComponentProps<typeof Path>, 'path' | 'style' | 'start' | 'end'> | - | Remaining Skia Path props. path, style, start, and end are controlled internally |
LineChartAnimatedLineAnimation
Animation configuration for the draw-on animation. Can be:
falseor"disabled": Skip the animation; jump straight toprogress[1]trueorundefined: Use default animation ({ type: 'timing', duration: 700 }){ state: 'disabled', ... }: Disable the animation while customizing other fieldsobject: Discriminated animation configuration on thetypeproperty
decay is intentionally excluded since a velocity-based decay has no natural stopping point at the sweep's to.
| prop | type | default | description |
|---|---|---|---|
type | 'timing' | 'spring' | - | Animation type. Discriminator that narrows the other fields to WithTimingConfig or WithSpringConfig |
progress | [number, number] | [0, 1] | [from, to] range bound to the Skia Path.end sweep. [1, 0] inverts the sweep for a fade-out |
...timing | WithTimingConfig | - | Reanimated timing fields (duration, easing) when type: 'timing' |
...spring | WithSpringConfig | - | Reanimated spring fields (damping, stiffness, mass, ...) when type: 'spring' |
LineChart.Tooltip
| prop | type | default | description |
|---|---|---|---|
x | SharedValue<number> | - | Horizontal position of the tooltip center, typically state.x.position from useChartPressState |
y | SharedValue<number> | - | Vertical position of the tooltip center, typically state.y[yKey].position from useChartPressState |
r | number | 5 | Radius of the indicator dot in logical pixels |
color | Color | theme chart-3 | Skia fill color. Falls back to the --color-chart-3 CSS variable when omitted |
...SkiaCircleProps | Omit<ComponentProps<typeof Circle>, 'cx' | 'cy' | 'c' | 'r'> | - | Remaining Skia Circle props (e.g. opacity, blendMode, strokeWidth, style). cx, cy, and c are controlled internally |
LineChart.Crosshair
| prop | type | default | description |
|---|---|---|---|
children | React.ReactNode | - | Skia PathEffect children. A user-supplied <DashPathEffect /> overrides the default dash intervals |
x | SharedValue<number> | - | Horizontal position of the rule, typically state.x.position from useChartPressState |
top | number | - | Top y-coordinate (in Skia canvas pixels) where the rule starts. Typically chartBounds.top |
bottom | number | - | Bottom y-coordinate (in Skia canvas pixels) where the rule ends. Typically chartBounds.bottom |
variant | LineChartCrosshairVariant | 'dashed' | Visual style. "solid" renders an unbroken stroke; "dashed" attaches a themed DashPathEffect with [4, 4] intervals |
color | Color | muted * 0.3 | Skia stroke color. Falls back to the theme muted at 30% alpha |
strokeWidth | number | 1 | Stroke width in logical pixels |
...SkiaPathProps | Omit<ComponentProps<typeof Path>, 'path' | 'style' | 'start' | 'end'> | - | Remaining Skia Path props. path, style, start, and end are controlled internally |
LineChartCrosshairVariant
| type | description |
|---|---|
'solid' | 'dashed' | "solid" renders the stroke unbroken; "dashed" attaches a themed DashPathEffect with default intervals matching web-style hover crosshairs |
Hooks
useLinePath
Re-exported from victory-native so consumers can build custom Skia <Path /> renderings on the same PointsArray the compound parts consume — useful for layering fills, gradients, or secondary strokes on top of the standard LineChart.Line.
import { useLinePath } from 'heroui-native-pro';
import { Path } from '@shopify/react-native-skia';
function CustomLine({ points }: { points: PointsArray }) {
const { path } = useLinePath(points, { curveType: 'natural' });
return <Path path={path} style="stroke" strokeWidth={3} color="red" />;
}See the full reference, including all supported curveType values and the connectMissingData option, in the victory-native useLinePath docs.