# Candlebar Chart

`CandlebarChart` shows open, high, low, close, and volume data for each interval. Use it for market, trading, and asset-monitoring screens where users need to inspect price movement candle by candle.

This chart is available in Chart Kit Pro. Install it once from [Installation](https://chartkit.io/docs/react-native/charts/pro-installation/).

## Crosshair Inspector

Use crosshair inspection when the selected candle drives UI outside the chart. Keep the selected index controlled, update it from `interaction.onSelect`, and render the OHLCV readout wherever it fits your screen. The crosshair stays under the user’s finger while a separate candle marker shows which candle is selected. Use a controlled viewport with `onViewportChange` when the mini range selector needs to move or resize the visible interval.

```tsx
import { useState } from "react";
import { Text, View } from "react-native";
import {
  resolveCartesianChartThemeConfig,
  useChartKitTheme
} from "react-native-chart-kit/v2";
import type { CandlestickChartViewportConfig } from "@chart-kit/pro";
import { CandlebarChart } from "@chart-kit/pro";

const candles = Array.from({ length: 40 }, (_, index) => {
  const open = 184 + Math.sin(index * 0.55) * 6 + index * 1.4;
  const move = Math.cos(index * 0.75) * 7;
  const close = open + move;
  const wick = 3 + Math.abs(move) * 0.35;

  return {
    time: `T${index + 1}`,
    open,
    high: Math.max(open, close) + wick,
    low: Math.min(open, close) - wick,
    close,
    volume: Math.round(48 + Math.abs(move) * 9 + index * 3)
  };
});

const formatValue = (value: number) => value.toFixed(1);
const formatSignedValue = (value: number) =>
  `${value >= 0 ? "+" : ""}${formatValue(value)}`;

export function CrosshairInspector() {
  const chartTheme = useChartKitTheme();
  const [selectedIndex, setSelectedIndex] = useState(24);
  const [viewport, setViewport] = useState<CandlestickChartViewportConfig>({
    startIndex: 6,
    endIndex: 35
  });
  const selected = candles[selectedIndex] ?? candles[candles.length - 1]!;
  const resolvedTheme = resolveCartesianChartThemeConfig({
    mode: chartTheme.mode,
    preset: chartTheme.preset,
    presets: chartTheme.presets,
    theme: chartTheme.theme
  });
  const isUp = selected.close >= selected.open;
  const upColor =
    resolvedTheme.series[1] ??
    (chartTheme.mode === "dark" ? "#22c55e" : "#16a34a");
  const downColor =
    resolvedTheme.series[3] ??
    (chartTheme.mode === "dark" ? "#f59e0b" : "#dc2626");
  const directionColor = isUp ? upColor : downColor;
  const change = selected.close - selected.open;
  const metrics = [
    ["O", formatValue(selected.open)],
    ["H", formatValue(selected.high)],
    ["L", formatValue(selected.low)],
    ["C", formatValue(selected.close)],
    ["VOL", String(selected.volume)]
  ] as const;

  return (
    <View
      style={{
        width: 410,
        maxWidth: "100%",
        overflow: "hidden",
        borderWidth: 1,
        borderColor: resolvedTheme.axis,
        borderRadius: 8,
        backgroundColor: resolvedTheme.background
      }}
    >
      <View
        style={{
          width: 410,
          maxWidth: "100%",
          borderBottomWidth: 1,
          borderBottomColor: resolvedTheme.grid,
          backgroundColor: resolvedTheme.plotBackground,
          paddingBottom: 8,
          paddingLeft: 10,
          paddingRight: 10,
          paddingTop: 8,
          flexDirection: "row",
          alignItems: "center",
          gap: 10
        }}
      >
        <View
          style={{
            minWidth: 70,
            flexShrink: 0
          }}
        >
          <Text
            style={{
              color: resolvedTheme.mutedText,
              fontSize: 8,
              fontWeight: "800",
              marginBottom: 2,
              textTransform: "uppercase"
            }}
          >
            {selected.time}
          </Text>
          <View
            style={{
              flexDirection: "row",
              alignItems: "baseline",
              gap: 5
            }}
          >
            <Text
              style={{
                color: resolvedTheme.text,
                fontSize: 13,
                fontWeight: "800"
              }}
            >
              {formatValue(selected.close)}
            </Text>
            <Text
              style={{
                color: directionColor,
                fontSize: 9,
                fontWeight: "800"
              }}
            >
              {formatSignedValue(change)}
            </Text>
          </View>
        </View>
        <View
          style={{
            flex: 1,
            minWidth: 0,
            flexDirection: "row",
            alignItems: "stretch"
          }}
        >
          {metrics.map(([label, value], index) => (
            <View
              key={label}
              style={{
                flex: 1,
                minWidth: 0,
                alignItems: "center",
                borderLeftColor: resolvedTheme.grid,
                borderLeftWidth: index === 0 ? 0 : 1,
                paddingLeft: index === 0 ? 0 : 5
              }}
            >
              <Text
                style={{
                  color: resolvedTheme.mutedText,
                  fontSize: 8,
                  fontWeight: "800",
                  marginBottom: 1,
                  textTransform: "uppercase"
                }}
              >
                {label}
              </Text>
              <Text
                style={{
                  color: label === "C" ? directionColor : resolvedTheme.text,
                  fontSize: 11,
                  fontWeight: "800"
                }}
              >
                {value}
              </Text>
            </View>
          ))}
        </View>
      </View>

      <CandlebarChart
        data={candles}
        dateKey="time"
        openKey="open"
        highKey="high"
        lowKey="low"
        closeKey="close"
        volumeKey="volume"
        formatYLabel={formatValue}
        height={326}
        interaction={{
          activation: "longPress",
          mode: "crosshair",
          onSelect: (event) => setSelectedIndex(event.dataIndex)
        }}
        rangeSelector={{
          visible: true,
          height: 70
        }}
        viewport={viewport}
        onViewportChange={(event) => setViewport(event.viewport)}
        selectedIndex={selectedIndex}
        selectionPriceLabel
        showHorizontalGridLines
        showYAxisLabels
        tooltip={false}
        width={410}
        yTickCount={4}
      />
    </View>
  );
}
```

## OHLC and Volume

```tsx
import { CandlebarChart } from "@chart-kit/pro";

const candles = Array.from({ length: 20 }, (_, index) => {
  const open = 184 + Math.sin(index * 0.55) * 6 + index * 2.4;
  const move = Math.cos(index * 0.75) * 7;
  const close = open + move;
  const wick = 3 + Math.abs(move) * 0.35;

  return {
    date: `T${index + 1}`,
    open,
    high: Math.max(open, close) + wick,
    low: Math.min(open, close) - wick,
    close,
    volume: Math.round(48 + Math.abs(move) * 9 + index * 3)
  };
});

export function TradingSession() {
  return (
    <CandlebarChart
      data={candles}
      dateKey="date"
      openKey="open"
      highKey="high"
      lowKey="low"
      closeKey="close"
      volumeKey="volume"
      defaultSelectedIndex={12}
      interaction="tap"
      tooltip
      width={410}
      height={300}
    />
  );
}
```

## Realtime Updates

Use realtime updates when the latest interval is still open. Keep the candle array in state, replace the active candle as ticks arrive, and append a new candle when the interval closes.

```tsx
import { useEffect, useMemo, useState } from "react";
import { Text, View } from "react-native";
import {
  resolveCartesianChartThemeConfig,
  useChartKitTheme
} from "react-native-chart-kit/v2";
import { CandlebarChart } from "@chart-kit/pro";

type Candle = {
  slot: number;
  time: string;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
};

const intervalMs = 1000;
const visiblePoints = 24;
const ticksPerCandle = 6;

const seedCandles: Candle[] = Array.from(
  { length: visiblePoints },
  (_, index) => {
    const open = 180 + Math.sin(index * 0.7) * 5 + index * 0.8;
    const move = Math.cos(index * 0.55) * 4;
    const close = open + move;
    const wick = 2 + Math.abs(move) * 0.4;

    return {
      slot: index,
      time: `T${index + 1}`,
      open,
      high: Math.max(open, close) + wick,
      low: Math.min(open, close) - wick,
      close,
      volume: Math.round(50 + Math.abs(move) * 9)
    };
  }
);

const formatValue = (value: number) => value.toFixed(1);
const formatSignedValue = (value: number) =>
  `${value >= 0 ? "+" : ""}${formatValue(value)}`;

const appendCandle = (candles: Candle[], sequence: number): Candle[] => {
  const previous = candles[candles.length - 1]!;
  const open = previous.close;
  const move = Math.sin(sequence * 0.8) * 4 + Math.cos(sequence * 0.34) * 2;
  const close = open + move;
  const wick = 2 + Math.abs(move) * 0.35;

  return [
    ...candles,
    {
      slot: sequence,
      time: `T${sequence + 1}`,
      open,
      high: Math.max(open, close) + wick,
      low: Math.min(open, close) - wick,
      close,
      volume: Math.round(55 + Math.abs(move) * 10)
    }
  ];
};

const updateOpenCandle = (candle: Candle, liveStep: number): Candle => {
  const move = Math.sin(liveStep * 0.9) * 1.4 + Math.cos(liveStep * 0.35);
  const close = candle.close + move;

  return {
    ...candle,
    close,
    high: Math.max(candle.high, close + Math.abs(move) * 0.5),
    low: Math.min(candle.low, close - Math.abs(move) * 0.5),
    volume: candle.volume + Math.round(2 + Math.abs(move) * 3)
  };
};

const createLiveCandles = (liveStep: number): Candle[] => {
  const closedCandleCount = Math.floor(liveStep / ticksPerCandle);
  let candles = seedCandles.map((candle) => ({ ...candle }));

  for (let index = 0; index < closedCandleCount; index += 1) {
    candles = appendCandle(candles, visiblePoints + index);
  }

  const last = candles[candles.length - 1]!;
  candles[candles.length - 1] = updateOpenCandle(last, liveStep);

  return candles.slice(-visiblePoints);
};

export function RealtimeCandleUpdates() {
  const chartTheme = useChartKitTheme();
  const [liveStep, setLiveStep] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setLiveStep((current) => current + 1);
    }, intervalMs);

    return () => clearInterval(intervalId);
  }, []);

  const candles = useMemo(() => createLiveCandles(liveStep), [liveStep]);
  const latest = candles[candles.length - 1]!;
  const resolvedTheme = resolveCartesianChartThemeConfig({
    mode: chartTheme.mode,
    preset: chartTheme.preset,
    presets: chartTheme.presets,
    theme: chartTheme.theme
  });
  const isUp = latest.close >= latest.open;
  const upColor =
    resolvedTheme.series[1] ??
    (chartTheme.mode === "dark" ? "#22c55e" : "#16a34a");
  const downColor =
    resolvedTheme.series[3] ??
    (chartTheme.mode === "dark" ? "#f59e0b" : "#dc2626");
  const directionColor = isUp ? upColor : downColor;
  const change = latest.close - latest.open;
  const metrics = [
    ["O", formatValue(latest.open)],
    ["H", formatValue(latest.high)],
    ["L", formatValue(latest.low)],
    ["C", formatValue(latest.close)],
    ["VOL", String(latest.volume)]
  ] as const;

  return (
    <View
      style={{
        width: 410,
        maxWidth: "100%",
        overflow: "hidden",
        borderWidth: 1,
        borderColor: resolvedTheme.axis,
        borderRadius: 8,
        backgroundColor: resolvedTheme.background
      }}
    >
      <View
        style={{
          width: 410,
          maxWidth: "100%",
          borderBottomWidth: 1,
          borderBottomColor: resolvedTheme.grid,
          backgroundColor: resolvedTheme.plotBackground,
          paddingBottom: 8,
          paddingLeft: 10,
          paddingRight: 10,
          paddingTop: 8,
          flexDirection: "row",
          alignItems: "center",
          gap: 10
        }}
      >
        <View
          style={{
            minWidth: 70,
            flexShrink: 0
          }}
        >
          <Text
            style={{
              color: resolvedTheme.mutedText,
              fontSize: 8,
              fontWeight: "800",
              marginBottom: 2,
              textTransform: "uppercase"
            }}
          >
            {latest.time}
          </Text>
          <View
            style={{
              flexDirection: "row",
              alignItems: "baseline",
              gap: 5
            }}
          >
            <Text
              style={{
                color: resolvedTheme.text,
                fontSize: 13,
                fontWeight: "800"
              }}
            >
              {formatValue(latest.close)}
            </Text>
            <Text
              style={{
                color: directionColor,
                fontSize: 9,
                fontWeight: "800"
              }}
            >
              {formatSignedValue(change)}
            </Text>
          </View>
        </View>
        <View
          style={{
            flex: 1,
            minWidth: 0,
            flexDirection: "row",
            alignItems: "stretch"
          }}
        >
          {metrics.map(([label, value], index) => (
            <View
              key={label}
              style={{
                flex: 1,
                minWidth: 0,
                alignItems: "center",
                borderLeftColor: resolvedTheme.grid,
                borderLeftWidth: index === 0 ? 0 : 1,
                paddingLeft: index === 0 ? 0 : 5
              }}
            >
              <Text
                style={{
                  color: resolvedTheme.mutedText,
                  fontSize: 8,
                  fontWeight: "800",
                  marginBottom: 1,
                  textTransform: "uppercase"
                }}
              >
                {label}
              </Text>
              <Text
                style={{
                  color: label === "C" ? directionColor : resolvedTheme.text,
                  fontSize: 11,
                  fontWeight: "800"
                }}
              >
                {value}
              </Text>
            </View>
          ))}
        </View>
        <View
          style={{
            flexShrink: 0,
            flexDirection: "row",
            alignItems: "center",
            gap: 4,
            borderRadius: 999,
            backgroundColor:
              chartTheme.mode === "dark"
                ? "rgba(20, 184, 166, 0.16)"
                : "rgba(20, 184, 166, 0.1)",
            paddingBottom: 2,
            paddingLeft: 6,
            paddingRight: 6,
            paddingTop: 2
          }}
        >
          <View
            style={{
              width: 6,
              height: 6,
              borderRadius: 999,
              backgroundColor: upColor
            }}
          />
          <Text
            style={{
              color: upColor,
              fontSize: 10,
              fontWeight: "800",
              textTransform: "uppercase"
            }}
          >
            Live
          </Text>
        </View>
      </View>
      <CandlebarChart
        accessibilityLabel="Realtime OHLC chart"
        data={candles}
        dateKey="time"
        openKey="open"
        highKey="high"
        lowKey="low"
        closeKey="close"
        volumeKey="volume"
        formatYLabel={formatValue}
        height={280}
        showHorizontalGridLines
        showYAxisLabels
        width={410}
      />
    </View>
  );
}
```

In production, replace `createLiveCandles()` with your websocket, stream, or polling adapter.

## Product Use Cases

Use Candlebar charts for stocks, crypto, commodities, FX, embedded broker flows, portfolio analytics, and any screen where users need to inspect price movement without leaving the mobile app.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` | `TData[]` | Object-row OHLC source data. |
| `dateKey` | `keyof TData` | Row key used for x-axis labels and selected candle labels. |
| `openKey` | `keyof TData` | Row key used for opening values. |
| `highKey` | `keyof TData` | Row key used for high wick values. |
| `lowKey` | `keyof TData` | Row key used for low wick values. |
| `closeKey` | `keyof TData` | Row key used for closing values. |
| `volumeKey` | `keyof TData` | Optional row key used for volume bars. |
| `defaultSelectedIndex` | `number` | Initial selected candle index. |
| `selectedIndex` | `number` | Controlled selected candle index for external inspectors. |
| `interaction` | `object` | Enables tap or crosshair selection and gesture callbacks. |
| `viewport` | `object` | Controlled visible candle range. |
| `onViewportChange` | `(event) => void` | Called when the visible range changes. |
| `rangeSelector` | `boolean` or `object` | Shows and configures the mini range selector. |
| `viewportInteraction` | `boolean` or `object` | Enables pan/zoom viewport gestures. |
| `width` | `number` | Outer chart width in pixels. |
| `height` | `number` | Outer chart height in pixels. |

[Edit page](https://github.com/chart-kit/react-native-chart-kit/edit/main/docs/charts/candlebar.md)

[Previous  
Installation](https://chartkit.io/docs/react-native/charts/pro-installation/)[Next  
Radar Chart](https://chartkit.io/docs/react-native/charts/radar/)
