DEV Community

Cover image for How to show 1D colored horizontal bar on a JavaScript Chart
Andrew Bt
Andrew Bt

Posted on • Originally published at scichart.com

How to show 1D colored horizontal bar on a JavaScript Chart

In this blog post, we cover how to add a 1-Dimensional Colored Bar docked to the top or bottom of a JavaScript Chart. This was a question originally posted on the SciChart.js forums titled Showing heatmap as a horizontal 1D bar on the SciChart.js forums. We created a solution uploaded to Github and here we're going to blog about how it works, and how to create similar solutions for your JavaScript Charts.

Here's the requirement

Perhaps you want to show some kind of status of a system or timeline / warnings or alerts on a JavaScript chart with colour codes. These should be docked top or bottom so that they scroll with the chart.

In SciChart.js you could achieve this by displaying a horizontal heatmap based on values docked to the X-Axis of a chart. So if the value on X-Axis is 1, colour on the heatmap is shown as Orange. If the next value is 2, the colour is show as red. This heatmap should be docked to the top or bottom of the chart and zoom or pan as the chart scale is updated.

The output should look something like this:

Docking 1D Colored Heatmap Bar to top or bottom of a JavaScript Chart

How to do this with SciChart.js

Npm Project Setup

First of all we need to setup a new project. For this we've used TypeScript, just plain HTML. The same thing can be done with JavaScript and Angular / Vue or React. Take a look at the getting started & boilerplate guides here for more info on how to setup each project type.

Create a package.json and install SciChart.js. There's no license required as SciChart.js how ships with a free community license. You'll also need devDependencies for webpack, webpack-cli, web pack-dev-server and copy-webpack-plugin.

{
  "name": "1d-heatmap",
  "version": "1.0.0",
  "description": "1D Heatmap",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "scichart": "^3.2.358"
  },
  "devDependencies": {
    "copy-webpack-plugin": "^9.0.1",
    "prettier": "^2.4.1",
    "ts-loader": "^9.2.6",
    "webpack": "^5.59.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.3.1"
  }
}

To save time, tsconfig.json should be added according to the GitHub repo here.

Next, you will need a webpack.config.js setup to do one small custom step required for SciChart.js, to copy the WebAssembly (wasm) files to output directory. Modify your webpack.config.js to include a CopyPlugin as follows

const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  mode: "production",
  entry: "./src/index.ts",
  performance: {
    hints: false
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/
      },
    ]
  },
  resolve: {
    extensions: [".js", ".ts"]
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "build")
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: "src/index.html", to: "" },
        { from: "node_modules/scichart/_wasm/scichart2d.data", to: "" },
        { from: "node_modules/scichart/_wasm/scichart2d.wasm", to: "" }
      ]
    })
  ]
};

What this does: when the application is compiled via npm start, WebAssembly files required by SciChart.js will be copied to the output directory. There is further documentation here on how to serve Wasm files, as well as a video tutorial on setting up projects with npm and getting-started guides for Vue/Angular/React on the SciChart website should you need them.

Next you're going to need somewhere in your HTML a <div> with an ID that SciChart.js can load into:

<div id="scichart-root" style="width: 800px; height: 600px"></div>

and finally to include the compiled output, which in this case is bundle.js. Take a look at src/index.html at the associated Github repo

Initialising the Chart with X,Y Axis

Once the project setup is done, initialising the chart can be done in a few lines of JavaScript.

All imports in the SciChart.js library are import { Type } from "scichart". We'll need an async function to initialise the chart, as the loading of WebAssembly files is asynchronous. We can create a chart in JavaScript with SciChart.js with this code:

import {
    EAxisAlignment,
    NumberRange,
    NumericAxis,
    SciChartJsNavyTheme,
    SciChartSurface,
} from 'scichart';

async function initSciChart() {
    const { sciChartSurface, wasmContext } = await SciChartSurface.create('scichart-root', {
        theme: new SciChartJsNavyTheme()
    });

    const xAxis = new NumericAxis(wasmContext, { axisAlignment: EAxisAlignment.Top});
    sciChartSurface.xAxes.add(xAxis);

    const yAxis = new NumericAxis(wasmContext, { axisAlignment: EAxisAlignment.Left, visibleRange: new NumberRange(0, 120)});
    sciChartSurface.yAxes.add(yAxis);
}

// Initialises or loads the chart 
initSciChart();

Initialising the Line and Heatmap Series

Next, we want to initialise the chart series.

In SciChart.js, chart series are declared as a RenderableSeries / DataSeries pair, where one performs the visual rendering, and one stores data structures and handles updates and changes to data on the chart.

We're going to declare two line series to show different techniques and a heatmap series below:

    const lineSeries = new FastLineRenderableSeries(wasmContext, {
        stroke: "steelblue",
        strokeThickness: 3,
    });
    const dataSeries = new XyDataSeries(wasmContext);
    const heatMapData: number[][] = [[]];
    let i = 0;
    for (let x = 0; x < 10; x += 0.2) {
        let y = 50 + 50 * Math.sin(x);
        // lineSeries data accepts x,y values
        dataSeries.append(x, y);
        // Heatmap data is a 2-dimensional array
        heatMapData[0][i] = y;
        i++;
    }
    lineSeries.dataSeries = dataSeries;

    // Heatmap data accepts a 2D array, and positions on the chart in X,Y using xStep, xStart
    // yStep, yStart
    const heatmapDataSeries = new UniformHeatmapDataSeries(wasmContext, {
        xStart: 0,
        xStep: 0.2,
        yStart: 110,
        yStep: 5,
        zValues: heatMapData,
    });

    const heatmapSeries = new UniformHeatmapRenderableSeries(wasmContext, {
        dataSeries: heatmapDataSeries,
        colorMap: new HeatmapColorMap({
            minimum: 0,
            maximum: 100,
            // Heatmap data maps to colours using a list of gradientStops 
            // using minimum (corresponds to offset=0) and maximum (offset=1)
            gradientStops: [
               { offset: 0, color: '#00008B' },
               { offset: 0.2, color: '#6495ED' },
               { offset: 0.4, color: '#006400' },
               { offset: 0.6, color: '#7FFF00' },
               { offset: 0.8, color: '#FFFF00' },
               { offset: 1.0, color: '#FF0000' },
            ]
        }),
    });

    sciChartSurface.renderableSeries.add(lineSeries, heatmapSeries);

 

This will add a line series to the chart with a sine wave, and a 1-dimensional heatmap placed on the chart at a specific Y-value. Colour information is applied to the heatmap but not the line series. We're going to add this in the next step.

For more information about how to initialise a Heatmap in SciChart.js, see the Heatmap Series documentation. Also the Line Series documentation may be helpful.

Colouring the Line Series with heat values

Line Series in SciChart.js support a feature called PaletteProviders. This allows you to colour a series with heat values according to value. This could be x-value, y-value, index or any other rule you would like.

You can declare a palette provider by implementing IStrokePaletteProvider and attaching to the FastLineRenderableSeries.paletteProvider property, however you can also use the PaletteFactory to create nice gradients.

Take a look at this code below:

    const colorData: number[] = PaletteFactory.createColorMap(
        wasmContext,
        gradientStops
    );
    const palette: IStrokePaletteProvider = {
        strokePaletteMode: EStrokePaletteMode.GRADIENT,
        onAttached(parentSeries: IRenderableSeries): void {
        },
        onDetached(): void {},
        overrideStrokeArgb(xValue: number, yValue: number, index: number): number {
            // This function is called on draw for each point visible in the chart
            // Return a custom colour as Uint ARGB (encoded as number) to update this point in the chart
            const y = dataSeries.getNativeYValues().get(index);
            const lerpFactor = y / 100;
            const mapIndex = wasmContext.NumberUtil.Constrain(
                Math.round(lerpFactor * (colorData.length - 1)),
                0,
                colorData.length - 1
            );
            const result = colorData[mapIndex];
            return result;
        }
    };
    lineSeries.paletteProvider = palette;

There's a lot of info in the PaletteProvider documentation on how this works. We won't go into it here to save time.

Run the application now with npm start and you should have an output similar to the one we showed in the image above!

Adding Zooming, Panning Behaviour to the chart

This part is really easy. By default the charts have no zoom, pan or interaction behaviour associated, but you can build up and compose any combination of zooming/panning in SciChart.js by adding ChartModifiers. Read the ChartModifier docs for more info.

sciChartSurface.chartModifiers.add(new ZoomPanModifier(), 
   new ZoomExtentsModifier(), 
   new MouseWheelZoomModifier(), 
   new RolloverModifier());

Docking the Heat Line to the top or bottom of the chart

OK so you'll notice if you run the application at this stage, that the Heatmap line will move up/down as the chart is panned up or down. This is undesired behaviour, and we want to dock the Heatmap line to the top or bottom of the chart.

To do this, we can override the yRange calculation provided by an individual series.

Add the following code to your example:

// 1. Update the XAxis to be docked to bottom of the chart
const xAxis = new NumericAxis(wasmContext, { axisAlignment: EAxisAlignment.Bottom});

// 2. Update the heatmap yStart, yStep = 0,1 respectively 
    const heatmapDataSeries = new UniformHeatmapDataSeries(wasmContext, {
        xStart: 0,
        xStep: 0.2,
        yStart: 0,
        yStep: 1,
        zValues: heatMapData,
    });

// 3. Add a function override for heatmapSeries.yRange. This replaces the default yRange calculation for the heatmap always placing it in the bottom 5% of the chart
    // NOTE: Use this with caution as DataSeries.getYRange() is also used in the yAxis autorange algorithm
    // so you will need to specify a visiblerange on the yaxis or have other series as well.
    heatmapDataSeries.getYRange = () => {
        return new NumberRange(yAxis.visibleRange.min, yAxis.visibleRange.diff * 0.05 + yAxis.visibleRange.min);
    };

Further Layout & Customisation options

There’s a number of layout and customisation options you can use in SciChart.js for docking series to top or bottom of the chart, or left or right to creat complex visualisations.

We’ll go into these in future blog posts. For now, enjoy!

Further reading

Here’s some further resources you may find helpful related to this topic:

Top comments (0)