Skip to main content

World Cup 2018, part 1

· 5 min read
Simon Porritt

It's world cup time again and I've been looking for a good overview of how it's all progressing in the group stages. Being a computer programmer I of course spent a small amount of time looking for one done by someone else, and then decided to just do it myself. With the trusty jsPlumb Toolkit at my disposal I figure it'll be a doddle.


This post is from June 2018 but has been updated to reflect how to code this using version 5.x of the Toolkit. With another world cup around the corner I'm keen to dust this off and be ready to use it again.

The result#

Let's jump to the result of my endeavours and then we'll go through how I got to this point. Here's what all the groups look like on the 25th of June 2018:


The dataset#

The model for this visualisation is broken up into several files. For starters we have the list of teams, in this format (this is obviously not the full list):

[  {"code": "ru", "name": "Russia"},  {"code": "uy", "name": "Uruguay"},  {"code": "eg", "name": "Egypt"}] 

Then we have the groups:

[    {"id": "A", "teams": ["ru", "uy", "eg", "sa"]},    {"id": "B", "teams": ["es", "pt", "ir", "ma"]},    {"id": "C", "teams": ["dk", "fr", "au", "pe"]},    {"id": "D", "teams": ["hr", "ng", "is", "ar"]},    {"id": "E", "teams": ["br", "ch", "rs", "cr"]},    {"id": "F", "teams": ["mx", "de", "se", "kr"]},    {"id": "G", "teams": ["en", "be", "tn", "pa"]},    {"id": "H", "teams": ["jp", "sn", "co", "pl"]}]

And we maintain a list of matches, broken up into the various phases of the tournament - first, the groups, then the round of 16, then the semis, then the final + third place playoff:

{    groups:[        {"id":"1","teams":["ru","sa"],"date":0, "score":[5,0]},        {"id":"2","teams":["eg","uy"],"date":0, "score":[0,1]},        {"id":"3","teams":["ir","ma"],"date":0, "score":[1,0]},        {  ..  }    ],    roundof16: [],    semis:[],    final:{},    thirdPlace:{}}

This is an attenuated version of the dataset, and contains no entries for anything past the group stage, as it hasn't happened yet.

The rendering#

To render each group we're going to load each group's data with an instance of the Toolkit, and then render it using a Circular layout, with a flag representing each country, and an edge between each pair of countries that shows the result of the match that those countries played. As a reminder of what we showed above, here's Group A (as of the 25th of June 2018):


Processing the data#

To load a group we first have to massage the data. Our steps are:

  • load the group, and add each team as a node on the dataset
  • for each pair of teams, load the match they have played
  • if such a match exists, track who won/lost, or whether it was a draw. This information is stored in an array on each team, ie. for a given team we know how many matches they have won, lost and drawn.
  • Add an edge for the match, if it has been played, to the dataset, supplying the match score as data for the edge.
  • sort the teams by their win/loss/draw records, and assign a rank to each team, with 0 being the first-placed team in the group.

So as an example, in the visualisation shown above, the dataset we load into our Toolkit instance is:

{    "nodes": [        {            "code": "ru",            "name": "Russia",            "wins": 4,            "losses": 0,            "goals": 16,            "draws": 0,            "rank": 0        },        {            "code": "uy",            "name": "Uruguay",            "wins": 4,            "losses": 0,            "goals": 4,            "draws": 0,            "rank": 1        },        {            "code": "eg",            "name": "Egypt",            "wins": 0,            "losses": 4,            "goals": 2,            "draws": 0,            "rank": 2        },        {            "code": "sa",            "name": "Saudi Arabia",            "wins": 0,            "losses": 4,            "goals": 0,            "draws": 0,            "rank": 3        }    ],    "edges": [        {            "source": "ru",            "target": "sa",            "data": {                "scoreA": "5",                "scoreB": "0"            }        },        {            "source": "eg",            "target": "ru",            "data": {                "scoreA": "1",                "scoreB": "3"            }        },        {            "source": "eg",            "target": "uy",            "data": {                "scoreA": "0",                "scoreB": "1"            }        },        {            "source": "sa",            "target": "uy",            "data": {                "scoreA": "0",                "scoreB": "1"            }        }    ]}

Instantiating a Toolkit instance#

import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance();

Rendering the dataset#

toolkit.render(container, {    layout:{        type:"Circular"    },    view:{        nodes:{            default:{                template:'<div class="group-node" title="${name}" rank="${rank}"><img class="flag" src="/img/flags/1x1/${code}.svg" alt="${name} Flag"/></div>'            }        },        edges:{            default:{                overlays:[                    { type:"Label", options:{ label:"${scoreA}", location:0.3 }},                    { type: "Label", options: { label:"${scoreB}", location:0.7 }}                ]            }        }    },    defaults:{        connector:"Straight",        endpoint: "Blank",        anchor: "Center"    },    enableWheelZoom: false,    enablePan: false,    zoomToFit: true})

This is a summary of what's happening in the render call:

  • We use a layout of type Circular
  • We map a single node type "default" to a template, which writes out a simple element that contains an image with the given country's flag.
  • We map a single edge type "default", which will apply to all edges, and which declares two overlays, one for each team's score.
  • We provide default values for all edges - a Straight connector, a Blank endpoint, and a Center anchor.
  • We disable wheel zoom and pan
  • We instruct the Surface to zoom the contents to fit after a data load operation.

The next step#

In part 2 of this series we'll take a look at another way to visualise the progress of the World Cup - a tree view of the progress of teams from the group finals through the round of 16, quarter finals, semi finals and the final/third place playoff.