Cursor and scrub

A drag that solves the curve on the UI thread and reports nothing but numbers.

Scrub
import {
  CartesianChart,
  ChartCursorDot,
  ChartCursorLine,
  ChartLine,
  useChartScrub,
} from "@delacour/react-native-charts";

function Revenue() {
  const scrub = useChartScrub(["revenue"]);

  return (
    <CartesianChart data={rows} scrub={scrub} xKey="day" yKeys={["revenue"]}>
      <ChartLine color="#0A84FF" curve="monotone" yKey="revenue" />
      <ChartCursorLine axis="x" color="#8E8E93" dash={[4, 4]} />
      <ChartCursorDot borderColor="#FFFFFF" color="#0A84FF" yKey="revenue" />
    </CartesianChart>
  );
}

useChartScrub allocates a set of shared values, one group per key you name; the root's scrub prop is what makes the chart take touches at all. Hold and drag: the pan clamps the touch to the plot, inverts it through the scale, finds the nearest datum and solves the drawn curve — all on the UI thread, with no hop to JavaScript, which is what lets the dot keep up with a fast drag.

ChartCursorLine and ChartCursorDot read those values and draw. Both are invisible until the scrub is active and both are parked off-canvas when there is nothing to point at, so a chart with no touch yet has no stray mark at the origin.

Behaviour

<CartesianChart scrub={scrub} scrubConfig={{ behaviour: "claim" }} />
<CartesianChart scrub={scrub} scrubConfig={{ behaviour: "hold", holdDuration: 150 }} />
<CartesianChart scrub={scrub} scrubConfig={{ enabled: false }} />

hold is the default: the scrub activates after a long press, so a chart inside a scrolling feed takes nothing by accident. claim gives horizontal drags to the scrub and vertical ones to the scroll — for a chart that owns its width. block wins outright, and needs the scrollable's own gesture passed to useScrubGesture to block.

Reading the scrub from a view

A readout on a plain view
import type { ChartScrubState } from "@delacour/react-native-charts";
import Animated, { useAnimatedStyle } from "react-native-reanimated";

function Readout({ scrub }: { scrub: ChartScrubState }) {
  const style = useAnimatedStyle(() => {
    const x = scrub.snappedX.value;
    return {
      opacity: scrub.isActive.value ? 1 : 0,
      transform: [{ translateX: Number.isFinite(x) ? x : 0 }],
    };
  });
  return <Animated.View style={style} />;
}

Every field on ChartScrubState holds a number or a boolean — never an SkPath, never an object. That is what lets a tooltip be an ordinary React Native view, positioned from a plain useAnimatedStyle, with no Skia import in sight. series[key].value is the nearest datum's value in domain units, which is what a label prints.

The per-series values start at NaN and x, y and snappedX go to NaN inside a gap. A NaN written into a style freezes that view with no error — guard with Number.isFinite, as above.

y versus snappedY

<ChartCursorDot color="#0A84FF" yKey="revenue" />
<ChartCursorDot color="#0A84FF" snap={false} yKey="revenue" />

Each series carries two points. snappedX/snappedY are the nearest datum's canvas point: a dot bound to them lands on real measurements, which is what the readout beside it names. x/y are the position on the drawn curve at the touched x, solved against the same cubics the renderer drew: a dot bound to them glides continuously along the line. Both come out of one binary search. The marks snap by default; snap={false} glides, and the dot will then disagree with any label showing a measured value.

On a horizontal chart there is no curve to glide along, so the glide values equal the snapped ones and the category lands in the root's snappedY.

Stacked series

Scrubbing a stack

For a key in stackKeys the dot rides the top of the visible segment, while series[key].value stays the raw measurement — so the mark sits where the eye expects and the readout prints what was measured.

API Reference

useChartScrub

function useChartScrub(keys: readonly string[]): ChartScrubState;

Stable for the life of the component unless the set of keys changes. A fresh array of the same keys does not reallocate.

ChartCursorLine

Prop

Type

ChartCursorDot

Prop

Type

ScrubConfig

Prop

Type

ChartScrubState

Prop

Type

ChartScrubSeriesState

Prop

Type

On this page