> ## 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.

# Markers & trades

> Annotate the chart with glyph markers and a live trade-fill stream.

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

<Frame caption="Trade-fill markers and a scrolling buy/sell stream">
  <video autoPlay loop muted playsInline controls poster="/media/markers-and-trades.jpg" src="https://mintcdn.com/brandtnewlabs/3aK_iydbC8Ff9g3s/media/markers-and-trades.mp4?fit=max&auto=format&n=3aK_iydbC8Ff9g3s&q=85&s=3444eba8048cd04faebee21c95f0027a" data-path="media/markers-and-trades.mp4" />
</Frame>

## Markers

Draw glyphs into the canvas at a `(time, value)` position. Markers are read on the UI thread, so
pass a `SharedValue<Marker[]>` and update it with `.set(...)` / `.modify(...)`.

```tsx theme={null}
import { useSharedValue } from "react-native-reanimated";
import { LiveChart, type Marker } from "react-native-livechart";

const markers = useSharedValue<Marker[]>([
  { id: "1", time: Date.now() / 1000, kind: "winner", value: 142.5 },
]);

<LiveChart
  data={data}
  value={value}
  markers={markers}
  markerHitRadius={16}
  onMarkerPress={(event) => {
    if (!event) return; // tap missed every marker
    console.log(event.marker.id, event.point);
  }}
/>;
```

<Note>
  `onMarkerPress` fires on **tap** — there is no hover on touch. (It replaces the
  old `onMarkerHover`.)
</Note>

### Stacking co-located markers

By default markers draw at their anchor and overlap. Set `markerCluster="stacked"` to fan
co-located markers apart **horizontally** (overlapping, left-over-right — a stacked-coins look) at
their `side` (`"above"` / `"below"` / `"center"`) — e.g. buys below the line and sells above it. Once a
cluster exceeds `maxBeforeGroup` (default 5) it collapses into a single count-badge marker; tapping it
fires `onMarkerPress` with `isGrouped: true` and the full `members` list. Grouping is recomputed every
frame, so a cluster fans back out as you zoom in.

```tsx theme={null}
<LiveChart data={data} value={value} markers={markers} markerCluster="stacked" />
```

Pass the object form to tune the fan — `overlap` (0–1, default `0.75`) and the collapse threshold:

```tsx theme={null}
markerCluster={{ overlap: 0.75, maxBeforeGroup: 8 }}
```

By default the stack fans **horizontally**. Pass `direction: "vertical"` to pile co-located markers
into a **column** instead — each glyph keeps its time (x) and stacks along the value axis, growing
away from the line in its `side` direction (`"above"` climbs up, `"below"` descends, `"center"` climbs
up from the line). This is the "transactions piled on the candle" look; raise `maxBeforeGroup` so a
tall column doesn't collapse to a count badge early.

```tsx theme={null}
markerCluster={{ direction: "vertical", overlap: 0.6, maxBeforeGroup: 20 }}
```

#### Custom collapsed-group badge

A collapsed group draws a round **count badge** by default (`groupBadge: "count"`). Two ways to give
it your own Skia look:

The built-in count uses the measured width of each digit, so proportional fonts such as `System`
remain readable. Pass an object with `letterSpacing` to add or remove space between count digits:

```tsx theme={null}
markerCluster={{ mode: "stacked", groupBadge: { letterSpacing: 1 } }}
```

**1. Reuse the members' look** — `groupBadge: "marker"` draws the **representative marker's own glyph**
(the newest marker in the run: its `image` → `icon`/`pill` → `kind`). So a run of green `+` buy pills
collapses to a single buy pill, not a generic count.

```tsx theme={null}
markerCluster={{ mode: "stacked", groupBadge: "marker", showGroupCount: true }}
```

**2. A dedicated group badge** — pass a [`MarkerGroupBadge`](/api-reference/types) object to draw a
glyph that's *independent of the members*, for when the collapse should look different from the
individual markers (e.g. tiny dots that collapse into a distinct "Buy 5" image). It takes your own
Skia `image`, or an `icon`/`pill` — the same options as a single marker.

```tsx theme={null}
const buy5 = useImage(require("./buy-5-badge.png")); // your SkImage
markerCluster={{ mode: "stacked", groupBadge: { image: buy5 } }}
// …or an icon badge:
markerCluster={{ mode: "stacked", groupBadge: { icon: "★", pill: true, color: "#a855f7" } }}
```

Either way, add `showGroupCount` to stamp the member count in the glyph's top-right corner (the
"Buy 5" look). All of this is drawn in Skia and batched with every other marker (one draw call) —
unlike `renderMarker`, which floats a React Native view. Tapping the group still fires
`onMarkerPress` with `isGrouped: true` and the `members` list, regardless of the badge style.

Built-in `kind` glyphs are `"trade" | "boost" | "graduation" | "winner" | "clawback"`. Override
the glyph with `icon` (text/emoji) or `image` (a Skia `SkImage`). Set `pill` to wrap the `icon` in a
filled circular badge in the marker `color` (icon rendered white) — e.g. a green `+` buy / red `−`
sell tag:

```tsx theme={null}
{ id: "buy-1", time, kind: "trade", icon: "+", color: "#16a34a", pill: true }
{ id: "sell-1", time, kind: "trade", icon: "−", color: "#dc2626", pill: true }
```

**Anchoring `y`:** pass an absolute `value`, or omit it to pin the marker to the line. On a
single-series chart, omitting `value` anchors the marker to the line at `time` (interpolated from
`data`); in multi-series, set `seriesId` to anchor to that series' line. So for a marker that should
always sit on the line, just give it a `time` and `kind`.

### Custom marker rendering

Built-in glyphs are drawn into the Skia canvas. When you need something the canvas **can't** draw —
a native `BlurView` glass badge, a gradient pill, an animated component — pass `renderMarker`. Return
a **React Native** element to float it, auto-centered, at the marker's live `(time, value)` position
(tracked on the UI thread); return `null`/`undefined` to keep the built-in glyph for that marker.

```tsx theme={null}
import { BlurView } from "expo-blur";

<LiveChart
  data={data}
  value={value}
  markers={markers}
  renderMarker={(marker) => {
    const side = (marker.data as { side?: "buy" | "sell" })?.side;
    if (!side) return null; // fall back to the built-in glyph
    return (
      <BlurView intensity={28} tint="light" style={styles.glassBadge}>
        <Text style={{ color: side === "buy" ? "#16a34a" : "#dc2626" }}>
          {side === "buy" ? "BUY" : "SELL"}
        </Text>
      </BlurView>
    );
  }}
/>;
```

Custom-rendered markers are floated as RN views over the canvas, so they render **crisp at native
resolution** (and skip the marker atlas entirely — no glyph drawn behind them). Taps still fire
`onMarkerPress` via the marker hit-test. `renderMarker` receives a second `ctx` argument
(`{ index, isGrouped, groupCount, side }`) so a representative can render a distinct collapsed look.
`expo-blur` is the consumer's choice — `renderMarker` accepts any element, and the library takes no
extra dependency.

<Note>
  Use `renderMarker` for a handful of special markers, not hundreds of high-frequency ones: each
  custom marker is its own animated view + projection, whereas built-in glyphs batch into a single
  draw call. Works the same on `LiveChart` and `LiveChartSeries`.
</Note>

## Trade stream

For trade-fill markers specifically, feed a `SharedValue<TradeEvent[]>`:

```tsx theme={null}
import type { TradeEvent } from "react-native-livechart";

const tradeStream = useSharedValue<TradeEvent[]>([]);
// tradeStream.modify((arr) => { "worklet"; arr.push({ side: "buy", price, size, time }); return arr; });

<LiveChart data={data} value={value} tradeStream={tradeStream} />;
```

```ts theme={null}
interface TradeEvent {
  side: "buy" | "sell";
  price: number;
  size: number;
  time: number;   // unix seconds
  symbol?: string;
}
```

<Note>
  Need a static line that doesn't track the data — a price alert, target, or working order? See
  [Reference lines & bands](/guides/reference-lines-and-bands).
</Note>
