Skip to content

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.

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.

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

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.

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

PropTypeDescription
dataTData[]Object-row OHLC source data.
dateKeykeyof TDataRow key used for x-axis labels and selected candle labels.
openKeykeyof TDataRow key used for opening values.
highKeykeyof TDataRow key used for high wick values.
lowKeykeyof TDataRow key used for low wick values.
closeKeykeyof TDataRow key used for closing values.
volumeKeykeyof TDataOptional row key used for volume bars.
defaultSelectedIndexnumberInitial selected candle index.
selectedIndexnumberControlled selected candle index for external inspectors.
interactionobjectEnables tap or crosshair selection and gesture callbacks.
viewportobjectControlled visible candle range.
onViewportChange(event) => voidCalled when the visible range changes.
rangeSelectorboolean or objectShows and configures the mini range selector.
viewportInteractionboolean or objectEnables pan/zoom viewport gestures.
widthnumberOuter chart width in pixels.
heightnumberOuter chart height in pixels.