Skip to main content

World Cup 2018, part 1

· 6 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.

note

This post was originally written in 2018, and at the time I was hoping to re-use this code when the next World Cup came around. For 2022 we've pulled this post into a standalone site - check it out at https://fifaworldcup.jsplumbtoolkit.com

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:

A
B
C
D
E
F
G
H

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):

A

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.


Start a free trial

Sending your evaluation request...

Interested in the concepts discussed in this article? Start a free trial and see how jsPlumb can help bring your ideas to market in record time.


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

Not a user of the jsPlumb Toolkit but thinking of checking it out? Head over to https://jsplumbtoolkit.com/trial. It's a good time to get started with jsPlumb.