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-native CartesianChart in a themed outer View. Accepts an animation prop for cascading "disable-all" to animated compound parts through AnimationSettingsProvider. Forwards ref to 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 cascaded isAllAnimationsDisabled: when disabled, the animate prop is dropped so data-change path interpolation is skipped.
  • LineChart.AnimatedLine: Replayable draw-on line. Sweeps the Skia Path.end trim from animation.progress[0] to animation.progress[1] (default [0, 1]) on mount and whenever resetKey identity changes, using the provided timing or spring config.
  • LineChart.Tooltip: Themed Skia Circle positioned at a chart-press coordinate. Accepts x / y shared values (typically from useChartPressState) and draws an indicator dot. The isActive visibility gate stays with the caller.
  • LineChart.Crosshair: Themed Skia vertical rule tracking a chart-press x-coordinate across top / bottom pixel bounds. Dashed by default via the variant prop.

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.animate from 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

proptypedefaultdescription
wrapperClassNamestring-Additional Tailwind classes for the outer View that wraps the chart. Required for chart height (e.g. h-48)
animationLineChartRootAnimation-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:

  • false or "disabled": Disable only root animations
  • "disable-all": Disable all animations including animated compound parts
  • true or undefined: Use default animations
  • object: Custom animation configuration with a state field 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

proptypedefaultdescription
colorClassNamestring'accent-chart-3'Uniwind accent-* class for the stroke color
strokeWidthnumber2Stroke width in logical pixels
animatePathAnimationConfig-victory-native path-interpolation config applied when points change. Dropped when cascaded isAllAnimationsDisabled is true
classNamestring-Uniwind class forwarded to the underlying Skia Line

Extends victory-native Linepoints, 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

proptypedefaultdescription
pointsPointsArray-Points for a single series, sourced from CartesianChart's render callback
curveTypeCurveType'linear'd3-shape curve factory name
connectMissingDatabooleanfalseWhether to visually connect across null / missing y values
colorColortheme chart-3Skia stroke color. Falls back to the --color-chart-3 CSS variable when omitted
strokeWidthnumber2Stroke width in logical pixels
animationLineChartAnimatedLineAnimation{ 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
resetKeynumber | string | boolean | null-Opaque identity value that re-triggers the draw-on animation when changed. Behaves like a React key for the animation only
...SkiaPathPropsOmit<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:

  • false or "disabled": Skip the animation; jump straight to progress[1]
  • true or undefined: Use default animation ({ type: 'timing', duration: 700 })
  • { state: 'disabled', ... }: Disable the animation while customizing other fields
  • object: Discriminated animation configuration on the type property

decay is intentionally excluded since a velocity-based decay has no natural stopping point at the sweep's to.

proptypedefaultdescription
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
...timingWithTimingConfig-Reanimated timing fields (duration, easing) when type: 'timing'
...springWithSpringConfig-Reanimated spring fields (damping, stiffness, mass, ...) when type: 'spring'

LineChart.Tooltip

proptypedefaultdescription
xSharedValue<number>-Horizontal position of the tooltip center, typically state.x.position from useChartPressState
ySharedValue<number>-Vertical position of the tooltip center, typically state.y[yKey].position from useChartPressState
rnumber5Radius of the indicator dot in logical pixels
colorColortheme chart-3Skia fill color. Falls back to the --color-chart-3 CSS variable when omitted
...SkiaCirclePropsOmit<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

proptypedefaultdescription
childrenReact.ReactNode-Skia PathEffect children. A user-supplied <DashPathEffect /> overrides the default dash intervals
xSharedValue<number>-Horizontal position of the rule, typically state.x.position from useChartPressState
topnumber-Top y-coordinate (in Skia canvas pixels) where the rule starts. Typically chartBounds.top
bottomnumber-Bottom y-coordinate (in Skia canvas pixels) where the rule ends. Typically chartBounds.bottom
variantLineChartCrosshairVariant'dashed'Visual style. "solid" renders an unbroken stroke; "dashed" attaches a themed DashPathEffect with [4, 4] intervals
colorColormuted * 0.3Skia stroke color. Falls back to the theme muted at 30% alpha
strokeWidthnumber1Stroke width in logical pixels
...SkiaPathPropsOmit<ComponentProps<typeof Path>, 'path' | 'style' | 'start' | 'end'>-Remaining Skia Path props. path, style, start, and end are controlled internally

LineChartCrosshairVariant

typedescription
'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.

On this page