Back in March, I walked you through how to create a
line chart in Highcharts. This was done using code for a
bar chart we had already created, also in Highcharts.
What we are going to do today is similar - we will create a dashboard-driven pie chart in Highcharts, using the original code we wrote for the bar chart. The code is in this repository, and we will be using the same data and HTML structure. For details and explanations to why some things are done a certain way, please refer to the previous web tutorial.
This is what the beginning looks like. I have removed most of the configuration, and changed the names of functions and titles to reflect what we are doing here today.
<!DOCTYPE html>
<html>
<head>
<title>Pie Chart</title>
<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>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script>
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);
for(let i = 0; i < keys.length; i++)
{
var option = document.createElement("option");
option.value = keys[i];
option.innerHTML = keys[i];
ddlSeason.appendChild(option);
}
renderPieChart();
});
function renderPieChart()
{
var season = document.getElementById("ddlSeason").value;
var stat = document.getElementById("ddlStat").value;
const chart = Highcharts.chart("container", {
});
}
const data = [];
</script>
</head>
<body>
<div id="container">
</div>
<div id="dashboard">
<label for="ddlSeason">
SEASON
<select id="ddlSeason" onchange="renderPieChart()">
</select>
</label>
<label for="ddlStat">
STATISTICS
<select id="ddlStat" onchange="renderPieChart()">
<option value="appearances" selected>Appearances</option>
<option value="goals">Goals</option>
</select>
</label>
</div>
</body>
</html>
This is all you should see here. The drop-down lists are still there, and will function as required.
For a pie chart, the data we need will be in the form of an array. In the
renderPieChart() function, declare
values as an empty array.
var season = document.getElementById("ddlSeason").value;
var stat = document.getElementById("ddlStat").value;
var values = [];
const chart = Highcharts.chart("container", {
});
Each array element will have two properties -
name and
y, where
name is the label and
y is the value. To that end, we will need to massage the data a bit. Use a
For loop to go through the appropriate selected season's
categories property.
var season = document.getElementById("ddlSeason").value;
var stat = document.getElementById("ddlStat").value;
var values = [];
for(let i = 0; i < data[season]["categories"].length; i++)
{
}
const chart = Highcharts.chart("container", {
});
Push an object into
values. The
name property of the object will be the current element in
categories, and the
y property will be that season's selected stat (which is the element in that stat's array pointed to by
i).
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
}
We have massaged the array, but since we have not actually used it yet, nothing will be changed in the display. What we need to do next is work on the configuration. First, declare the
chart property. It has a single property,
type. Set that to "pie". Simple, right?
const chart = Highcharts.chart("container", {
chart:
{
type: "pie",
}
});
Set the next two properties. It's a stylistic choice, so don't worry too much about it.
chart:
{
type: "pie",
},
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" }
}
Coming along nicely.
Now we declare the general color scheme using the
colors array. It's a deep
red.
subtitle:
{
text: "Football statistics by TeochewThunder",
style: { "color": "rgba(200, 0, 0, 0.8)", "font-size": "0.8em" }
},
colors: ["rgba(200, 0, 0, 1)"]
We declare the
series property. This is a big one. The
name property is a concatenation of
season and the stat selected. For the
data property, the value is
values, the array we created earlier on.
colors: ["rgba(200, 0, 0, 1)"],
series:
[
{
name: season + " " + stat,
data: values
}
]
Voila! We have a pie chart with labels and all. Try playing around with the drop-down lists to ensure that it works.
And if you mouse over, you'll see the
name property!
Good enough for now...
... but it can be better. Let's make a little change. Add the
borderWidth property, and set it to 3.
series:
[
{
name: season + " " + stat,
data: values,
borderWidth: 3
}
]
The gap has widened between pie slices. A minor improvement.
Now take a look at 2017/2018 goals. The data is kind of clear already, but let's improve this.
Use the
sort() method on the
values array, sorting by the
y property. Here's
a bit of background on the
sort() method.
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
}
values.sort((a, b) => parseFloat(b.y) - parseFloat(a.y));
const chart = Highcharts.chart("container", {
chart:
{
type: "pie",
},
Now that the data has been sorted, the pie is visually easier to read.
Add the
plotOptions property. In it, there will be a single property, named
pie. In
pie, add the property
allowPointSelect and set it to
true. Then add the
cursor property and set it to "pointer".
colors: ["rgba(200, 0, 0, 1)"],
plotOptions:
{
pie:
{
allowPointSelect: true,
cursor: "pointer"
}
},
series:
[
{
name: season + " " + stat,
data: values,
borderWidth: 3
}
]
Now if you click on a pie slice, it will jut out. Click on it again, and it will slide back in! Cool, eh?
There's more to be done here! Add the property
colors. Its value will be the
colors array.
pie:
{
allowPointSelect: true,
cursor: "pointer",
colors: colors
}
We'll define the
colors array at the beginning of the
renderPieChart() function.
function renderPieChart()
{
var season = document.getElementById("ddlSeason").value;
var stat = document.getElementById("ddlStat").value;
var values = [];
var colors = [];
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
}
And in the
For loop where we push data into the
values array, we will push stuff into the
colors array as well.
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
colors.push();
}
First, we want to get the same deep
red we've been using.
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
colors.push("rgba(200, 0, 0, 1)");
}
But we'll be performing some operations on that, so pass that string into the
color() method of the
Highcharts object.
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
colors.push(Highcharts.color("rgba(200, 0, 0, 1)"));
}
Brighten it using the
brighten() method, by a cumulative factor of 0.1 for each slice... but this only works if you have less than 10 values in your data array. We're good for now, so carry on.
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
colors.push(Highcharts.color("rgba(200, 0, 0, 1)").brighten(i * 0.1));
}
Round it up with the
get() method.
for(let i = 0; i < data[season]["categories"].length; i++)
{
values.push({ "name": data[season]["categories"][i], "y": data[season][stat][i]});
colors.push(Highcharts.color("rgba(200, 0, 0, 1)").brighten(i * 0.1).get());
}
And now we have a beautiful monochrome pie chart! The slices are progressively lighter in shade!
Data Labels
One final touch! We will fiddle with the data labels next. First, let's change the color of the labels. Add the
dataLabels property. Within it, the color property should specify a nice
yellow. Set
enabled to
true, or the labels will not show up.
pie:
{
allowPointSelect: true,
cursor: "pointer",
colors: colors,
dataLabels:
{
color: "rgba(255, 200, 0, 1)",
enabled: true
}
}
There you go.
Yellow labels!
Now declare the
format property. We have as its value, a template string that has HTML in it. You can figure out for yourselves what this shows!
pie:
{
allowPointSelect: true,
cursor: "pointer",
colors: colors,
dataLabels:
{
color: "rgba(255, 200, 0, 1)",
enabled: true,
format: "{point.name}<br>{point.y} " + stat
}
}
Nevermind, I'll show you.
Now add the
distance property, and set it to a negative value, say, -50.
dataLabels:
{
color: "rgba(255, 200, 0, 1)",
enabled: true,
format: "{point.name}<br>{point.y} " + stat,
distance: -50
}
And the labels are now closer to the center of the pie!
The smaller slices don't really matter as much. So we add the
filter property. In it, we set the
property,
operator and
value properties to state that we want to show only labels for slices that have percentages over 5.
dataLabels:
{
color: "rgba(255, 200, 0, 1)",
enabled: true,
format: "{point.name}<br>{point.y} " + stat,
distance: -50,
filter:
{
property: "percentage",
operator: ">",
value: 5
}
}
And now you see the slice for Danny Ings is still there, but the distracting label is gone!
Now that we have the values in the labels, it makes no sense to have them in a hover. So convert them to percentages instead. We do this by specifying the
tooltip property. The
pointFormat property within
tooltip will have a template string with HTML.
subtitle:
{
text: "Football statistics by TeochewThunder",
style: { "color": "rgba(200, 0, 0, 0.8)", "font-size": "0.8em" }
},
tooltip:
{
pointFormat: "{series.name}: <b>{point.percentage:.1f}%</b>"
},
colors: ["rgba(200, 0, 0, 1)"],
Now when you hover, this is what you see - the percentage!
Highcharts is so cool!
We've barely even scratched the surface. There is so much that can be configured and customized. We could, for example, add a legend (though that would work horribly with a monochrome color scheme) or turn this into a donut chart. We could also set one slice as selected by default. The possibilities seem endless.
I've got my pie on you!
T___T