Angular Orderbook Heatmap

Create a heatmap chart showing historical orderbook levels

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

theme.ts

createCandlestickChart.tsx

Copy to clipboard
Minimise
Fullscreen
1import {
2    SciChartSurface,
3    NumericAxis,
4    ENumericFormat,
5    ZoomPanModifier,
6    ZoomExtentsModifier,
7    MouseWheelZoomModifier,
8    NumberRange,
9    OhlcDataSeries,
10    FastCandlestickRenderableSeries,
11    SciChartJsNavyTheme,
12    DateTimeNumericAxis,
13    CursorModifier,
14    CursorTooltipSvgAnnotation,
15    EDataSeriesType,
16    ESeriesType,
17    FastMountainRenderableSeries,
18    GradientParams,
19    IRenderableSeries,
20    OhlcSeriesInfo,
21    Point,
22    SeriesInfo,
23    HeatmapColorMap,
24    UniformHeatmapRenderableSeries,
25    UniformHeatmapDataSeries,
26    EXyDirection,
27} from "scichart";
28
29import { appTheme } from "../../../theme";
30
31// Data file paths (files are copied via webpack.config.js)
32const ohlcFilePath = "LTCUSDT_OHLC.csv";
33const orderbookLevels = "orderbook_levels.csv";
34
35const baseUrl =
36    typeof window !== "undefined" &&
37    !window.location.hostname.includes("scichart.com") &&
38    !window.location.hostname.includes("localhost")
39        ? "https://www.scichart.com/demo"
40        : "";
41
42/** OHLCV candlestick data structure */
43type TCandleData = {
44    xValues: number[];
45    openValues: number[];
46    highValues: number[];
47    lowValues: number[];
48    closeValues: number[];
49    volumeValues: number[];
50};
51
52/**
53 * Loads OHLCV candlestick data from CSV file
54 * @returns Promise resolving to parsed candle data arrays
55 */
56async function loadCandleData(): Promise<TCandleData> {
57    const xValues: number[] = [];
58    const openValues: number[] = [];
59    const highValues: number[] = [];
60    const lowValues: number[] = [];
61    const closeValues: number[] = [];
62    const volumeValues: number[] = [];
63
64    try {
65        const filepath = baseUrl + ohlcFilePath;
66        const response = await fetch(filepath);
67
68        if (!response.ok) {
69            throw new Error(`HTTP error! status: ${response.status}`);
70        }
71
72        const csvText = await response.text();
73        const lines = csvText.split("\n");
74
75        // Parse each data row (skip header at index 0)
76        for (let i = 1; i < lines.length; i++) {
77            const line = lines[i].trim();
78            if (!line) continue;
79
80            const rowData = line.split(",");
81
82            if (rowData.length >= 6) {
83                const priceBar = {
84                    date: Number.parseInt(rowData[0]),
85                    open: Number.parseFloat(rowData[1]),
86                    high: Number.parseFloat(rowData[2]),
87                    low: Number.parseFloat(rowData[3]),
88                    close: Number.parseFloat(rowData[4]),
89                    volume: Number.parseFloat(rowData[5]),
90                };
91
92                // Only add valid numeric data
93                if (
94                    !isNaN(priceBar.date) &&
95                    !isNaN(priceBar.open) &&
96                    !isNaN(priceBar.high) &&
97                    !isNaN(priceBar.low) &&
98                    !isNaN(priceBar.close) &&
99                    !isNaN(priceBar.volume)
100                ) {
101                    xValues.push(priceBar.date);
102                    openValues.push(priceBar.open);
103                    highValues.push(priceBar.high);
104                    lowValues.push(priceBar.low);
105                    closeValues.push(priceBar.close);
106                    volumeValues.push(priceBar.volume);
107                }
108            }
109        }
110
111        return {
112            xValues,
113            openValues,
114            highValues,
115            lowValues,
116            closeValues,
117            volumeValues,
118        };
119    } catch (error) {
120        console.error("Error loading candle data:", error);
121        throw error;
122    }
123}
124
125/** Parsed order book heatmap data structure */
126type TParsedHeatmapData = {
127    /** 2D array of order values (Z-axis intensity) */
128    zValues: number[][];
129    /** Unix timestamps for X-axis cell positions */
130    xCellOffsets: number[];
131    /** Price levels for Y-axis cell positions */
132    yCellOffsets: number[];
133    /** Minimum non-zero value in the heatmap */
134    minValue: number;
135    /** Maximum value in the heatmap */
136    maxValue: number;
137};
138
139/**
140 * Loads order book depth heatmap data from CSV file
141 * CSV format: First row contains timestamps, subsequent rows contain price level and order values
142 * @returns Promise resolving to parsed heatmap data with Z-values and axis offsets
143 */
144async function loadHeatmapData(): Promise<TParsedHeatmapData> {
145    const zValues: number[][] = [];
146    let xCellOffsets: number[] = [];
147    const yCellOffsets: number[] = [];
148    let minValue = Infinity;
149    let maxValue = -Infinity;
150
151    try {
152        const dataFile = baseUrl + orderbookLevels;
153        const response = await fetch(dataFile);
154        const csvText = await response.text();
155        const lines = csvText.split("\n");
156
157        for (let i = 0; i < lines.length; i++) {
158            const line = lines[i].trim();
159            if (!line) continue;
160
161            const rowData = line.split(",");
162
163            if (i === 0) {
164                // Header row contains Unix timestamps for each column
165                const [_, ...cellOffsets] = rowData;
166                xCellOffsets = cellOffsets.map((timestampString: string) => {
167                    const timestamp = Number.parseInt(timestampString);
168                    return timestamp || 0;
169                });
170            } else {
171                // Data rows: first column is price level, remaining columns are order values
172                const [price, ...zValuesRow] = rowData;
173
174                if (!Number.isNaN(Number.parseInt(price))) {
175                    const rowValues = zValuesRow.map((val: string) => {
176                        const numVal = Number.parseFloat(val) || 0;
177                        // Track min/max for color map scaling (exclude zeros)
178                        if (numVal > 0) {
179                            minValue = Math.min(minValue, numVal);
180                            maxValue = Math.max(maxValue, numVal);
181                        }
182                        return numVal;
183                    });
184
185                    zValues.push(rowValues);
186                    yCellOffsets.push(Number.parseFloat(price));
187                }
188            }
189        }
190
191        // Default range if no valid values found
192        if (minValue === Infinity) {
193            minValue = 0;
194            maxValue = 1;
195        }
196
197        return { zValues, xCellOffsets, yCellOffsets, minValue, maxValue };
198    } catch (error) {
199        console.error("Error loading heatmap data:", error);
200        throw error;
201    }
202}
203
204/**
205 * Creates an Order Book Heatmap chart with candlestick overlay
206 * Demonstrates combining UniformHeatmapRenderableSeries with FastCandlestickRenderableSeries
207 * to visualize order book depth alongside price action
208 *
209 * @param rootElement - HTML element ID or element to render the chart into
210 * @returns Promise resolving to the chart surface and candlestick series
211 */
212export const drawExample = async (rootElement: string | HTMLDivElement) => {
213    const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
214        theme: new SciChartJsNavyTheme(),
215    });
216
217    // X-Axis: DateTimeNumericAxis for continuous time data
218    // Note: For stocks/forex with market hours, use DiscontinuousDateAxis to collapse weekend gaps
219    const xAxis = new DateTimeNumericAxis(wasmContext, {
220        cursorLabelFormat: ENumericFormat.Date_HHMMSS,
221    });
222    sciChartSurface.xAxes.add(xAxis);
223
224    // Y-Axis: Price axis with currency formatting
225    const priceAxis = new NumericAxis(wasmContext, {
226        labelFormat: ENumericFormat.Decimal,
227        labelPrecision: 2,
228        labelPrefix: "$",
229    });
230    sciChartSurface.yAxes.add(priceAxis);
231
232    // Load data from CSV files
233    const { xValues, openValues, highValues, lowValues, closeValues, volumeValues } = await loadCandleData();
234    const { zValues, xCellOffsets, yCellOffsets, minValue, maxValue } = await loadHeatmapData();
235
236    // Heatmap color gradient: dark background -> white (low values) -> red (high values)
237    const gradientStops = [
238        { offset: 0, color: appTheme.DarkIndigo },
239        { offset: 0.03, color: appTheme.ForegroundColor },
240        { offset: 0.4, color: appTheme.VividRed },
241    ];
242
243    // Color map scales Z-values to gradient colors
244    const colorMap = new HeatmapColorMap({
245        minimum: minValue,
246        maximum: maxValue,
247        gradientStops,
248    });
249
250    // Calculate average X-axis step size for uniform heatmap spacing
251    let totalStep = 0;
252    let stepCount = 0;
253    for (let i = 1; i < xCellOffsets.length; i++) {
254        totalStep += xCellOffsets[i] - xCellOffsets[i - 1];
255        stepCount++;
256    }
257    const averageXStep = stepCount > 0 ? totalStep / stepCount : xCellOffsets[1] - xCellOffsets[0];
258
259    // Calculate average Y-axis step size for uniform heatmap spacing
260    let totalYStep = 0;
261    let yStepCount = 0;
262    for (let i = 1; i < yCellOffsets.length; i++) {
263        totalYStep += yCellOffsets[i] - yCellOffsets[i - 1];
264        yStepCount++;
265    }
266    const averageYStep = yStepCount > 0 ? totalYStep / yStepCount : yCellOffsets[1] - yCellOffsets[0];
267
268    // Create heatmap data series with uniform cell spacing
269    const heatmapDataSeries = new UniformHeatmapDataSeries(wasmContext, {
270        xStart: xCellOffsets[0],
271        xStep: averageXStep,
272        yStart: yCellOffsets[0],
273        yStep: averageYStep,
274        zValues,
275        dataSeriesName: "Order Value",
276    });
277
278    // Calculate data ranges for axis configuration
279    const candleXRange = [Math.min(...xValues), Math.max(...xValues)];
280    const heatmapXRange = [Math.min(...xCellOffsets), Math.max(...xCellOffsets)];
281
282    // Create heatmap series with semi-transparency to show candlesticks through it
283    const heatmapSeries = new UniformHeatmapRenderableSeries(wasmContext, {
284        opacity: 0.4,
285        dataSeries: heatmapDataSeries,
286        colorMap,
287        stroke: appTheme.PaleSkyBlue,
288    });
289    // Disable texture filtering for crisp cell boundaries
290    heatmapSeries.useLinearTextureFiltering = false;
291
292    sciChartSurface.renderableSeries.add(heatmapSeries);
293
294    // Set Y-axis to show full price range from heatmap data
295    const heatmapYRange = [Math.min(...yCellOffsets), Math.max(...yCellOffsets)];
296    priceAxis.visibleRange = new NumberRange(heatmapYRange[0], heatmapYRange[1]);
297
298    // Set X-axis to show overlapping time range between candle and heatmap data
299    const overlapStart = Math.max(candleXRange[0], heatmapXRange[0]);
300    const overlapEnd = Math.min(candleXRange[1], heatmapXRange[1]);
301
302    if (overlapStart < overlapEnd) {
303        xAxis.visibleRange = new NumberRange(overlapStart, overlapEnd);
304    } else {
305        // No overlap - default to heatmap range
306        xAxis.visibleRange = new NumberRange(heatmapXRange[0], heatmapXRange[1]);
307    }
308
309    // Create candlestick series with OHLC data
310    const candleDataSeries = new OhlcDataSeries(wasmContext, {
311        xValues,
312        openValues,
313        highValues,
314        lowValues,
315        closeValues,
316        dataSeriesName: "LTC/USDT",
317    });
318
319    const candlestickSeries = new FastCandlestickRenderableSeries(wasmContext, {
320        dataSeries: candleDataSeries,
321        stroke: appTheme.ForegroundColor,
322        strokeThickness: 2,
323        brushUp: appTheme.VividGreen + "AA",
324        brushDown: appTheme.MutedRed + "AA",
325        strokeUp: appTheme.VividGreen,
326        strokeDown: appTheme.MutedRed,
327        dataPointWidth: 0.8,
328        isVisible: true,
329        opacity: 1,
330    });
331
332    sciChartSurface.renderableSeries.add(candlestickSeries);
333    console.log("Added candlestick series with", candleDataSeries.count(), "data points");
334
335    // Add chart interaction modifiers
336    sciChartSurface.chartModifiers.add(
337        new ZoomExtentsModifier(),
338        new ZoomPanModifier({
339            enableZoom: true,
340            horizontalGrowFactor: 0.005,
341            verticalGrowFactor: 0,
342            xyDirection: EXyDirection.XDirection,
343        }),
344        new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
345        new CursorModifier({
346            crosshairStroke: appTheme.PaleOrange + 55,
347            axisLabelFill: appTheme.PaleOrange + 55,
348            tooltipLegendTemplate: getTooltipLegendTemplate,
349        })
350    );
351
352    return { sciChartSurface, candlestickSeries };
353};
354
355/**
356 * Transforms series for the SciChart overview/navigator component
357 * Converts candlestick series to mountain series for cleaner overview display
358 * @param defaultSeries - The original renderable series
359 * @returns Transformed series for overview, or undefined to hide
360 */
361const getOverviewSeries = (defaultSeries: IRenderableSeries) => {
362    if (defaultSeries.type === ESeriesType.CandlestickSeries) {
363        // Display candlestick data as a mountain series in the overview for cleaner visualization
364        return new FastMountainRenderableSeries(defaultSeries.parentSurface.webAssemblyContext2D, {
365            dataSeries: defaultSeries.dataSeries,
366            fillLinearGradient: new GradientParams(new Point(0, 0), new Point(0, 1), [
367                { color: appTheme.VividSkyBlue + "77", offset: 0 },
368                { color: "Transparent", offset: 1 },
369            ]),
370            stroke: appTheme.VividSkyBlue,
371        });
372    }
373    // Hide other series (e.g., heatmap) from overview
374    return undefined;
375};
376
377/** Configuration for SciChart overview/navigator component */
378export const sciChartOverview = {
379    theme: appTheme.SciChartJsTheme,
380    transformRenderableSeries: getOverviewSeries,
381};
382
383/**
384 * Custom tooltip template for CursorModifier
385 * Displays OHLC values for candlestick series and order values for heatmap
386 * @param seriesInfos - Array of series info objects for series under cursor
387 * @param svgAnnotation - The tooltip annotation instance
388 * @returns SVG string for tooltip content
389 */
390const getTooltipLegendTemplate = (seriesInfos: SeriesInfo[], svgAnnotation: CursorTooltipSvgAnnotation) => {
391    let outputSvgString = "";
392
393    seriesInfos.forEach((seriesInfo, index) => {
394        const y = 20 + index * 20;
395        const textColor = seriesInfo.stroke;
396        let legendText = seriesInfo.formattedYValue;
397
398        // Format OHLC data for candlestick series
399        if (seriesInfo.dataSeriesType === EDataSeriesType.Ohlc) {
400            const o = seriesInfo as OhlcSeriesInfo;
401            legendText = `Open=${o.formattedOpenValue} High=${o.formattedHighValue} Low=${o.formattedLowValue} Close=${o.formattedCloseValue}`;
402        }
403        // Format heatmap data showing price level and order count
404        else if (seriesInfo.seriesName === "Order Value") {
405            legendText = `${seriesInfo.formattedYValue} Orders: ${seriesInfo.hitTestInfo.zValue}`;
406        }
407
408        outputSvgString += `<text x="8" y="${y}" font-size="13" font-family="Verdana" fill="${textColor}">
409          ${seriesInfo.seriesName}: ${legendText}
410      </text>`;
411    });
412
413    return `<svg width="100%" height="100%">
414                <g transform=translate(5,5)>
415               ${
416                   outputSvgString ? `<rect width="480px" height="50px" fill="#000000" opacity="0.4" rx="5" />` : ``
417               }
418                ${outputSvgString}
419                <g>
420             </svg>`;
421};
422

Order Book Heatmap Chart

This example demonstrates how to create an Order Book Heatmap visualization using SciChart.js, combining a UniformHeatmapRenderableSeries with a FastCandlestickRenderableSeries to display historical orderbook depth levels alongside price candles.

What is an Order Book Heatmap?

An order book heatmap visualizes the distribution of buy and sell orders at different price levels over time. The intensity of color represents the volume of orders at each price point, helping traders identify:

  • Support and resistance levels - Areas with high order concentration
  • Liquidity zones - Where large orders are clustered
  • Price walls - Significant order accumulations that may affect price movement

Key Features

  • UniformHeatmapDataSeries - Efficiently stores 2D grid data with uniform X and Y spacing
  • HeatmapColorMap - Configurable gradient color mapping from order volume to visual intensity
  • Candlestick overlay - Price action displayed on top of the heatmap for context
  • Custom tooltips - CursorModifier with custom template showing OHLC data and order values
  • Interactive navigation - ZoomPanModifier, MouseWheelZoomModifier, and ZoomExtentsModifier for exploring the data

Implementation Highlights

The heatmap uses a custom color gradient that transitions from dark (low volume) through white to red (high volume), making it easy to spot areas of high liquidity. The candlestick series is overlaid with semi-transparent colors to ensure both datasets remain visible.

angular Chart Examples & Demos

See Also: Financial Charts (9 Demos)

Angular Candlestick Chart | Online JavaScript Chart Examples

Angular Candlestick Chart

Discover how to create a Angular Candlestick Chart or Stock Chart using SciChart.js. For high Performance JavaScript Charts, get your free demo now.

Angular OHLC Chart | Angular Charts | SciChart.js Demo

Angular OHLC Chart

Easily create Angular OHLC Chart or Stock Chart using feature-rich SciChart.js chart library. Supports custom colors. Get your free trial now.

Angular Realtime Ticking Stock Chart | SciChart.js Demo

Angular Realtime Ticking Stock Charts

Create a Angular Realtime Ticking Candlestick / Stock Chart with live ticking and updating, using the high performance SciChart.js chart library. Get free demo now.

Angular Multi-Pane Stock Chart using Subcharts | View JavaScript Charts

Angular Multi-Pane Stock Charts using Subcharts

Create a Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Tenor Curves Demo | Angular Charts | SciChart.js Demo

Tenor Curves Demo

Demonstrating the capability of SciChart.js to create a composite 2D &amp; 3D Chart application. An example like this could be used to visualize Tenor curves in a financial setting, or other 2D/3D data combined on a single screen.

Angular Multi-Pane Stock Chart | View JavaScript Charts

Angular Multi-Pane Stock Charts using Sync Multi-Chart

Create a Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Angular Market Depth Chart | Angular Charts | SciChart.js

Angular Market Depth Chart

Create a Angular Depth Chart, using the high performance SciChart.js chart library. Get free demo now.

Angular Chart Hoverable Buy Sell Marker Annotations

Angular Chart Hoverable Buy Sell Marker Annotations

Demonstrates how to place Buy/Sell arrow markers on a Angular Stock Chart using SciChart.js - Annotations API

Angular User Annotated Stock Chart | SciChart.js Demo

Angular User Annotated Stock Chart

This demo shows you how to create a <strong>{frameworkName} User Annotated Stock Chart</strong> using SciChart.js. Custom modifiers allow you to add lines and markers, then use the built in serialisation functions to save and reload the chart, including the data and all your custom annotations.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.