LightningChart JSBar Chart Race JavaScript

TutorialDevelop a bar chart race JavaScript application with NodeJS, TypeScript, & LightningChart JS

Updated on October 10th, 2023 | Written by human

Bar chart race JavaScript  

When I wrote this article, the COVID-19 pandemic was at its peak point. Today, things are much better thanks to vaccinations that continued their steady positive global effect.

With this bar chart race JavaScript, we show how different countries diagnosed and reported COVID-19-infected people counts during the pandemic.

For the data used for this article, we will use the LightningChart JS bar chart race to show you how to visualize these infection counts while adding some more features such as the increased, reorder, and counting animation.

Note that the data used to create this bar chart race JavaScript are real. So, this chart will be useful if you need to work with COVID-19 information. This exercise will consume data from the Coronavirus Tracker API and a local JSON file.

zip icon
Download the project to follow the tutorial

Project overview

In an effort to track the virus number of cases worldwide and ensure that people were safe, many organizations developed custom charts to visualize their data and this is the example that we developed: a bar chart race JavaScript.

This type of chart displays the number of cases over time as the position of the countries in the chart varies according to the number of cases. This makes it easy to see the development of the countries in terms of the number of cases they have registered.

Configuring our template

1. Please, download the template that is provided in this article.

2. Once opened in Visual Studio Code, you will see a file tree like this one:

Bar-chart-race-javascript-file-tree

As you can see, we only have a JSON data file and the COVIDCHART type script file.

3. Please, open a new terminal.

4. As usual in a Node JS project, we will have to run our NPM Install command.

 This would be everything for our initial setup. Let’s code.

JSON file

In our JSON file we will see the following structure:

{"members": [
    {
        "country": "US",
        "history": {
        }
    },
    {
        "country": "Russia",
        "history": {
        }
    },
    {
        "country": "Spain",
        "history": {
        }
    },
    {
        "country": "Turkey",
        "history": {
        }
  • Members: The root node will be used in our typescript code to take the members within.
  • Country: Name value for all the countries selected in this file.
  • History: Date and numeric values of COVID cases (by day).
"country": "Spain",
        "history": {
            "3/15/20": 7798,
            "3/16/20": 9942,
            "3/17/20": 11748,
            "3/18/20": 13910,
In case you want to add more data to this file, you can find here more data.

COVIDCHART.ts

Inside this file, we will have all the logic needed to create our chart, configure animations, and format the data.

1. Import JSON data file.

2. Declare the constant lcjs that will refer to our @arction/lcjs library.

3. Extract required classes from lcjs.

import covid from './covid.json';
// Import LightningChartJS
const lcjs = require('@arction/lcjs')

// Extract required parts from LightningChartJS.
const {
    lightningChart,
    SolidFill,
    ColorHEX,
    AnimationEasings,
    Animator,
    UIOrigins,
    UILayoutBuilders,
    AxisTickStrategies,
    UIElementBuilders,
    AxisScrollStrategies,
    emptyLine,
    emptyFill,
    Themes
} = lcjs

4. Now we will declare the following constants:

// Variables used in the example
const rectThickness = 1
const rectGap = 0.5
const bars = []
const duration = 2000 // duration of timer and animation

Those values will affect the behavior of our chart, so declaring them now and using them as global values, would be easier for any update in our chart.

  • rectThickness: This will affect the thickness of all bars in our chart. With a higher value the bars will be thinner and vice versa (the value can float).

  • rectGap: will affect the position of the labels and numbers of each bar.

  • Duration: This would be the acceleration time of all our bars. If you specify a minor value, the bars will load the values faster.

5. Configuring the chart object: Before creating the chart, we need to configure the main properties.

const barChart = options => {
    // Create a XY chart and add a RectSeries to it for rendering rectangles.
    const chart = ls.ChartXY(options)
        // Use Chart's title to track date
        .setTitle('COVID-19 cases ' + new Date(2020, 2, 15).toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" }))
        // Disable AutoCursor (hide information of bar by hover the mouse over it)
        .setAutoCursorMode(0)
        // Add padding to Chart's right side
        .setPadding({ right: 40 })
        .setMouseInteractions(false);
  • Ls.ChartXY: We specified that we want to create an XY chart type.
  • setTitle: text that will be displayed as a title at the top of our chart. The value is a string type, so you can construct a string value dynamically.
  • setAutoCursorMode: Set chart AutoCursor behavior, by selecting a preset option from AutoCursorModes.
  • setPadding: Number with pixel margins for all sides or data structure with individual pixel paddings for each side. Any side can be omitted, only passed values will be overridden.
  • setMouseInteractions: This Boolean value will enable all mouse interactions that can be supported by the chart in construction.
  • getDefaultAxisY/AxisX: Get a reference to the default Y-Axis of the ChartXY. This will always return a reference to the Y-axis that is closest to the chart (starting from the bottom). All methods below getDefaultAxisX will affect only the X-axis. (Same logic in case you need to reference to the Y-axis getDefaultAxisY).
    // Cache X axis
    const axisX = chart.getDefaultAxisX()
        // Disable the scrolling animation for the X Axis, so it doesn't interfere.
        .setAnimationScroll(false)
        .setMouseInteractions(false);
  • setTickStrategy: This TickStrategy defines the positioning and formatting logic of Axis ticks as well as the style of created ticks.

createTickLabel 

In this function, we will declare the label properties for all our bars:

    // Set custom label for Y-axis
    const createTickLabel = (entry, y) => {
        return axisY.addCustomTick(UIElementBuilders.AxisTick)
            .setValue(y + rectGap)
            .setGridStrokeLength(0)
            .setTextFormatter(_ => entry.country)
            .setMarker((marker) => marker
                .setTextFillStyle(new SolidFill({ color: ColorHEX('#aaaf') }))
                .setTextFont(fontSettings => fontSettings.setSize(17))
            )
    }

addCountryHandler 

The function returns a single bar with the property of dimensions, data(entry), and labels.

    const addCountryHandler = entry => {
        const rectDimensions = {
            x: 0,
            y: y,
            width: entry.value,
            height: rectThickness
        }

Each country has its own rectangle series for different style:

        const rectSeries = chart.addRectangleSeries()
        const rect = rectSeries.add(rectDimensions)

Add TextBox element to bar:

        const label = chart.addUIElement(
            UILayoutBuilders.TextBox,
            { x: axisX, y: axisY }
        )
            .setOrigin(UIOrigins.LeftBottom)
            .setPosition({
                x: entry.value,
                y: y
            })
            .setText(entry.value.toLocaleString())
            .setTextFont(fontSettings => fontSettings.setSize(15))
            .setPadding(10)
            .setBackground((background) => background
                .setFillStyle(emptyFill)
                .setStrokeStyle(emptyLine)
            )

Set label title and position:

        const tick = createTickLabel(entry, y)

Set interval for the Y-axis:

        axisY.setInterval(-rectThickness, y)

Increase value of the Y-variable:

        y += rectThickness

Return Figure:

        return {
            entry,
            rect,
            label,
            tick,
        }
    }

addCountries

The function will work as a loop, executing the addCountryHandler function for each bar.

    // Loop for adding bars
    const addCountries = (entries) => {
        for (const entry of entries) {
            bars.push(addCountryHandler(entry))
        }
    }

sortCountries

The function will sort all data by date. Inside the “data” parameter, we will have all values inside the “members” element. The “raceDay” parameter will work as an initial value where our chart will begin. “raceDay” will use the value from the variable “initDay” (15). The result will be returned inside the “countryList” object.

    // Sorting and splicing array of data
    const sortCountries = (data, raceDay) => {

        let myday = (raceDay.getMonth() + 1) + '/' + raceDay.getDate() + '/' + raceDay.getFullYear().toString().substr(-2)
        const countries = {...data}

        
        // Map list of countries and sort them in the order (First sort by value, then by country)
        const countryList = Object.values(countries).map((c: any) => (
            { country: c.country, value: c.history[myday] }
        )).sort((a, b) => (a.value > b.value) ? 1 : (a.value === b.value) ? ((a.country > b.country) ? 1 : -1) : -1)

        // Keep only top 20 countries
        countryList.splice(0, countryList.length - 20)

        return countryList
    }

startRace

This function will orchestrate all previous functions that we declared. This function will be executed for the startRaceHandler function.

    // Loop for re-rendering list of data
    const startRace = (data) => {

        // Inital day of race
        let raceDay = new Date(2020, 2, initday)

        // Set title of chart 
        chart.setTitle(connectionError + ' COVID-19 cases ' + raceDay.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" }))

        // Get sorting data
        const sortedCountries = sortCountries(data, raceDay)

        // Check if the functions(startRace) has already done first pass
        if (bars.length > 0) {
            for (let i = 0; i < sortedCountries.length; i++) {

startRaceHandler

Here we will send the JSON data to the startRace function. We will start making an HTTP request (fetch), to get COVID data provided by LightningChart. This data was obtained by the CORONAVIRUS Tracker API. The result will be sent to the startRace function. If the request fails, the JSON file will be taken as a data source.

Inside of startRace, we will use the function mergeData. Some countries are divided by region, and it is needed to merge them. This applies more to data provided in real time.

    // Fetch all countries and history of cases
    fetch('https://www.arction.com/lightningchart-js-interactive-examples/data/covid/confirmed.json')
        .then(res => res.json())
        .then(data => {
            //Uncomment if want to use data from Lighning Charts
            
                const dat = [...data.locations]
                chart.startRace(mergeData(dat))
            

            //Uncomment if want to use json file
            //chart.startRace(mergeData(covid.members))
            
        })
        .catch(err => {
            yesterday = '2020-05-30T21:00:00.000Z'
            connectionError = 'Example of data'       
            chart.startRace(mergeData(covid.members))
        })

The last step would be to create the chart object and send this to startRaceHandler. All previous methods will be contained inside startRaceHandler.

// Create chart
const newchart = barChart({
    theme: Themes.darkTurquoise
})

// Start fetching
startRaceHandler(newchart)

UI Theme for the bar chart race JavaScript component

We can then specify the theme (Look and Feel) that works best for us.

NPM Start

The explanation and project are ready. The last step is to run our project. As in the other articles, we just need to run the command npm start in our terminal.

As we can see, the start race function will control the speed and the automatic classification of each bar. The countries will move up and down, based on the number of COVID cases. The Theme feature can change the perception of the chart, so I recommend trying all the options provided by LightningChart.

Conclusion

A bar chart race JavaScript can be a basic but very useful chart type for understanding and displaying statistical data. Many times, we decide to use office software, instead of experimenting with the use of programming libraries, because it can be complex for many of us.

In this exercise, it was demonstrated in a fairly simple way, how to implement an animated bar graph that helps to dynamically understand the situation that is being studied (in this example, the COVID cases in the world).

I invite you to try downloading this project and experience it on your own. Similarly, I invite you to experiment with the various themes (UI) that Lightning Chart provides us.

Finally, I would like to mention that this type of chart can be used in mobile applications, web applications, and desktop applications. The possibilities are many and could help you offer a complete and more professional product.

Regarding desktop applications, I recommend you visit our previous article: Develop a cross-platform medical electrocardiogram (ECG) application.

 Greetings!

Omar Urbano Software Engineer

Omar Urbano

Software Engineer

LinkedIn icon
divider-light

Continue learning with LightningChart