Monday, 8 November 2021

Web Tutorial: The Highcharts Column Chart

In recent years, I've been doing quite a bit of work on data visualization. One of the nicer libraries out there on the market, from the few sampes I've seen, is Highcharts.

Today, I will be attempting to replicate what I previously did for bar charts in D3 and the old-fashioned way, but this time, using Highcharts. Let's explore what crazy new features we can unlock!

The data I will be using today is, again, from Liverpool Football Club. I am using updated statistics appearance and goal statistics for certain players, up to the 2020/2021 season.

On to the code!

As usual, we begin with some HTML. There should be a link to the Highcharts JavaScript file.
<!DOCTYPE html>
<html>
    <head>
        <title>Bar Chart</title>

        <style>

        </style>

        <script src="https://code.highcharts.com/highcharts.js"></script>

        <script>

        </script>
    </head>

    <body>

    </body>
</html>


Here, let's make sure we have a placeholder for the chart and the controls. These are divs with appropriate ids.
<body>
    <div id="container">

    </div>

    <div id="dashboard">

    </div>

</body>


The CSS styles show that both these divs will take up 100% width of the screen. container will have a height of 600 pixels while dashboard will be 100 pixels tall, and text will be aligned center.
<style>
    #container
    {
        width: 100%;
        height: 600px;
    }

    #dashboard
    {
        width: 100%;
        height: 100px;
        text-align: center;
    }

</style>


In the dashboard div, add label tags with select tags within. They will have ids ddlSeason and ddlStat respectively.
<div id="dashboard">
    <label for="ddlSeason">
        SEASON
        <select id="ddlSeason">

        </select>
    </label>

    <label for="ddlStat">
        STATISTICS
        <select id="ddlStat">
            <option value="appearances" selected>Appearances</option>
            <option value="goals">Goals</option>
        </select>
    </label>

</div>


Add two options for ddlStat. We are adding them manually since there are only two. They are appearances and goals.
<label for="ddlStat">
    STATISTICS
    <select id="ddlStat">
        <option value="appearances" selected>Appearances</option>
        <option value="goals">Goals</option>

    </select>
</label>


Now add styling for label tags. We set the display property to inline-block because we are going to set the widths as well. Color and font is up to you, though I'm going with the Liverpool red.
<style>
    #container
    {
        width: 100%;
        height: 600px;
    }

    #dashboard
    {
        width: 100%;
        height: 100px;
        text-align: center;
    }

    label
    {
        display: inline-block;
        width: 20em;
        font-family: verdana;
        color: rgba(200, 0, 0, 1);
    }

</style>


Now you see the controls at the dashboard area.


The Data

Now in the script tag, add an event listener to fire off when the page loads. Then outside of it, declare a constant, data. It will be an array.
<script>
    document.addEventListener("DOMContentLoaded", function () {
                
    });

    const data = [];

</script>


Here, we push an object into the data array with a key, "2016/2017". That is the name of the season and we will be using the season names for keys. Look at how the data is structured. The object has categories, appearances and goals as arrays. Highcharts will be using these as data series.
document.addEventListener("DOMContentLoaded", function () {
    data["2016/2017"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Danny Ings"],
        "appearances": [41, 27, 29, 2],
        "goals": [12, 1, 13, 0]
    };    
            
});


Here, we input the rest of the seasons.
document.addEventListener("DOMContentLoaded", function () {
    data["2016/2017"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Danny Ings"],
        "appearances": [41, 27, 29, 2],
        "goals": [12, 1, 13, 0]
    };

    data["2017/2018"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Danny Ings", "Alex Oxlade-Chamberlain", "Mohd Salah"],
        "appearances": [54, 20, 44, 14, 42, 52],
        "goals": [27, 1, 20, 1, 5, 44]
    };

    data["2018/2019"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Alex Oxlade-Chamberlain", "Mohd Salah", "Fabinho"],
        "appearances": [48, 44, 50, 2, 52, 41],
        "goals": [16, 1, 26, 0, 27, 1]
    };

    data["2019/2020"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Alex Oxlade-Chamberlain", "Mohd Salah", "Fabinho"],
        "appearances": [52, 40, 47, 43, 48, 39],
        "goals": [12, 4, 22, 8, 23, 2]
    };

    data["2020/2021"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Alex Oxlade-Chamberlain", "Mohd Salah", "Fabinho"],
        "appearances": [48, 28, 48, 17, 51, 42],
        "goals": [9, 1, 16, 1, 21, 0]
    }; 
               
});


Declare ddlSeason. We are going to set it to the element in the DOM. Then we declare keys and use it to get all the keys that we have entered into the data array so far.
document.addEventListener("DOMContentLoaded", function () {
    data["2016/2017"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Danny Ings"],
        "appearances": [41, 27, 29, 2],
        "goals": [12, 1, 13, 0]
    };

    data["2017/2018"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Danny Ings", "Alex Oxlade-Chamberlain", "Mohd Salah"],
        "appearances": [54, 20, 44, 14, 42, 52],
        "goals": [27, 1, 20, 1, 5, 44]
    };

    data["2018/2019"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Alex Oxlade-Chamberlain", "Mohd Salah", "Fabinho"],
        "appearances": [48, 44, 50, 2, 52, 41],
        "goals": [16, 1, 26, 0, 27, 1]
    };

    data["2019/2020"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Alex Oxlade-Chamberlain", "Mohd Salah", "Fabinho"],
        "appearances": [52, 40, 47, 43, 48, 39],
        "goals": [12, 4, 22, 8, 23, 2]
    };

    data["2020/2021"] =
    {
        "categories": ["Roberto Firminho", "Jordan Henderson", "Sadio Mané", "Alex Oxlade-Chamberlain", "Mohd Salah", "Fabinho"],
        "appearances": [48, 28, 48, 17, 51, 42],
        "goals": [9, 1, 16, 1, 21, 0]
    };

    var ddlSeason = document.getElementById("ddlSeason");
    var keys = Object.keys(data);     
           
});


And then we use a For loop to iterate through the keys array. And within the For loop, we append options to the drop-down list ddlSeason.
var ddlSeason = document.getElementById("ddlSeason");
var keys = Object.keys(data);

for(let i = 0; i < keys.length; i++)
{
    var option = document.createElement("option");
    option.value = keys[i];
    option.innerHTML = keys[i];
    ddlSeason.appendChild(option);
}


And here we see that the seasons are now in the first drop-down list.


Visualizing the data

Now we're going to weave some Highcharts magic! You will see firsthand how Highcharts generates a chart using just the data and configuration options you put in.

Add a call to function renderBarChart().
for(let i = 0; i < keys.length; i++)
{
    var option = document.createElement("option");
    option.value = keys[i];
    option.innerHTML = keys[i];
    ddlSeason.appendChild(option);
}

renderBarChart();


We'll declare renderBarChart() here.
    renderBarChart();                
});

function renderBarChart()
{

}


const data = [];


We start off the function by declaring season and stat, and setting them to the currently selected values of ddlSeason and ddlStat respectively.
function renderBarChart()
{
    var season = document.getElementById("ddlSeason").value;
    var stat = document.getElementById("ddlStat").value;

}


Now this next line is what renders the bar chart into the container div.
function renderBarChart()
{
    var season = document.getElementById("ddlSeason").value;
    var stat = document.getElementById("ddlStat").value;

    const chart = Highcharts.chart("container", {

    });

}


We are going to pass in some values into the configuration object. We then pass in the chart property, which is in turn an object. We have to specify the property type, which is "column". The next few are entirely optional. I have opted to give my chart a rounded red border.
const chart = Highcharts.chart("container", {
    chart:
    {
        type: "column",
        borderColor: "rgba(200, 0, 0, 1)",
        borderRadius: 10,
        borderWidth: 2,
    }

});


You will see that the default title, since we have not yet specified it, is "Chart title".


Editor's Note: The screenshots provided for the title are inaccurate due to an error in the code, retroactively detected.

Let's specify this using the title property. Again, it's an object and I have opted to provide some (entirely optional) styling.
const chart = Highcharts.chart("container", {
    chart:
    {
        type: "column",
        borderColor: "rgba(200, 0, 0, 1)",
        borderRadius: 10,
        borderWidth: 2,
    },
    title:
    {
        text: "Liverpool FC",
        style: { "color": "rgba(200, 0, 0, 1)", "font-size": "2.5em", "font-weight": "bold" }
    }

});


There you go!


Let's add a subtitle.
title:
{
    text: "Liverpool FC",
    style: { "color": "rgba(200, 0, 0, 1)", "font-size": "2.5em", "font-weight": "bold" }
},
subtitle:
{
    text: "Football statistics by TeochewThunder",
    style: { "color": "rgba(200, 0, 0, 0.8)", "font-size": "0.8em" }
}


Here's how it looks.


Now let's add the xAxis and series properties. These are what creates the bars. xAxis is an object which contains the categories property - it's an array and we will use the categories property of the currently selected season of the data array. That is in turn another array, and will contain the player names.
subtitle:
{
    text: "Football statistics by TeochewThunder",
    style: { "color": "rgba(200, 0, 0, 0.8)", "font-size": "0.8em" }
},
xAxis:
{
    categories: data[season]["categories"]
}


The series property is an object which contains the name and data properties. name is just a label - we will use season and stat to create it. For data, we will use the stat property of the currently selected season of the data array.
subtitle:
{
    text: "Football statistics by TeochewThunder",
    style: { "color": "rgba(200, 0, 0, 0.8)", "font-size": "0.8em" }
},
xAxis:
{
    categories: data[season]["categories"]
},
series:
[                        
    {
        name: season + " " + stat,
        data: data[season][stat]
    }
]


Here are the bars! There's a nice animation too. And we didn't even have to code it! The default color is a light blue.


We add the specification for colors. This time, it's just an array, not an object.
subtitle:
{
    text: "Football statistics by TeochewThunder",
    style: { "color": "rgba(200, 0, 0, 0.8)", "font-size": "0.8em" }
},
colors: ["rgba(200, 0, 0, 1)"],
xAxis:
{
    categories: data[season]["categories"]
},


Presto!


Hey we can pretty this up a bit by adding a rounded yellow border.
{
    name: season + " " + stat,
    data: data[season][stat],
    borderRadius: 5,
    borderColor: "rgba(255, 200, 0, 1)",
    borderWidth: 3

}


Nice! Also see that when you mouse over one of the bars, there's additional info about it.


Now, we don't want the word "values" at the side. It's redundant. So do this. Add the yAxis property. It's an object that contains the title property. Here you can specify the text you want in place of "values", and I'm just going to specify an empty string. I also specify gridLineColor and tickColor to use a translucent red, but this is purely cosmetic.
xAxis:
{
    categories: data[season]["categories"]
},
yAxis:
{
    title:
    {
        text: ""
    },
    gridLineColor: "rgba(200, 0, 0, 0.2)",
    tickColor: "rgba(200, 0, 0, 0.2)"
},

series:
[                        
    {
        name: season + " " + stat,
        data: data[season][stat],
        borderRadius: 5,
        borderColor: "rgba(255, 200, 0, 1)",
        borderWidth: 3
    }
]


Yep.


Adding an average line

The easiest way to implement this in Highcharts, is by adding a spline to the chart. But before that, we need to calculate the data. First, declare an empty array, arrAverage and total, which is initialized to 0.
var season = document.getElementById("ddlSeason").value;
var stat = document.getElementById("ddlStat").value;

var arrAverage = [];
var total = 0;


const chart = Highcharts.chart("container", {


Then use a For loop to iterate through the selected dataset. Add all the values found to total.
var arrAverage = [];
var total = 0;

for(let i = 0; i < data[season][stat].length; i++)
{
    total += data[season][stat][i];
}


Iterate through the dataset again. This time we will push the calculated average into arrAverage. This means you will get an array full of identical values. Fear not, this is exactly what we want.
var arrAverage = [];
var total = 0;

for(let i = 0; i < data[season][stat].length; i++)
{
    total += data[season][stat][i];
}

for(let i = 0; i < data[season][stat].length; i++)
{
    arrAverage.push(parseFloat((total / data[season][stat].length).toFixed(2)));
}


Now add one more object in the series array, just before the part which renders the bars (or "columns", as Highcharts likes to call them). Here, we specify the name, type (use "spline" to ensure you get a line), and data which is arrAverage.
series:
[                        
    {
        name: "Average " + stat + " for " + season,
        type: "spline",
        data: arrAverage
    }, 
                       
    {
        name: season + " " + stat,
        data: data[season][stat],
        borderRadius: 5,
        borderColor: "rgba(255, 200, 0, 1)",
        borderWidth: 3
    }
]


Now the line appears!


I kind of want the line to be a different color and use a dotted line, so I'm going to add these specifications. These are optional, by the way.
series:
[                        
    {
        name: "Average " + stat + " for " + season,
        type: "spline",
        data: arrAverage,
        lineColor: "rgba(255, 200, 0, 1)",
        lineWidth: 3,
        dashStyle: "Dot",
        marker:
        {
            fillColor: "rgba(255, 200, 0, 1)"
        }

    },                        
    {
        name: season + " " + stat,
        data: data[season][stat],
        borderRadius: 5,
        borderColor: "rgba(255, 200, 0, 1)",
        borderWidth: 3
    }
]


Now you see that the line looks different.


One last thing...

Add a change handler to the drop-down lists. This ensures that every time you change a value, the chart re-renders.
<label for="ddlSeason">
    SEASON
    <select id="ddlSeason" onchange="renderBarChart()">

    </select>
</label>

<label for="ddlStat">
    STATISTICS
    <select id="ddlStat" onchange="renderBarChart()">
        <option value="appearances" selected>Appearances</option>
        <option value="goals">Goals</option>
    </select>
</label>


We're done!

This was awesome. And so little effort too.

Highcharts does make a lot of things easier with their sensible defaults. I hope to bring more web tutorials like this, to this blog soon.

Keep column and carry on,
T___T

No comments:

Post a Comment