JavaScript Chart - Examples
SciChart.js ships with over 80 JavaScript Chart demos which you can browse, view the source code and see related documentation. Build incredible complex dashboards with SciChart.js, our High Performance JavaScript Chart Library.
This example extends on our JavaScript Candlestick Chart example by showing you how to build an interactive, realtime financial chart or stock chart using Scichart.js – High Performance JavaScript Charts.
This demo is feature-rich and showcases the following:
- Candlestick charts with live real-time updating data from the exchange.
- How to switch between Candlestick and OHLC chart type dynamically.
- Simple technical indicators like moving averages.
- Zooming, panning interactions.
- Scrollbars with overview of historical price data.
- Cursors and legends which update on mouse-move.
- Plotting volume bars with green/red fill depending on the candle data.
- Plotting large trades on the chart. Leave the example running for a while and it will highlight trades larger than $25,000 being executed on Binance.
Setting up the Candlestick Chart
The candlestick chart is initialised in a function createCandlestickChart(). This sets up the chart with a Candlestick series, Line series for moving averages, Zooming and Panning behaviour, a Cursor with legend and Scrollbar or ‘overview’ control.
Volume bars are visualised on a hidden Y-Axis so they have their own scale and are coloured using a PaletteProvider – an API feature in SciChart.js that lets you colour data-points based on a rule.
export const createCandlestickChart = async (divChartId: string, divOverviewId: string) => {
// Create a SciChartSurface
const { sciChartSurface, wasmContext } = await SciChartSurface.create(divChartId, {
theme: appTheme.SciChartJsTheme
});
// Add an XAxis of type DateTimeAxis
// Note for crypto data this is fine, but for stocks/forex you will need to use CategoryAxis which collapses gaps at weekends
// In future we have a hybrid IndexDateAxis which 'magically' solves problems of different # of points in stock market datasetd with gaps
const xAxis = new DateTimeNumericAxis(wasmContext);
// xAxis.labelProvider.useCache = false;
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.Once
})
);
// Create a secondary YAxis to host volume data on its own scale
const Y_AXIS_VOLUME_ID = "Y_AXIS_VOLUME_ID";
sciChartSurface.yAxes.add(
new NumericAxis(wasmContext, {
id: Y_AXIS_VOLUME_ID,
growBy: new NumberRange(0, 4),
isVisible: false,
autoRange: EAutoRange.Always
})
);
// 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);
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
const volumeDataSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Volume" });
sciChartSurface.renderableSeries.add(
new FastColumnRenderableSeries(wasmContext, {
dataSeries: volumeDataSeries,
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")
})
);
// Add large trades data to the chart
const largeTradesDataSeries = new XyzDataSeries(wasmContext, { dataSeriesName: `Trades Size > $${LARGE_TRADE_THRESHOLD.toLocaleString()}`});
sciChartSurface.renderableSeries.add(
new FastBubbleRenderableSeries(wasmContext, {
dataSeries: largeTradesDataSeries,
stroke: appTheme.VividGreen,
pointMarker: new EllipsePointMarker(wasmContext, { width: 64, height: 64, opacity: 0.23, strokeThickness: 2 }),
paletteProvider: new LargeTradesPaletteProvider(appTheme.VividGreen, appTheme.MutedRed)
})
);
// Optional: Add some interactivity modifiers
sciChartSurface.chartModifiers.add(
new ZoomExtentsModifier(),
new ZoomPanModifier(),
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
const sciChartOverview = await SciChartOverview.create(sciChartSurface, divOverviewId, {
theme: appTheme.SciChartJsTheme,
transformRenderableSeries: getOverviewSeries
});
// Add a watermark annotation, updated in setData() function
const watermarkAnnotation = new TextAnnotation({
x1: 0.5,
y1: 0.5,
xCoordinateMode: ECoordinateMode.Relative,
yCoordinateMode: ECoordinateMode.Relative,
horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
verticalAnchorPoint: EVerticalAnchorPoint.Center,
opacity: 0.2,
textColor: appTheme.ForegroundColor,
fontSize: 48,
fontWeight: "Bold",
text: "",
annotationLayer: EAnnotationLayer.BelowChart
});
sciChartSurface.annotations.add(watermarkAnnotation);
// Add a vertical line annotation at the latest price
const latestPriceAnnotation = new HorizontalLineAnnotation({
isHidden: true,
strokeDashArray: [2, 2],
strokeThickness: 1,
axisFontSize: 13,
axisLabelStroke: appTheme.ForegroundColor,
showLabel: true,
});
sciChartSurface.annotations.add(latestPriceAnnotation);
};
Adding Data & Realtime Updates
Since this example showcases live updates of a stock chart, we created a BinanceSocketClient to fetch data from the Binance Cryptocurrency exchange and subscribe to live price updates. This uses Rx-js and returns a stream of trade updates from the exchange as they occur.
// Susbscribe to price updates from the exchange
const subscription = binanceSocketClient.getRealtimeCandleStream("BTCUSDT", "1m").subscribe(pb => {
const priceBar = { date: pb.openTime, open: pb.open, high: pb.high, low: pb.low, close: pb.close, volume: pb.volume };
controls.onNewTrade(priceBar, pb.lastTradeSize, pb.lastTradeBuyOrSell);
});
These are passed to the chart in the onNewTrade function, inside createCandlestickChart(). We want to append to the chart if a new candle arrives, or update the existing one. The logic for this is seen below:
const onNewTrade = (priceBar: TPriceBar, tradeSize: number, lastTradeBuyOrSell: boolean) => {
// On new price bar from the exchange, we want to append or update the existing one (based on time)
const currentIndex = candleDataSeries.count() - 1;
const getLatestCandleDate = candleDataSeries.getNativeXValues().get(currentIndex);
if (priceBar.date / 1000 === getLatestCandleDate) {
// Case where the exchange sends a candle which is already on the chart, update it
candleDataSeries.update(currentIndex, priceBar.open, priceBar.high, priceBar.low, priceBar.close);
volumeDataSeries.update(currentIndex, priceBar.volume);
} else {
// Case where the exchange sends a new candle, append it
candleDataSeries.append(priceBar.date / 1000, priceBar.open, priceBar.high, priceBar.low, priceBar.close);
volumeDataSeries.append(priceBar.date / 1000, priceBar.volume);
// Is the latest candle in the viewport?
if (xAxis.visibleRange.max > getLatestCandleDate) {
// If so, shift the xAxis by one candle
const dateDifference = priceBar.date / 1000 - getLatestCandleDate;
const shiftedRange = new NumberRange(xAxis.visibleRange.min + dateDifference, xAxis.visibleRange.max + dateDifference);
xAxis.animateVisibleRange(shiftedRange, 250, easing.inOutQuad);
}
}
// Update the large trades displaying trades > $LARGE_TRADE_THRESHOLD in value
const tradeValue = tradeSize * priceBar.close;
if (tradeValue > LARGE_TRADE_THRESHOLD) {
const tradeValueNormalised = 20 * Math.log10(tradeValue) - 70;
console.log(`Large trade: ${new Date(priceBar.date)}, price ${priceBar.close}, size ${lastTradeBuyOrSell ? "+" : "-"}$${tradeValue.toFixed(2)}`);
// @ts-ignore
largeTradesDataSeries.append(priceBar.date / 1000, priceBar.close, tradeValueNormalised, { isSelected: false, lastTradeBuyOrSell });
}
// Update the latest price line annotation
updateLatestPriceAnnotation(priceBar);
};
Lastly, there are several other functions inside createCandlestickChart() such as setXRange(), setData() – which sets historical data, enableOhlc() which allow you to switch from OHLC to candlestick, and updateLatestPriceAnnotation(), which updates the position of a horizontal line showing the latest current market price.
Visualising Large Trades (Whale Detector!)
A neat feature we added into the demo to show the flexibility & power of SciChart.js was the ability to visualise large trades as bubbles over the chart.
How this works:
- When onNewTrade() is called, if the trade size is over LARGE_TRADE_THRESHOLD (default: $25,000), then an X,Y point is added to a Bubble Series.
- The Z-value of the bubble series is the scaling factor. We use a function to scale bubbles based on trade size so larger trades result in larger bubbles.
- Metadata is passed in to the largeTradesDataSeries saying if the latest trade was buy or sell. This is used to change the bubble colour.
See the following areas of the source-code in createCandlestickChart() to see how this works:
// Add large trades data to the chart
const largeTradesDataSeries = new XyzDataSeries(wasmContext, { dataSeriesName: `Trades Size > $${LARGE_TRADE_THRESHOLD.toLocaleString()}`});
sciChartSurface.renderableSeries.add(
new FastBubbleRenderableSeries(wasmContext, {
dataSeries: largeTradesDataSeries,
stroke: appTheme.VividGreen,
pointMarker: new EllipsePointMarker(wasmContext, { width: 64, height: 64, opacity: 0.23, strokeThickness: 2 }),
// LargeTradesPaletteProvider colours bubbles red or green based on metadata.lastTradeBuyOrSell = true or false
paletteProvider: new LargeTradesPaletteProvider(appTheme.VividGreen, appTheme.MutedRed)
})
);
// When new trade data comes in
const onNewTrade = (priceBar: TPriceBar, tradeSize: number, lastTradeBuyOrSell: boolean) => {
// ...
// Update the large trades displaying trades > $LARGE_TRADE_THRESHOLD in value
const tradeValue = tradeSize * priceBar.close;
if (tradeValue > LARGE_TRADE_THRESHOLD) {
const tradeValueNormalised = 20 * Math.log10(tradeValue) - 70;
console.log(`Large trade: ${new Date(priceBar.date)}, price ${priceBar.close}, size ${lastTradeBuyOrSell ? "+" : "-"}$${tradeValue.toFixed(2)}`);
// @ts-ignore
largeTradesDataSeries.append(priceBar.date / 1000, priceBar.close, tradeValueNormalised, { isSelected: false, lastTradeBuyOrSell });
}
// ...
};
// Class which manages red/green fill colouring on Large Trades depending on if the trade is buy or sell
class LargeTradesPaletteProvider implements IPointMarkerPaletteProvider {
private readonly upColorArgb: number;
private readonly downColorArgb: number;
constructor(upColor: string, downColor: string) {
this.upColorArgb = parseColorToUIntArgb(upColor);
this.downColorArgb = parseColorToUIntArgb(downColor);
}
// Return up or down color for the large trades depending on if last trade was buy or sell
overridePointMarkerArgb(xValue: number, yValue: number, index: number, opacity?: number, metadata?: IPointMetadata): TPointMarkerArgb {
// @ts-ignore
const tradeColor = metadata?.lastTradeBuyOrSell ? this.upColorArgb : this.downColorArgb;
return {fill: tradeColor, stroke: tradeColor};
}
strokePaletteMode: EStrokePaletteMode = EStrokePaletteMode.SOLID;
onAttached(parentSeries: IRenderableSeries): void {}
onDetached(): void {}
}
Do you want to create more complex JavaScript Financial or Stock Charts? Contact our technical-sales team to see what the SciChart.js team can do for you on a consultative basis.