Friday 15 July 2022

Web Tutorial: Highcharts Pie Chart

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

No comments:

Post a Comment