> ## Documentation Index
> Fetch the complete documentation index at: https://react-native-livechart.brandtnewlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Time-scroll

> Pan back through history; auto-follow resumes at the live edge.

<Warning>
  **Experimental.** `timeScroll` and `zoom` are prototypes — the gesture model and API may still
  change. Both work on `LiveChart` (line and candle) and `LiveChartSeries` (multi-series line).
</Warning>

By default the chart follows the live edge. Enable `timeScroll` to **pan back through history**:
drag (or fling) to scroll the window into the past, and the chart stops auto-following until you
return to the live edge, where live-following resumes. One-finger plot scrubbing is unchanged.

<Note>
  **Live example:**
  [`app/demo/time-scroll.tsx`](https://github.com/brandtnewlabs/react-native-livechart/blob/main/app/demo/time-scroll.tsx)
  in the example app.
</Note>

<Frame caption="Pan back through history; auto-follow resumes at the live edge">
  <video autoPlay loop muted playsInline controls poster="/media/time-scroll.jpg" src="https://mintcdn.com/brandtnewlabs/3aK_iydbC8Ff9g3s/media/time-scroll.mp4?fit=max&auto=format&n=3aK_iydbC8Ff9g3s&q=85&s=df66caa79911f7c9cf7def9d99494c21" data-path="media/time-scroll.mp4" />
</Frame>

```tsx theme={null}
<LiveChart data={data} value={value} timeScroll />
```

Panning is clamped to the earliest retained point, so keep enough history in `data` (or `candles`)
to scroll into — seed a few windows' worth and keep your buffer longer than the visible window.

While scrolled back, the **Y-range fits the visible window**: only the points (or candles) inside
the frozen window shape the axis — newer data and the live price sit beyond the right edge and
don't stretch it — so the axis re-fits the history you're looking at as you pan. Back at the live
edge, the range folds the live value back in as usual.

### Turning it back off

Setting `timeScroll` back to `false` while scrolled back — or switching `mode` so your component
stops passing it — **glides the window back to the live edge**: a brief eased animation (it
decelerates onto live), not an instant jump. It never stays frozen at the previous scroll position,
so toggling time-scroll (or changing modes) is always a clean return to live, and re-enabling it
later starts from the live edge rather than the old frozen spot. This holds even when your line and
candle series carry different amounts of history: a frozen edge that would land before the active
series' first point follows live instead of stranding the plot empty.

Tune that glide — or turn it off — with the **`returnToLive`** prop (a sibling of `timeScroll`, so it
survives `timeScroll={false}`):

```tsx theme={null}
<LiveChart data={data} value={value} timeScroll={enabled} returnToLive={false} />        // instant snap
<LiveChart data={data} value={value} timeScroll={enabled} returnToLive={{ duration: 700 }} /> // slower glide
```

`true` (the default) eases onto live over 450 ms; `false` snaps instantly (the pre-4.3 behavior);
`{ duration }` sets a custom length in milliseconds. It only governs this programmatic return —
flinging back to the live edge with the pan gesture keeps its own inertia.

## Gestures

`timeScroll: true` uses the default **drag-to-scroll** gesture. Pass a `TimeScrollConfig` to pick the
activation:

```tsx theme={null}
<LiveChart data={data} value={value} timeScroll={{ gesture: "axisDrag" }} />
```

* **`"holdToScrub"`** (default) — a one-finger drag **anywhere** scrolls the timeline; scrubbing
  moves to a **press-and-hold** (Rainbow-style). A quick drag scrolls; hold, then drag, to scrub.
* **`"axisDrag"`** — only a drag that **starts on the bottom x-axis strip** ("grab the time ruler")
  scrolls. The plot area stays free, so a one-finger drag there scrubs immediately.

### Hold duration (`scrubHoldMs`)

In `holdToScrub`, `scrubHoldMs` sets how long to press before scrub engages (so a quicker drag
scrolls instead). Higher = more deliberate scrub, fewer accidental scrubs while scrolling. Default
`500`.

```tsx theme={null}
<LiveChart
  data={data}
  value={value}
  scrub
  timeScroll={{ gesture: "holdToScrub", scrubHoldMs: 350 }}
/>
```

### Release momentum (`fling`)

By default, a quick drag keeps moving with decaying momentum after you lift your finger. Set
`fling: false` for a deliberate hard stop: the window remains exactly where the drag ends. This
does not change the live-edge snap zone, so releasing near the live edge still resumes
auto-following.

```tsx theme={null}
<LiveChart data={data} value={value} timeScroll={{ fling: false }} />
```

`fling` defaults to `true`. The example app's **Time scroll** screen includes a **Fling inertia**
switch to compare both behaviors.

### Overscroll (`overscroll`)

By default the window hard-stops at the data: the oldest point on the left, the live edge on the
right. Set `overscroll` to a fraction of the visible window (`0`–`1`) to drag **past** those bounds
into blank space, TradingView-style — pull the latest candle toward the middle to leave room on the
right, or pan left past loaded history (pairs well with `onVisibleRangeChange` paging while older
data lazy-loads in).

```tsx theme={null}
<LiveChart data={data} value={value} timeScroll={{ overscroll: 0.9 }} />
```

A released drag or fling that settles close to the live edge (within 2% of the window) re-attaches
to live; anything further out stays parked where it stopped. The same bounds apply to pinch-to-zoom.
Default `0` (hard stops).

## Pinch-to-zoom (`zoom`)

Enable `zoom` for **two-finger pinch-to-zoom** of the visible window. Pinch out to zoom in (a
narrower window), pinch in to zoom out — anchored at the **focal point** between your fingers, so
the time under your fingers stays put. It composes with `timeScroll`: zoom level and scroll position
are independent, and you can pan a zoomed-in window. Pinch is two-finger, so it never competes with
the one-finger pan / scrub.

```tsx theme={null}
<LiveChart data={data} value={value} timeScroll zoom />
```

Bounds default sensibly — you can zoom in to `timeWindow / 8` and out to the full retained data span.
Pass a `ZoomConfig` to set them explicitly:

```tsx theme={null}
<LiveChart data={data} value={value} zoom={{ minTimeWindow: 5, maxTimeWindow: 3600 }} />
```

The Y-range auto-fits the visible window as you zoom, and candle bars re-flow to the new width.

### Reset zoom from a button

Both chart components expose a `resetZoom()` method through a
[`LiveChartHandle`](/api-reference/types#imperative-handle). Call it from any outside event to
clear the focal-anchored pinch window and return to the configured `timeWindow` at the live edge:

```tsx theme={null}
import { useRef } from "react";
import { Button } from "react-native";
import { LiveChart, type LiveChartHandle } from "react-native-livechart";

const chartRef = useRef<LiveChartHandle>(null);

<Button title="Reset zoom" onPress={() => chartRef.current?.resetZoom()} />

<LiveChart
  ref={chartRef}
  data={data}
  value={value}
  timeWindow={300}
  timeScroll
  zoom
/>
```

The same handle works on `LiveChartSeries`. Resetting clears both state values changed by a
focal pinch: the overridden window width and its shifted right edge. That means the chart resumes
following live data instead of staying parked at the pinch anchor while its width returns to
normal. Calling it again is a safe no-op. A paused chart remains paused, and the displayed width
uses the chart's normal `smoothing` as it settles back to `timeWindow`.

The example app's **Time scroll** screen includes a **Reset zoom** button and a live visible-window
readout so the behavior can be exercised directly.

## Paging callbacks (`onVisibleRangeChange`, `onReachStart`)

To lazily page in history as the user scrolls/zooms back, two callbacks report the visible window:

```tsx theme={null}
<LiveChart
  data={data}
  value={value}
  timeScroll
  zoom
  onVisibleRangeChange={({ startSec, endSec, following }) => {
    // throttled to ~1 Hz; `following` is true at the live edge
  }}
  onReachStart={() => {
    // fires once when the left edge nears the oldest data — fetch older history here
  }}
/>
```

* **`onVisibleRangeChange`** fires (throttled to \~1 Hz) with the window's `startSec` / `endSec` and
  whether the chart is `following` the live edge.
* **`onReachStart`** is a **one-shot edge trigger**: it fires once when the left edge comes within one
  window-width of the earliest retained point, then re-arms after you move back out — the cue to load
  the next page of older data. When the fetched data extends `data` / `candles` further back, it can
  fire again as you keep scrolling.

For a complete REST pagination controller, cursor and concurrency guards, websocket point updates,
candlestick rollovers, and an infinite-query example, see
[Loading data on scroll](/guides/loading-data-on-scroll).

## Full-width plot while scrolled (`yAxis.float`)

A normal chart reserves a right gutter for the price axis, so candles stop short of the edge. Pair
`timeScroll` with [`yAxis: { float: true }`](/api-reference/types#config-objects) and the plot runs
**full-width under a floating price axis** — but only while you're scrolled back. At the live edge
(the default state) the chart keeps its normal gutter so the latest candle and the badge don't sit
under the floating labels; as soon as you scroll back, the plot expands under the axis so panned-in
candles aren't cut off, and it reverts when you return to live.

```tsx theme={null}
<LiveChart
  data={data}
  value={value}
  mode="candle"
  candles={candles}
  liveCandle={liveCandle}
  timeScroll
  yAxis={{ float: true }}
  badge={{ followViewEdge: true }}
/>
```

## Track the visible edge (`badge.followViewEdge`)

While scrolled back, the live price is off-screen — so the **live badge, dot, and value line are
hidden** by default (they'd mark a price that isn't in view). Set
[`badge.followViewEdge`](/api-reference/types#config-objects) so the **badge, value
line, and live dot** instead track the price at the **visible window's right edge** — the last
price you can see — and snap back to the live value when you return to the live edge. The badge's
momentum color is calculated at that same historical edge, so new live ticks cannot recolor a
stationary historical badge. To keep the badge, dot, and value line pinned at the off-screen live
price instead (the pre-4.12 behavior), set `timeScroll={{ hideLiveOnScrollBack: false }}`.

The plotted line is independent of those overlay settings: while scrolled back, it always ends
at the visible window's right-edge value and never connects the historical window to the current
live price.

## Keep the dot on the live point (`dot.trackWhileParked`)

The default live dot is pinned to the plot's right edge, so while scrolled back it would sit on a
historical price — it's hidden (and its pulse suppressed) instead. Set
[`dot.trackWhileParked`](/api-reference/types#config-objects) and the dot **tracks the true live
point** while scrolled back or overscrolled: it stays visible at the live point's x, keeps its
heartbeat pulse (it still marks an honest live position), and hides once the live point leaves
the visible window. This is most visible with `overscroll` — drag past the live edge into blank
space and the dot detaches from the plot edge, pulsing at the real last price.

```tsx theme={null}
<LiveChart
  data={data}
  value={value}
  timeScroll={{ overscroll: 0.5 }}
  dot={{ trackWhileParked: true }}
/>
```

Because a tracking dot no longer marks an off-screen price, it is exempt from
`hideLiveOnScrollBack` (the badge and value line keep the default hide). The flag is **ignored
with `badge.followViewEdge`** — an edge-pinned dot must stay aligned with its badge. Off by
default; the example app's **Time scroll** screen includes a **Dot tracks live point** switch.

## Notes

* **Both charts.** `timeScroll` and `zoom` are wired into `LiveChart` and `LiveChartSeries`. In
  multi-series, each series' dot / value label tracks its value at the visible right edge while
  scrolled (the dot rides the end of each line, not the live price).
* **Coexists with scrubbing.** Plot-area scrub (and the [order ticket](/guides/order-ticket))
  work alongside time-scroll — the gestures are disambiguated by region (`axisDrag`) or press-hold
  (`holdToScrub`). Pinch-zoom is two-finger, so it stays out of the one-finger gestures' way.
* **`axisDrag` in multi-series** doesn't yet exclude the bottom-band from scrub; `holdToScrub` (the
  default) is the recommended gesture for `LiveChartSeries`.
* See the [`LiveChart` reference](/api-reference/livechart#scrubbing) and
  [`TimeScrollConfig`](/api-reference/types#config-objects) / [`ZoomConfig`](/api-reference/types#config-objects)
  for every field.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.