Demonstrates how to create a JavaScript Candlestick Chart using SciChart.js, High Performance JavaScript Charts
// SCICHART EXAMPLE
import {
CursorModifier,
CursorTooltipSvgAnnotation,
DiscontinuousDateAxis,
EAutoRange,
EDataSeriesType,
EFillPaletteMode,
ENumericFormat,
ESeriesType,
FastCandlestickRenderableSeries,
FastColumnRenderableSeries,
FastLineRenderableSeries,
FastMountainRenderableSeries,
FastOhlcRenderableSeries,
GradientParams,
IFillPaletteProvider,
IPointMetadata,
IRenderableSeries,
MouseWheelZoomModifier,
NumberRange,
NumericAxis,
OhlcDataSeries,
OhlcSeriesInfo,
parseColorToUIntArgb,
Point,
SciChartSurface,
SeriesInfo,
XyDataSeries,
XyMovingAverageFilter,
ZoomExtentsModifier,
ZoomPanModifier,
} from "scichart";
import { appTheme } from "./appTheme";
import { simpleBinanceRestClient } from "./binanceRestClient";
import { ExampleDataProvider, TPriceBar } from "./ExampleDataProvider";
import {
ETradeChartLabelFormat,
} from "scichart/Charting/Visuals/Axis/LabelProvider/SmartDateLabelProvider";
const Y_AXIS_VOLUME_ID = "Y_AXIS_VOLUME_ID";
export const drawExample = (dataSource: string) => async (rootElement: string | HTMLDivElement) => {
// Create a SciChartSurface
const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
theme: appTheme.SciChartJsTheme,
});
// We have a hybrid DiscontinuousDateAxis which 'magically' solves problems of different # of points in stock market datasetd with gaps
const xAxis = new DiscontinuousDateAxis(wasmContext, {
// autoRange.never as we're setting visibleRange explicitly below. If you dont do this, leave this flag default
autoRange: EAutoRange.Never,
cursorLabelFormat: ENumericFormat.Date_HHMM,
// Increase this threshold as the default value is right on our default range
labelThresholds: { [ETradeChartLabelFormat.Minutes]: 60 * 60 * 24 * 10 },
});
sciChartSurface.xAxes.add(xAxis);
// Create a NumericAxis on the YAxis with 2 Decimal Places
sciChartSurface.yAxes.add(
new NumericAxis(wasmContext, {
growBy: new NumberRange(0.1, 0.1),
labelFormat: ENumericFormat.Decimal,
labelPrecision: 2,
labelPrefix: "$",
autoRange: EAutoRange.Always,
})
);
// Create a secondary YAxis to host volume data on its own scale
sciChartSurface.yAxes.add(
new NumericAxis(wasmContext, {
id: Y_AXIS_VOLUME_ID,
growBy: new NumberRange(0, 4),
isVisible: false,
autoRange: EAutoRange.Always,
})
);
const xValues: number[] = [];
const openValues: number[] = [];
const highValues: number[] = [];
const lowValues: number[] = [];
const closeValues: number[] = [];
const volumeValues: number[] = [];
// Fetch data from now to 300 1hr candles ago
const endDate = new Date(Date.now());
const startDate = new Date();
startDate.setHours(endDate.getHours() - 300);
let priceBars: TPriceBar[];
if (dataSource !== "Random") {
priceBars = await simpleBinanceRestClient.getCandles("BTCUSDT", "1h", startDate, endDate, 500, dataSource);
} else {
priceBars = ExampleDataProvider.getRandomCandles(300, 60000, startDate, 60 * 60, true);
}
// Maps PriceBar { date, open, high, low, close, volume } to structure-of-arrays expected by scichart
priceBars.forEach((priceBar: any) => {
xValues.push(priceBar.date);
openValues.push(priceBar.open);
highValues.push(priceBar.high);
lowValues.push(priceBar.low);
closeValues.push(priceBar.close);
volumeValues.push(priceBar.volume);
});
// Zoom to the latest 100 candles
const lastBar = priceBars[priceBars.length - 1];
const firstVisiblebar = priceBars[priceBars.length - 100];
xAxis.visibleRange = new NumberRange(firstVisiblebar.date, lastBar.date);
// Create and add the Candlestick series
// The Candlestick Series requires a special dataseries type called OhlcDataSeries with o,h,l,c and date values
const candleDataSeries = new OhlcDataSeries(wasmContext, {
xValues,
openValues,
highValues,
lowValues,
closeValues,
dataSeriesName: dataSource === "Random" ? "Random" : "BTC/USDT",
});
const candlestickSeries = new FastCandlestickRenderableSeries(wasmContext, {
dataSeries: candleDataSeries,
stroke: appTheme.ForegroundColor, // used by cursorModifier below
strokeThickness: 1,
brushUp: appTheme.VividGreen + "77",
brushDown: appTheme.MutedRed + "77",
strokeUp: appTheme.VividGreen,
strokeDown: appTheme.MutedRed,
});
sciChartSurface.renderableSeries.add(candlestickSeries);
// Add an Ohlcseries. this will be invisible to begin with
const ohlcSeries = new FastOhlcRenderableSeries(wasmContext, {
dataSeries: candleDataSeries,
stroke: appTheme.ForegroundColor, // used by cursorModifier below
strokeThickness: 1,
dataPointWidth: 0.9,
strokeUp: appTheme.VividGreen,
strokeDown: appTheme.MutedRed,
isVisible: false,
});
sciChartSurface.renderableSeries.add(ohlcSeries);
//Add some moving averages using SciChart's filters/transforms API
// when candleDataSeries updates, XyMovingAverageFilter automatically recomputes
sciChartSurface.renderableSeries.add(
new FastLineRenderableSeries(wasmContext, {
dataSeries: new XyMovingAverageFilter(candleDataSeries, {
dataSeriesName: "Moving Average (20)",
length: 20,
}),
stroke: appTheme.VividSkyBlue,
})
);
sciChartSurface.renderableSeries.add(
new FastLineRenderableSeries(wasmContext, {
dataSeries: new XyMovingAverageFilter(candleDataSeries, {
dataSeriesName: "Moving Average (50)",
length: 50,
}),
stroke: appTheme.VividPink,
})
);
// Add volume data onto the chart
sciChartSurface.renderableSeries.add(
new FastColumnRenderableSeries(wasmContext, {
dataSeries: new XyDataSeries(wasmContext, { xValues, yValues: volumeValues, dataSeriesName: "Volume" }),
strokeThickness: 0,
// This is how we get volume to scale - on a hidden YAxis
yAxisId: Y_AXIS_VOLUME_ID,
// This is how we colour volume bars red or green
paletteProvider: new VolumePaletteProvider(
candleDataSeries,
appTheme.VividGreen + "77",
appTheme.MutedRed + "77"
),
})
);
// Optional: Add some interactivity modifiers
sciChartSurface.chartModifiers.add(
new ZoomExtentsModifier(),
new ZoomPanModifier({ enableZoom: true }),
new MouseWheelZoomModifier(),
new CursorModifier({
crosshairStroke: appTheme.VividOrange,
axisLabelFill: appTheme.VividOrange,
tooltipLegendTemplate: getTooltipLegendTemplate,
})
);
// Add Overview chart. This will automatically bind to the parent surface
// displaying its series. Zooming the chart will zoom the overview and vice versa
//Exporting at the bottom by an object-
// const overview = await SciChartOverview.create(sciChartSurface, divOverviewId, {
// theme: appTheme.SciChartJsTheme,
// transformRenderableSeries: getOverviewSeries,
// });
return { sciChartSurface, candlestickSeries, ohlcSeries };
};
class VolumePaletteProvider implements IFillPaletteProvider {
fillPaletteMode: EFillPaletteMode = EFillPaletteMode.SOLID;
private ohlcDataSeries: OhlcDataSeries;
private upColorArgb: number;
private downColorArgb: number;
constructor(masterData: OhlcDataSeries, upColor: string, downColor: string) {
this.upColorArgb = parseColorToUIntArgb(upColor);
this.downColorArgb = parseColorToUIntArgb(downColor);
this.ohlcDataSeries = masterData;
}
onAttached(parentSeries: IRenderableSeries): void {}
onDetached(): void {}
// Return up or down color for the volume bars depending on Ohlc data
overrideFillArgb(
xValue: number,
yValue: number,
index: number,
opacity?: number,
metadata?: IPointMetadata
): number {
const isUpCandle =
this.ohlcDataSeries.getNativeOpenValues().get(index) >=
this.ohlcDataSeries.getNativeCloseValues().get(index);
return isUpCandle ? this.upColorArgb : this.downColorArgb;
}
// Override stroke as well, even though strokethickness is 0, because stroke is used if column thickness goes to 0.
overrideStrokeArgb(
xValue: number,
yValue: number,
index: number,
opacity?: number,
metadata?: IPointMetadata
): number {
return this.overrideFillArgb(xValue, yValue, index, opacity, metadata);
}
}
// Override the standard tooltip displayed by CursorModifier
const getTooltipLegendTemplate = (seriesInfos: SeriesInfo[], svgAnnotation: CursorTooltipSvgAnnotation) => {
let outputSvgString = "";
// Foreach series there will be a seriesInfo supplied by SciChart. This contains info about the series under the house
seriesInfos.forEach((seriesInfo, index) => {
const y = 20 + index * 20;
const textColor = seriesInfo.stroke;
let legendText = seriesInfo.formattedYValue;
if (seriesInfo.dataSeriesType === EDataSeriesType.Ohlc) {
const o = seriesInfo as OhlcSeriesInfo;
legendText = `Open=${o.formattedOpenValue} High=${o.formattedHighValue} Low=${o.formattedLowValue} Close=${o.formattedCloseValue}`;
}
outputSvgString += `<text x="8" y="${y}" font-size="13" font-family="Verdana" fill="${textColor}">
${seriesInfo.seriesName}: ${legendText}
</text>`;
});
return `<svg width="100%" height="100%">
${outputSvgString}
</svg>`;
};
// Override the Renderableseries to display on the scichart overview
const getOverviewSeries = (defaultSeries: IRenderableSeries) => {
if (defaultSeries.type === ESeriesType.CandlestickSeries) {
// Swap the default candlestick series on the overview chart for a mountain series. Same data
return new FastMountainRenderableSeries(defaultSeries.parentSurface.webAssemblyContext2D, {
dataSeries: defaultSeries.dataSeries,
fillLinearGradient: new GradientParams(new Point(0, 0), new Point(0, 1), [
{ color: appTheme.VividSkyBlue + "77", offset: 0 },
{ color: "Transparent", offset: 1 },
]),
stroke: appTheme.VividSkyBlue,
});
}
// hide all other series
return undefined;
};
export const overviewOptions = {
theme: appTheme.SciChartJsTheme,
transformRenderableSeries: getOverviewSeries,
};
This example demonstrates a high-performance candlestick chart built with SciChart.js using JavaScript. It visualizes financial data using candlestick and OHLC series, complemented by moving averages and volume bars to provide a comprehensive view of market trends.
The chart is initialized by creating a SciChartSurface with WebAssembly rendering for optimal performance, following the guidelines from the Performance Tips & Tricks documentation. It sets up a DateTimeNumericAxis for time-based data and multiple NumericAxis for price and volume, with the latter serving as a secondary axis for additional data visualization. Data is fetched and mapped directly into the appropriate series types without using additional builder APIs, ensuring full control over the chart’s configuration.
The example integrates both candlestick and OHLC series, as detailed in the OHLC Series Type documentation, allowing users to toggle between the two views. It further applies moving average filters using the XyMovingAverageFilter to smooth data trends, and implements a custom IFillPaletteProvider to dynamically color volume bars based on price movements, as described in the PaletteProvider API documentation. Additionally, interactive modifiers such as zooming, panning, and custom SVG tooltips enhance user engagement, with tooltip customization techniques available in this guide.
By using JavaScript, this implementation avoids reliance on framework-specific constructs, offering a lean and direct approach to building high-performance financial charts. Best practices for setting up axes, integrating multiple series, and enabling smooth interactivity are followed rigorously, as highlighted in the interactive zooming and panning documentation. This example serves as a solid foundation for developers looking to leverage SciChart.js in JavaScript environments without additional abstraction layers.












