Candlestick

Candlestick chart is used to represent stock price movements. This example uses Bar and ErrorBar to render a candlestick chart with custom Tooltip.
import { generateMockMarketData, RechartsDevtools } from '@recharts/devtools';
import {
  Bar,
  BarChart,
  BarShapeProps,
  CartesianGrid,
  DefaultZIndexes,
  ErrorBar,
  Rectangle,
  Tooltip,
  TooltipContentProps,
  TooltipIndex,
  XAxis,
  YAxis,
  useRechartsTheme,
} from 'recharts';
import { MarketCandle } from '@recharts/devtools/dist/generateMockMarketData';

const data: ReadonlyArray<MarketCandle> = generateMockMarketData(100, 1337, 100, 1768145757834);

const barDataKey: (entry: MarketCandle) => [number, number] = entry => [
  Math.min(entry.close, entry.open),
  Math.max(entry.close, entry.open),
];

const whiskerDataKey: (entry: MarketCandle) => [number, number] = entry => {
  const highEnd = Math.max(entry.close, entry.open);
  return [highEnd - entry.low, entry.high - highEnd];
};

const timestampToMinutes = (timestamp: number) => {
  const date = new Date(timestamp);
  return `${date.getUTCHours()}:${String(date.getUTCMinutes()).padStart(2, '0')}`;
};

const formatDollars = (value: number) => `${value.toFixed(2)}`;

const Candlestick = (props: BarShapeProps) => {
  // @ts-expect-error Recharts does spread MarketCandle on the props but the types don't reflect that
  const color = props.open < props.close ? 'green' : 'red';
  return <Rectangle {...props} fill={color} stroke="none" />;
};

const TooltipContent = (props: TooltipContentProps) => {
  const theme = useRechartsTheme();
  const { active, payload } = props;
  if (active && payload && payload.length) {
    const firstPayload = payload[0];
    if (firstPayload == null) {
      return null;
    }
    const entry: MarketCandle = firstPayload.payload;
    return (
      <div
        style={{
          ...theme?.typography,
          ...theme?.tooltip?.contentStyle,
          padding: '0 1em',
        }}
      >
        <p style={{ margin: 0 }}>{`Time: ${timestampToMinutes(entry.time)}`}</p>
        <p style={{ margin: 0 }}>{`Open: ${formatDollars(entry.open)}`}</p>
        <p style={{ margin: 0 }}>{`Close: ${formatDollars(entry.close)}`}</p>
        <p style={{ margin: 0 }}>{`Low: ${formatDollars(entry.low)}`}</p>
        <p style={{ margin: 0 }}>{`High: ${formatDollars(entry.high)}`}</p>
      </div>
    );
  }
  return null;
};

export default function CandlestickExample({ defaultIndex }: { defaultIndex?: TooltipIndex }) {
  return (
    <BarChart
      data={data}
      style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1.618 }}
      responsive
    >
      <XAxis dataKey="time" tickFormatter={timestampToMinutes} />
      <YAxis domain={['dataMin - 1', 'dataMax + 1']} tickFormatter={formatDollars} />
      <CartesianGrid vertical={false} />
      <Bar dataKey={barDataKey} shape={Candlestick}>
        <ErrorBar dataKey={whiskerDataKey} width={0} zIndex={DefaultZIndexes.bar - 1} />
      </Bar>
      <Tooltip content={TooltipContent} defaultIndex={defaultIndex} />
      <RechartsDevtools />
    </BarChart>
  );
}