How to show 1D colored horizontal bar on a JavaScript Chart
Tags: javascript, chart, webdev
Published: 2024-03-13 14:59:03

<blockquote><em>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 <a href="https://www.scichart.com/questions/js/showing-heatmap-as-a-horizontal-1d-bar">Showing heatmap as a horizontal 1D bar</a> on the SciChart.js forums. We created a solution uploaded to <a href="https://github.com/ABTSoftware/SciChart.JS.Examples/tree/master/Sandbox/CustomerExamples/1D%20Heatmap">Github</a> and here we're going to blog about how it works, and how to create similar solutions for your <a href="https://www.scichart.com/javascript-chart-features/">JavaScript Charts</a>.</em></blockquote>
<h2>Here's the requirement</h2>
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:

<img class="alignnone wp-image-8199 size-full" src="https://www.scichart.com/wp-content/uploads/2023/12/1D-heatmap-color-bar-docked-to-javascript-chart-axis.jpg" alt="Docking 1D Colored Heatmap Bar to top or bottom of a JavaScript Chart" width="1596" height="1194" />
<h2>How to do this with SciChart.js</h2>
<h3>Npm Project Setup</h3>
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 <strong>getting started &amp; boilerplate guides <a href="https://www.scichart.com/getting-started/javascript-chart/">here</a></strong> for more info on how to setup each project type.

Create a <code>package.json</code> and install SciChart.js. There's no license required as SciChart.js how ships with a <a href="https://www.scichart.com/licensing-scichart-js/">free community license</a>. You'll also need devDependencies for <code>webpack</code>, <code>webpack-cli</code>, <code>web pack-dev-server</code> and <code>copy-webpack-plugin</code>.
<pre><code class="language-json">{
  "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"
  }
}
</code></pre>
To save time, <code>tsconfig.json</code> should be added according to the <a href="https://github.com/ABTSoftware/SciChart.JS.Examples/tree/master/Sandbox/CustomerExamples/1D%20Heatmap">GitHub repo here</a>.

Next, you will need a <code>webpack.config.js</code> 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 <code>CopyPlugin</code> as follows
<pre><code class="language-javascript">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: "" }
      ]
    })
  ]
};
</code></pre>
What this does: when the application is compiled via <code>npm start</code>, WebAssembly files required by SciChart.js will be copied to the output directory. There is further <a href="https://www.scichart.com/documentation/js/current/webframe.html#Deploying%20Wasm%20or%20WebAssembly%20and%20Data%20Files%20with%20your%20app.html">documentation here on how to serve Wasm files</a>, as well as a <a href="https://www.scichart.com/documentation/js/current/webframe.html#Tutorial%2001%20-%20Setting%20up%20a%20Project%20with%20SciChart.js.html">video tutorial on setting up projects with npm</a> and <a href="http://scichart.com/getting-started/scichart-javascript/">getting-started guides for Vue/Angular/React</a> on the SciChart website should you need them.

Next you're going to need somewhere in your HTML a <code>&lt;div&gt;</code> with an ID that SciChart.js can load into:
<pre><code class="language-markup">&lt;div id="scichart-root" style="width: 800px; height: 600px"&gt;&lt;/div&gt;</code></pre>
and finally to include the compiled output, which in this case is bundle.js. Take a look at src/index.html at the associated <a href="https://github.com/ABTSoftware/SciChart.JS.Examples/tree/master/Sandbox/CustomerExamples/1D%20Heatmap">Github repo</a>
<h3>Initialising the Chart with X,Y Axis</h3>
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 <code>import { Type } from "scichart"</code>. 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:
<pre><code class="language-javascript">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();</code></pre>
<h3>Initialising the Line and Heatmap Series</h3>
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:
<pre><code class="language-javascript">    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 &lt; 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);</code></pre>
&nbsp;

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 <a href="https://www.scichart.com/documentation/js/current/webframe.html#The-Uniform-Heatmap-Chart-Type.html">Heatmap Series documentation</a>. Also the <a href="https://www.scichart.com/documentation/js/current/webframe.html#The%20Line%20Series%20Type.html">Line Series documentation</a> may be helpful.
<h3>Colouring the Line Series with heat values</h3>
Line Series in SciChart.js support a feature called <a href="https://www.scichart.com/documentation/js/current/webframe.html#Per-point%20Colouring%20of%20Line%20Segments.html">PaletteProviders</a>. 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 <code>IStrokePaletteProvider</code> and attaching to the <code>FastLineRenderableSeries.paletteProvider</code> property, however you can also use the <code>PaletteFactory</code> to create nice gradients.

Take a look at this code below:
<pre><code class="language-javascript">    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;</code></pre>
There's a lot of info in the P<a href="https://www.scichart.com/documentation/js/current/webframe.html#Per-point%20Colouring%20of%20Line%20Segments.html">aletteProvider documentation</a> on how this works. We won't go into it here to save time.

Run the application now with <code>npm start</code> and you should have an output similar to the one we showed in the image above!
<h3>Adding Zooming, Panning Behaviour to the chart</h3>
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 <a href="https://www.scichart.com/documentation/js/current/webframe.html#What%20is%20the%20ChartModifier%20API.html">ChartModifier docs</a> for more info.
<pre><code class="language-javascript">sciChartSurface.chartModifiers.add(new ZoomPanModifier(), 
   new ZoomExtentsModifier(), 
   new MouseWheelZoomModifier(), 
   new RolloverModifier());
</code></pre>
<h3>Docking the Heat Line to the top or bottom of the chart</h3>
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:
<pre><code class="language-">// 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 = () =&gt; {
        return new NumberRange(yAxis.visibleRange.min, yAxis.visibleRange.diff * 0.05 + yAxis.visibleRange.min);
    };</code></pre>
<h2>Further Layout &amp; Customisation options</h2>
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!
<h2>Further reading</h2>
<blockquote>Here’s some further resources you may find helpful related to this topic:
<ul>
 	<li><a href="https://www.scichart.com/getting-started/scichart-javascript/">Getting started with SciChart.js</a></li>
 	<li><a href="https://www.scichart.com/documentation/js/current/webframe.html#The-Uniform-Heatmap-Chart-Type.html">The Heatmap Series chart type</a></li>
 	<li><a href="https://www.scichart.com/documentation/js/current/webframe.html#The%20Line%20Series%20Type.html">the Line Series chart type</a></li>
 	<li><a href="https://www.scichart.com/documentation/js/current/webframe.html#The%20PaletteProvider%20API.html">PaletteProviders</a> for per-datapoint colouring</li>
</ul>
</blockquote>