Skip to content

Candlestick

The createCandlestick helper turns OHLC (open/high/low/close) items into candles: a direction-colored body spanning open→close, painted over a thin wick spanning low→high.

ts
// createCandlestick turns OHLC items into direction-colored open/close body
// bars painted over thin low/high wick bars — four ordinary bar series.
import { createCandlestick } from '@mochart/core';
import type { MochartInputConfig } from '@mochart/core';

const candlestick = createCandlestick([
  { label: 'Jun 01', open: 96.5, high: 97.4, low: 96.1, close: 97.1 },
  { label: 'Jun 02', open: 97.2, high: 99.2, low: 97.2, close: 98.6 },
  { label: 'Jun 03', open: 98.1, high: 102.3, low: 98.0, close: 101.2 },
  { label: 'Jun 04', open: 101.4, high: 101.8, low: 98.4, close: 99.1 },
  { label: 'Jun 05', open: 99.0, high: 99.6, low: 96.8, close: 97.3 },
  { label: 'Jun 08', open: 97.5, high: 98.8, low: 96.9, close: 98.4 },
  { label: 'Jun 09', open: 98.3, high: 98.5, low: 95.7, close: 96.2 },
  { label: 'Jun 10', open: 96.2, high: 97.9, low: 95.9, close: 97.6 },
  { label: 'Jun 11', open: 97.7, high: 100.4, low: 97.5, close: 100.1 },
  { label: 'Jun 12', open: 100.0, high: 100.9, low: 98.6, close: 99.0 }
]);

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Daily Share Price (fictional, $)' },
  categoryAxis: candlestick.categoryAxis,
  valueAxes: [{ title: '$ per share' }],
  series: candlestick.series
};

export const data = candlestick.data;

How it works

  • Each item is { label, open, high, low, close }. A candle is up when the close is at or above the open, down otherwise.
  • The candles are four ordinary bar series — an up and a down body spanning from open via rangeProperty, and an up and a down wick spanning lowhigh, narrowed to a sliver of the slot with barWidthFraction and listed first so the bodies paint over them. Every row carries values for exactly one direction, and missingValues: 'connect' with partialRangeIsMissing keeps the other direction's series from rendering — the same trick as the Waterfall.
  • The category axis is ordinal, so non-trading days (weekends, holidays) simply don't exist on the axis instead of leaving gaps — note Jun 05 sits next to Jun 08 above.
  • The default direction colors are aqua/red rather than the conventional green/red: green↔red is the classic red-green-blindness collision, while this pair stays distinguishable on light and dark surfaces. Override per direction with colors, rename the legend entries with seriesTitles, and tune the widths with wickWidthFraction / bodyWidthFraction.
  • The tooltip shows two rows per candle: the body's open – close span under its direction title, and the wick's low – high span under rangeTitle (default "Range"). The wicks stay out of the legend but follow their body's legend filtering and focus via followSeries, so toggling a direction removes whole candles and focusing a direction highlights whole candles.
  • Each row also carries the raw open/high/low/close plus change and direction, and the computed candles come back under candles — or call computeCandlesticks(items) alone for the math without the chart fragments.
  • For the tick-bar style of the same data — a thin low/high line with open and close ticks instead of a body — see OHLC Bars.

Hollow candles

Pass hollow: true to draw up candles as outlines — the classic hollow-candle style where a filled body means down:

ts
// The hollow option outlines up candles instead of filling them — the classic
// hollow-candle style where a filled body means down. The wicks split into
// segments around the body so they don't show through the hollow interior.
import { createCandlestick } from '@mochart/core';
import type { MochartInputConfig } from '@mochart/core';

const candlestick = createCandlestick([
  { label: 'Jun 01', open: 96.5, high: 97.4, low: 96.1, close: 97.1 },
  { label: 'Jun 02', open: 97.2, high: 99.2, low: 97.2, close: 98.6 },
  { label: 'Jun 03', open: 98.1, high: 102.3, low: 98.0, close: 101.2 },
  { label: 'Jun 04', open: 101.4, high: 101.8, low: 98.4, close: 99.1 },
  { label: 'Jun 05', open: 99.0, high: 99.6, low: 96.8, close: 97.3 },
  { label: 'Jun 08', open: 97.5, high: 98.8, low: 96.9, close: 98.4 },
  { label: 'Jun 09', open: 98.3, high: 98.5, low: 95.7, close: 96.2 },
  { label: 'Jun 10', open: 96.2, high: 97.9, low: 95.9, close: 97.6 },
  { label: 'Jun 11', open: 97.7, high: 100.4, low: 97.5, close: 100.1 },
  { label: 'Jun 12', open: 100.0, high: 100.9, low: 98.6, close: 99.0 }
], { hollow: true });

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Daily Share Price (fictional, $)' },
  categoryAxis: candlestick.categoryAxis,
  valueAxes: [{ title: '$ per share' }],
  series: candlestick.series
};

export const data = candlestick.data;

In hollow mode the low→high wick can't be painted behind the body (it would show through the hollow interior), so the helper splits it into segments that stop at the body edges, and the original wick series turns shapeless — it keeps the tooltip's single low – high range row and its focus/filter wiring, but draws nothing. The up body outlines itself through shapeStyle — a stroke color and width against fillOpacity: 0, pinned to 0 in the focused and defocused states too so hovering thickens the outline rather than filling it — and its legend and tooltip icons pick up the stroke color automatically. colors, seriesTitles and the width options apply as in filled mode.

Volume pane

Give the items a volume and pass volume: true (works with hollow too, and with OHLC Bars) to add the classic pane of direction-colored volume bars along the bottom of the plot:

ts
// The volume option adds a pane of direction-colored volume bars along the
// bottom of the plot: a hidden second axis confines the bars to the lower
// band via domain margins, and the returned valueAxes fragment
// carries both pane axes.
import { createCandlestick } from '@mochart/core';
import type { MochartInputConfig } from '@mochart/core';

const candlestick = createCandlestick([
  { label: 'Jun 01', open: 96.5, high: 97.4, low: 96.1, close: 97.1, volume: 310000 },
  { label: 'Jun 02', open: 97.2, high: 99.2, low: 97.2, close: 98.6, volume: 540000 },
  { label: 'Jun 03', open: 98.1, high: 102.3, low: 98.0, close: 101.2, volume: 890000 },
  { label: 'Jun 04', open: 101.4, high: 101.8, low: 98.4, close: 99.1, volume: 650000 },
  { label: 'Jun 05', open: 99.0, high: 99.6, low: 96.8, close: 97.3, volume: 480000 },
  { label: 'Jun 08', open: 97.5, high: 98.8, low: 96.9, close: 98.4, volume: 290000 },
  { label: 'Jun 09', open: 98.3, high: 98.5, low: 95.7, close: 96.2, volume: 610000 },
  { label: 'Jun 10', open: 96.2, high: 97.9, low: 95.9, close: 97.6, volume: 350000 },
  { label: 'Jun 11', open: 97.7, high: 100.4, low: 97.5, close: 100.1, volume: 720000 },
  { label: 'Jun 12', open: 100.0, high: 100.9, low: 98.6, close: 99.0, volume: 330000 }
], { volume: true });

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Daily Share Price (fictional, $)' },
  categoryAxis: candlestick.categoryAxis,
  valueAxes: candlestick.valueAxes!.map((axisConfig) =>
    axisConfig.id === 'price' ? { ...axisConfig, title: '$ per share' } : axisConfig),
  series: candlestick.series
};

export const data = candlestick.data;

The pane is pure domain-margin geometry on a second value axis, so it adapts to every data update: the result gains a valueAxes fragment with a price axis whose enlarged minMarginFraction lifts the candles into the upper plot, and a hidden volume axis pinned at 0 whose maxMarginFraction confines the bars to the bottom band (margins above 1 are allowed for exactly this banding). Tune the split with volume: { heightFraction, gapFraction } (defaults 0.2 and 0.05), relabel the tooltip rows with valueLabel (default "Volume"), or set visible: true on the volume axis fragment to show its scale. The volume bars follow their direction series — toggling or focusing Up takes its volume bars along — and stay out of the legend, with one volume row per day in the tooltip.

Released under the BSD-3-Clause License.