Wednesday 27 July 2022

Code indentation reduction using Guard Clauses

Excessive indentation seems to be a common plague among programmers, regardless of your years of experience. Ever so often, I find myself getting caught in multiple levels of logic, and the inevitable nesting that follows, makes my code terribly hard to read. I should know; I've been called out plenty of times on this!

Take this example in JavaScript.

function f (x, y)
{
  if (x)
  {
    if (y)
    {
      return y;
    }
    else
    {
      return x;
    }
  }
}


To combat this, programmers use early exit techniques, and recently, I discovered there was actually a term for this: guard clauses.

That's right, decades after my first Hello World program, I'm still learning new stuff. Perhaps even stuff I should have learned a long time ago. But, as they say, better late than never.

Now, in the example above, we don't do anything if x is false. There's no Else clause. So what we do, is exit early.

function f (x, y)
{
  if(!x) return;
 
  //if (x)
  //{
    if (y)
    {
      return y;
    }
    else
    {
      return x;
    }
  //}
}


So the code now looks like this.
function f (x, y)
{
  if(!x) return;
 
  if (y)
  {
    return y;
  }
  else
  {
    return x;
  }
}


See what we did there? We cut to the chase. Now the code just has one less If clause taking up the cognitive load. But is that enough? Not at all; we can actually simplify this further.

function f (x, y)
{
  if(!x) return;
  if(!y) return x;

  //if (y)
  //{
    return y;
  //}
  //else
  //{
  //  return x;
  //}
}


The code now looks like this. What we did was make the code return x automatically if y is false. And then the code returns y by default, but that line will never be reached if x or y is false. And there! Now there are no further If clauses to deal with. Two levels of nesting have been removed, and the code looks cleaner already.
function f (x, y)
{
  if(!x) return;
  if(!y) return x;

  return y;
}


So, these are guard clauses. They conduct checks, and pre-emptively exit the code when appropriate, so that no further processing needs to be done. Other than the obvious computational savings though, this also serves to reduce the cognitive load on the reader of the code.

A guard clause is like security
ejecting you early.

Think of guard clauses as bouncers at a night club guarding against unruly patrons and ejecting them when necessary.

An actual working example

Now let's take a look at an actual back-end script in PHP. This was used to create a temporary CSV file, write to it using data based on the current day, and then send to an FTP address. Obviously, the FTP login and password has been fudged here, but the rest of it is authentic enough. $result in this case is an array of objects derived from executing a query to the database. From $result, we want to write to the CSV file all data that does not fall on a weekend, and the total has to be a positive number.

function deliverDailyFile($result)
{
    $conn_id = ftp_connect('111.222.333.444');
    $login_result = ftp_login($conn_id, 'tthunder', 'thund3r$trike!!!');

    if ($login_result)
    {
        $folder = ABSPATH . 'cron/01/transactions/daily/';
        $filename =  "transactions_" . date('Y_m_d') . ".csv";
        $fp = fopen($folder . $filename, 'w');

        if ($fp)
        {
            $counter = 0;
        
            foreach ($result as $r)
            {
                if ($r->dayOfWeek != 0 && $r->dayOfWeek != 6)
                {
                    if ($r->total > 0)
                    {
                        $counter++;
                        fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
                    }
                }
            }
        
            ftp_pasv($conn_id, true);
            $uploaded = ftp_put($conn_id, 'transactions/' . $filename, $folder . $filename, FTP_BINARY, 0);
            
            if ($uploaded)
            {
                unlink($folder . $filename);
            }
            else
            {
                return false;
            }

            fclose($fp);
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
    
    return true;
}


Now, for what amounts to a very simple script, there's too much indentation and it is hard to read. It would be a lot harder to read once the code inevitably gets extended or altered. To remove the first level of indentation, we introduce a guard clause to check for the value of $login_result. If it is false, exit early, returning false.
function deliverDailyFile($result)
{
    $conn_id = ftp_connect('111.222.333.444');
    $login_result = ftp_login($conn_id, 'tthunder', 'thund3r$trike!!!');

    if (!$login_result) return false;

    //if ($login_result)
    //{
        $folder = ABSPATH . 'cron/01/transactions/daily/';
        $filename =  "transactions_" . date('Y_m_d') . ".csv";
        $fp = fopen($folder . $filename, 'w');

        if ($fp)
        {
            $counter = 0;
        
            foreach ($result as $r)
            {
                if ($r->dayOfWeek != 0 && $r->dayOfWeek != 6)
                {
                    if ($r->total > 0)
                    {
                        $counter++;
                        fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
                    }
                }
            }
        
            ftp_pasv($conn_id, true);
            $uploaded = ftp_put($conn_id, 'transactions/' . $filename, $folder . $filename, FTP_BINARY, 0);
            
            if ($uploaded)
            {
                unlink($folder . $filename);
            }
            else
            {
                return false;
            }

            fclose($fp);
        }
        else
        {
            return false;
        }
    //}
    //else
    //{
    //    return false;
    //}
    
    return true;
}


At this point, $login_result is definitely true if the code is still running. The next check is for $fp to be true.
function deliverDailyFile($result)
{
    $conn_id = ftp_connect('111.222.333.444');
    $login_result = ftp_login($conn_id, 'tthunder', 'thund3r$trike!!!');

    if (!$login_result) return false;

    $folder = ABSPATH . 'cron/01/transactions/daily/';
    $filename =  "transactions_" . date('Y_m_d') . ".csv";
    $fp = fopen($folder . $filename, 'w');

    if ($fp)
    {
        $counter = 0;

        foreach ($result as $r)
        {
            if ($r->dayOfWeek != 0 && $r->dayOfWeek != 6)
            {
                if ($r->total > 0)
                {
                    $counter++;
                    fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
                }
            }
        }

        ftp_pasv($conn_id, true);
        $uploaded = ftp_put($conn_id, 'transactions/' . $filename, $folder . $filename, FTP_BINARY, 0);

        if ($uploaded)
        {
            unlink($folder . $filename);
        }
        else
        {
            return false;
        }

        fclose($fp);
    }
    else
    {
        return false;
    }
    
    return true;
}


Therefore, the next guard clause checks for the value of $fp, and exits early if false. Thus, we remove another level of indentation.
function deliverDailyFile($result)
{
    $conn_id = ftp_connect('111.222.333.444');
    $login_result = ftp_login($conn_id, 'tthunder', 'thund3r$trike!!!');

    if (!$login_result) return false;

    $folder = ABSPATH . 'cron/01/transactions/daily/';
    $filename =  "transactions_" . date('Y_m_d') . ".csv";
    $fp = fopen($folder . $filename, 'w');
    
    if (!$fp) return false;

    //if ($fp)
    //{
        $counter = 0;

        foreach ($result as $r)
        {
            if ($r->dayOfWeek != 0 && $r->dayOfWeek != 6)
            {
                if ($r->total > 0)
                {
                    $counter++;
                    fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
                }
            }
        }

        ftp_pasv($conn_id, true);
        $uploaded = ftp_put($conn_id, 'transactions/' . $filename, $folder . $filename, FTP_BINARY, 0);

        if ($uploaded)
        {
            unlink($folder . $filename);
        }
        else
        {
            return false;
        }

        fclose($fp);
    //}
    //else
    //{
    //    return false;
    //}
    
    return true;
}


Can this be improved? Undoubtedly. There's too much indentation within the For-each loop.
function deliverDailyFile($result)
{
    $conn_id = ftp_connect('111.222.333.444');
    $login_result = ftp_login($conn_id, 'tthunder', 'thund3r$trike!!!');

    if (!$login_result) return false;

    $folder = ABSPATH . 'cron/01/transactions/daily/';
    $filename =  "transactions_" . date('Y_m_d') . ".csv";
    $fp = fopen($folder . $filename, 'w');
    
    if (!$fp) return false;

    $counter = 0;

    foreach ($result as $r)
    {
        if ($r->dayOfWeek != 0 && $r->dayOfWeek != 6)
        {
            if ($r->total > 0)
            {
                $counter++;
                fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
            }
        }
    }

    ftp_pasv($conn_id, true);
    $uploaded = ftp_put($conn_id, 'transactions/' . $filename, $folder . $filename, FTP_BINARY, 0);

    if ($uploaded)
    {
        unlink($folder . $filename);
    }
    else
    {
        return false;
    }

    fclose($fp);
    
    return true;
}


So we add a guard clause that checks if $r->dayOfWeek falls on a weekend, and we use the continue statement if so.
foreach ($result as $r)
{
    if ($r->dayOfWeek == 0 || $r->dayOfWeek == 6) continue;

    //if ($r->dayOfWeek != 0 && $r->dayOfWeek != 6)
    //{
        if ($r->total > 0)
        {
            $counter++;
            fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
        }
    //}
}


At this point, if the code is still running within the loop, $r->dayOfWeek is definitely a weekday. We can actually eliminate the last If block as well. It seems a little excessive, but sure, let's do it.
foreach ($result as $r)
{
    if ($r->dayOfWeek == 0 || $r->dayOfWeek == 6) continue;
    if ($r->total <= 0) continue;

    //if ($r->total > 0)
    //{
        $counter++;
        fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
    //}
}


This is the final result. We have reduced it to one If-else block. This mostly works for cases where nothing much happens in the Else part of the If-else block, so be cautioned against overusing this.
function deliverDailyFile($result)
{
    $conn_id = ftp_connect('111.222.333.444');
    $login_result = ftp_login($conn_id, 'tthunder', 'thund3r$trike!!!');

    if (!$login_result) return false;

    $folder = ABSPATH . 'cron/01/transactions/daily/';
    $filename =  "transactions_" . date('Y_m_d') . ".csv";
    $fp = fopen($folder . $filename, 'w');
    
    if (!$fp) return false;

    $counter = 0;

    foreach ($result as $r)
    {
        if ($r->dayOfWeek == 0 || $r->dayOfWeek == 6) continue;
        if ($r->total <= 0) continue;
        
        $counter++;
        fputcsv($fp, [$counter, $r->year, $r->month, $r->day, $r->num_transactions, $r->category, $r->total]);
    }

    ftp_pasv($conn_id, true);
    $uploaded = ftp_put($conn_id, 'transactions/' . $filename, $folder . $filename, FTP_BINARY, 0);

    if ($uploaded)
    {
        unlink($folder . $filename);
    }
    else
    {
        return false;
    }

    fclose($fp);
    
    return true;
}


Conclusion

Code works whether or not guard clauses are utilized. But working code is the bare minimum. Code should be easy to read, and intent made clear as much as possible. Guard clauses accomplish this, and consequently, makes code easier to maintain.

if (endPost) return;
T___T

Tuesday 19 July 2022

Spot The Bug: Pushing it with JavaScript

What's up, programmers?!

It's time for Spot The Bug once again, and JavaScript is the language of the day.

Meet your doom, bugs!

I was trying to make a simple soccer team builder using HTML, CSS and JavaScript. The HTML and CSS were fine, but where I inevitably screwed up was the JavaScript. This may be an important lesson for fledgling programmers, so listen up!

This was the code...
<!DOCTYPE html>
<html>
    <head>
        <title>Team Builder</title>

        <style>
            label
            {
                width: 5em;
                display: inline-block;
            }
        </style>

        <script>
            var players =
            [
                { "name": "Name", "position": "Position"}
            ];

            function addPlayer()
            {
                var name = document.getElementById("txtName").value;
                var pos = document.getElementById("ddlPos").value;

                if (newPlayers.length == 0) {
                    newPlayers = players.push({ "name": name, "position": pos});
                }
                    
                var content = "";

                for (var i = 0; i < newPlayers.length; i++)
                {
                    content += "<tr><td>" + newPlayers[i].name + "</td><td>" + newPlayers[i].position + "</td></tr>";
                }

                document.getElementById("newPlayers").innerHTML = content;
            }

            let newPlayers = [];
        </script>
    </head>

    <body>
        <label>Name:</label> <input id="txtName" />
        <br />
        <label>Position:</label>
        <select id="ddlPos">
            <option value="Defence">Defence</option>
            <option value="Midfield">Midfield</option>
            <option value="Forward">Forward</option>
        </select>
        <br />
        <button onclick="addPlayer()">Add Player</button>

        <hr />

        <table id="newPlayers">

        </table>
    </body>
</html>


The point was, nothing would be displayed in the newPlayers div if I entered nothing. But once I started entering stuff, there should be headings and the data that I entered.

What went wrong

All I ever got, no matter how many times I entered data, was this.



Looking at the console gave me this. So the value of newPlayers was an integer and not an array?!




Why it went wrong

This is a bit of a rookie error. I was under the impression that setting newPlayers this way would give newPlayers the value of players after the input was pushed to players.

newPlayers = players.push({ "name": name, "position": pos});


However, the push() method returns not the resultant array, but the length of the resultant array! Here's the specification for the push() method.

No wonder I couldn't apply a For loop to iterate through newPlayers, because newPlayers would be an integer!

How I fixed it

First, I should have made a copy of players using the slice() method.
if (newPlayers.length == 0) {
  newPlayers = players.slice();
}

var content = "";


Then I should have pushed the input.
if (newPlayers.length == 0) {
  newPlayers = players.slice();
}

newPlayers.push({ "name": name, "position": pos});

var content = "";


And now it worked!




Moral of the story

As always, when using any method or function, it is important to understand what the method returns, rather than what you think it returns.

Don't push() your luck,
T___T

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

Sunday 10 July 2022

Film Review: Black Mirror Series One (Part 3/3)

Time to take a look at the final episode, The Entire History of You.




The Premise

In a reimagined version of today's world, a device known as The Grain is implanted into human beings, which records all visual and audio events experienced by them. These can be replayed using another device known as a Redo, with zooming and analytics able to be run on these replays.


Society runs on these - we see customs officers request them to ascertain that the owners have not been in contact with any known persons of interest, and policemen request them for visual evidence when reports are made.

The Characters

Toby Kebbell is Liam Foxwell the lawyer. He somehow manages to look simultaneously befuddled, accusatory and disturbed. He's obsessive and not very likeable.

Jodie Whittaker, who six years later took over the mantle of Doctor Who, is his wife, Ffion Foxwell. I had no idea the name was spelled with two "f"s. I'm quite taken with her display, here. Sure, she gets a little dramatic, but maybe that's called for.

Tom Cullen as Jonas. Not a big fan of his performance. I feel like he isn't really believable as the guy who somehow keeps being able to get into the pants of women.

Phoebe Fox as Hallam. She cuts a suitably tragic figure as the victim of a "Gouging", and later on becomes the one-night stand of Jonas, which actually serves as an interesting plot point.

Rhashan Stone as Jeff, the black guy who works in Recruitment and obsesses over details. Entertainingly whiny.

Rebekah Staton as Colleen. Works in Grain Development. Not extremely interesting.

Mona Goodwin as Gina the babysitter. Doesn't have much to do except look uncomfortable, which she does acceptably well, I guess.

Amy Beth Hayes
as Lucy the hostess. Pleasant but superficial, with very little to do with the plot.

Jimi Mistry as her husband Paul. Serves as a dialogue point and a prop, nothing more.

The Mood

There is almost zero humor in this episode. The other episodes had moments of dark comedy - this one is just dark. Not visually, but tonally. Everything just seems dead serious and tragic.


And this at the end, when Liam Gouges himself. Grim!

What I liked

The story. It was a tight narrative, masterfully told with little to no dead weight in between the plot points.

The scene where Liam watches the Redo of his wife having an affair with Jonas. It is heartbreaking and the entire leadup to that was masterfully executed. What's even better is that only audio is heard and we, the audience, doesn't actually see anything.

The special effect of the eyes changing color to a luminous grey every time a person is doing a Redo, didn't strike me as all that special at first. Until this scene, where Liam and Ffion are having sex and reliving other memories at the same time.


This was powerful... especially when it's implied that Ffion is focusing on some other memory of someone else while she's having sex with her husband. Because we see what Liam's watching, but we don't see the same for Ffion.

The moral of the story seems to be that it's better to live in the present than keep reliving the past. From Jonas's account of how he spends his time masturbating to replays of his previous sexual conquests instead of having sex with the current women in his life, to Liam's obsessiveness with details from the past rather than enjoying what he has, the episode makes it easy to see why Nature causes memories to fade, and what would happen if this were not the case.

The scope of this tale is extremely relatable as well. Cheating spouses and infidelity is a naturally hard-hitting subject simply because it's such an everyday concern.

What I didn't

What happens when someone watches a Redo? Does that get recorded? Is that like a photocopy of a photocopy? So many questions!


This entire concept of The Grain is brilliant, but it has so many other implications. What about memories of people having sex? Can that be voluntarily peddled for porn, instead? What are the legalities here?

Conclusion

This episode is the crowning achievement in Black Mirror Series One. The premise and story are not only bleak, but also a cautionary tale as to the dangers of being able to recall everything in your life history with perfect clarity.

Sure, it could have been a lot more, but maybe it's just as well that the scope was trimmed down for this one story. Less is more, after all.

My Rating

9.5 / 10

Final thoughts on Black Mirror Series One

Love it. Love it. It's tech-related, though not overly techy, and the thought-provoking narrative throughout the series is: tech could be able to accomplish this, but how far should we take it before it becomes harmful? The National Anthem deals with the attention span of the general spectator public on new media. Fifteen Million Credits deals with the obsession of virtual users with useless, well, virtual stuff. And The Entire History of You touches on the culture of surveillance. All great stuff.

Resume viewing? Y/N
T___T

Thursday 7 July 2022

Film Review: Black Mirror Series One (Part 2/3)

Let us now examine the next episode in this series, Fifteen Million Merits!

The Premise

This episode begins with yet another shot of someone sleeping (seriously, is this some sort of pattern?) and we soon find out that our protagonist is in some kind of dystopian future where everything costs "merits" immediately, even toothpaste.


These merits are earned via peddling exercise bikes to generate power. The public is entertained by all kinds of programmes (ranging from simulations, porn and game shows) while performing this service, and every individual harbors dreams of making it big one day, and being able to get off their bikes.

The Characters

Daniel Kaluuya is Bingham Madsen, or just "Bing". I last saw him in Get Out (where he was excellent!) and Black Panther. This guy's facial expressions in this episode are a delight to watch, ranging from eye-rolling boredom, depression, hope and frenzied desperation.

Jessica Brown Findlay as Abi Khan, the talented singer and the subject of Bing's affections. She likes origami. When we first see her, she is sweet, pleasant and positively radiates serenity. The behavior is consistent as the show goes on, at least until her downfall.

Rupert Everett as Judge Hope. Wow, it's been a while since I saw this guy... probably twenty years ago or more, as the villain in Inspector Gadget.

Ashley Thomas, or "Bashy", as Judge Wraith, the man behind porn production studio Wraith Babes. Basically the lecherous black dude stereotype. But he does have some pretty good lines.

Man, just fucking kill yourself. You get heavy on me, dawg, I swear to God I'll kick your ass back to life just so I can cut your Goddamn head off.

Julia Davis as Judge Charity. Serves as the Paula Abdul archetype in the talent judges' panel.

Paul Popplewell plays Dustin with crassness, lewdness and sheer obnoxiousness.

Isabella Laughland is Swift, the girl who displays an interest in Bing. We don't see much of her, which is a shame, because I enjoyed their brief interactions.

Colin Carmichael as Kai, who seems to be a bit of a dim-wit, representing the kind who treat their online avatar too obsessively.

Kerrie Hayes has a grand time hamming it up as Glee. We only see her in a scene or two, but she is a hoot!

The Mood

The visuals are pretty bleak, which is quite apt for this story. The surroundings are angular and sterile, and everyone is dressed in shades of grey. Things get more colorful once we move to the virtual reality stage, but that somehow ratchets up the claustrophobia and drabness of it all.

What I liked

It is pretty cute how different hand (and even finger) gestures control different screen interactions.

Popup messages like these add to the sinister vibe. Not only are ads intrusive, they are mandatory. And if the viewer closes his eyes, the system refuses to continue until otherwise! Brrr! These ads are ubiquitous - at one point they even appear on the walls of urinals!


The concept of the Lemons - obese members of society who cannot contribute directly to producing power and thus are reduced to menial labor. They are made the butt of cruel jokes such as in games like Fattax and Gut Brothers, and the first-person shooter game that Bing plays even has him shooting them!

Call me a sap, but the interactions between Bing and Abi are pretty cute.



The bored, constipated looks of the staff of Hot Shot are practically a uniform. It's amusing.

Bing's expletive-laden speech to the judges of Hot Shot near the end. Daniel Kulaaya really nails this one with his raw anguish.

What I didn't

The brief flashes of nudity seem unnecessary. It's porn, we fucking get it.

The plot point where Abi takes the drug-laced drink Cuppliance and gets persuaded into doing porn. This just feels unrealistic and lazy.


I feel like the episode could have just ended right there, with Bing being tortured by scenes of Abi's porn. Because the rest of it is just shaky. It hinges too much on the concept of Cuppliance, which wasn't all that compelling to begin with.

Conclusion

This episode really nails the entire Social-Media-is-evil thing. The quest for approval, admiration and above all, stardom. The avatars were great and really act as a nod to present-day Metaverse even though this was aired ten years ago. The layers of ironies within this episode, plot twists and all, just about offset whatever wasn't perfect about it.

My Rating

9 / 10

Next

The Entire History of You

Monday 4 July 2022

Film Review: Black Mirror Series One (Part 1/3)

This film review is long overdue. I've been meaning to review the Black Mirror series for a while now, but things kept getting in the way. Black Mirror is a series of short stories in each Series, each one packed with sly social commentary on tech and its effects on the world.


It's a whole lot of dark comedy and thought-provoking drama. For this review of Black Mirror Series One, there are three different stories and I will be reviewing these stories separately.

Warning

Black Mirror is not only creepy little skits about tech, it's also full of very dark humor. Sometimes way dark. Violence and foul language are only the tip of the proverbial iceberg.

The Premise

For The National Anthem, it all kicks off with a panicked call to Prime Minister Michael Callow in the middle of the night. Princess Susannah has been kidnapped and will be executed unless Prime Minister Michael Callow has sex with a pig on live TV. What follows is the reactions of the British government, the Press, and the public.

The Characters

Rory Kinnear as Prime Minster Michael Callow. Kinnear doesn't look like much, but here he delivers a pretty powerful performance - befuddlement anxiety, followed by moments of rage. It's gripping stuff.


Anna Wilson-Jones as Jane Callow. Wide-eyed, teary and dramatic. The actress does a fine job considering the sheer silliness of the entire premise. She is supposed to be a victim of the entire thing as well, though somehow doesn't come off as very sympathetic.

Lindsay Duncan plays Alex Cairns, Press Secretary with a potent combination of political savvy, starchiness and shrewdness.

Tom Goodman-Hill
as Tom Bilce, who appears to be some kind of liaison to the Press. He doesn't do much with his lines near the beginning, but as the show runs along, we see him becoming increasingly frazzled.

Donald Sumpter as Julian Hereford, Minister of Defence. Grave and solemn. Guy's face doesn't change much.

Alex Macqueen plays Special Agent Callett, with stony-faced gravitas. Seriously, this guy wouldn't be out of place in a Hitman movie.

Lydia Wilson as Princess Susannah. She puts up a great and convincing, if perhaps a little stiff, portrayal of a young woman scared to death of her kidnappers.

Jay Simpson as stand-in performer Rod Senseless. Cocky but enthusiastic dude, and fun to watch. Wish he had more screen-time.

Alastair Mackenzie as Martin, Editor-in-Chief of the Press. Not that much is seen of him, though he alternates between exasperation and eagerness as the biggest story of the century breaks and he's torn between wanting to do his job and needing to adhere to the laws of the land.

Chetna Pandya as Malaika, the pretty journalist who keeps being shown taking naked selfies of herself. Why is she even in this?!

Patrick Kennedy
as sleepy-eyed, almost sheepish Section Chief Walker. Mostly there as furniture.

The Mood

The lighting in the early stages is a nice contrast to the full-color video of Princess Susannah. Somehow it really brings across the bleak war room vibe. The story starts out dark visually, and gets darker thematically.



What I liked

The scary power of the Internet is on full display here. The British Government try to kill a story, but the fact that it was uploaded on YouTube means that the video is here to stay. The speed at which information is disseminated in the modern era, is thoroughly underlined.



I especially love how various segments of the public is shown discussing it, including that scene in the press and how they wrestle with whether or not they should work on that story. The medical team discusses whether or not the PM will actually fuck a pig on live TV. Bonus points for the entirely cavalier way that guy is holding those crutches!


OMG the hashtags are to die for.

There's a deliciously cruel twist near the end, and a bold statement about the attention of the public today.

The ending was a real downer. The PM's marriage is utterly ruined and it wasn't like he deserved it. Still, it's a realistic ending and for that, I give it props.

What I didn't

The reporter takes a naked selfie of herself in a restroom... for what? The viewers' cheap titillation?


In fact, the entire running gag of her taking sexually explicit selfies and sending them to her contact for information, just feels like a gigantic waste of time.

Conclusion

This episode is a great commentary on new media and the Internet, the public consciousness and the power of public perception. It has been more than a decade since this came out, but the message within still reverberates.

My Rating

8 / 10

Next

Fifteen Million Merits