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