Skip to main content

World Cup 2018, part 2

· 5 min read
Simon Porritt

Following on from part 1 of this series, in which we created a visualisation for the progress of each of the group stages, in today's installment we're going to take a look at drawing the post-group stages using the Toolkit's Hierarchical layout.


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.

These stages start with the "round of 16", then go to the quarter finals, then semis, and then the final and the third place playoff. A suitable structure for visualising this is an inverted tree with two root nodes.

The result

Again we'll jump to the result of my endeavours and then we'll go through how I got to this point. On the 25th of June 2018 the group phases were not finished, but this visualisation shows the current 1st and 2nd each team in group:

Remember I mentioned this blog post is being updated after the fact? We now have all the match data for the 2018 world cup, so at the end of this post I've included a version of this visualisation with every match result.

The dataset

The model for this visualisation is discussed in part one of this series.

Processing the data

We build on part 1 by first processing the groups, which provides the information we need about who is placed first and second in each group, as the round of 16 matches are played between the first and second placed teams of the various groups.

In this visualisation, each node in the Toolkit represents a single match, which has two teams, a score, and optionally a penalty count. So the dataset consists of a node for each of the round of 16 matches, quarter finals and semi finals, and one for each of the final and the third place playoff - a total of 16 nodes. Each node's payload is in this format:

"id": "40",
"score": [ 1, 1 ],
"penalties": [ 3, 4 ],
"date": 20180704,
"teams": [
{ "id": "co" },
{ "id": "en" }

Edges in the visualisation model a team's progression through the stages, and consist of just the ID of the two matches:

"source": "40",
"target": "41"

The rendering

In this post we're using the Hierarchical layout to display an inverted tree, with the round of 16 as the top row, and the final/ and third place playoff in the bottom row.

Instantiating a Toolkit instance

import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance();

Rendering the dataset

const surface = toolkit.render(container, {
template:'<div class="match-node">' +
'<div class="team">' +
'<div class="flag">' +
'<img class="flag" src="/img/flags/1x1/${teams[0].code}.svg" alt="${teams[0].name} Flag"/>' + '</div>' +
'<div class="name">${teams[0].name}</div>' +
'<div class="score" data-penalties="${penalties[0]}">${score[0]}</div>' + '' +
'</div>' +
'<div class="team">' +
'<div class="flag">' +
'<img class="flag" src="/img/flags/1x1/${teams[1].code}.svg" alt="${teams[1].name} Flag"/>' +
'</div>' +
'<div class="name">${teams[1].name}</div>' +
'<div class="score" data-penalties="${penalties[1]}">${score[1]}</div></div>' +
endpoint: "Blank",
anchors: ["ContinuousTop", "ContinuousBottom"]
zoomToFit: true

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

  • We use a layout of type Hierarchical, with invert:true set.
  • We map a single node type "default" to a template, which writes out a simple element that contains the names of the two teams, their flags, the score, and also the penalty count, if present. Note that in the template we don't need to take into account the value being null: the Toolkit's template engine handles this for us.
  • We don't need to map an edge type as there's nothing special going on with edges - no overlays, no click listeners etc.
  • We provide default values for all edges - the StateMachine connector, a Blank endpoint, and for anchors we use ContinuousTop as the source anchor and ContinuousBottom as the target. This means that each connection gets assigned its own anchor point on the source and target nodes, and the Top and Bottom suffixes restrict the choice of face to those options. The layout is inverted, so in fact edges have the later stage match as their source and the earlier stage match as their target.
  • In this visualisation, unlike in part 1 of this series, wheel zoom and pan is enabled (they are by default; we just took out the directives that set them to false in part 1).
  • We instruct the Surface to zoom the contents to fit after a data load operation via the zoomToFit:true parameter.

The complete dataset

Fast forward a few years from the original date of this post and we've got all the match results for the 2018 world cup, so let's take a look. In this visualisation, though, we'll switch the orientation of the layout to vertical, to make better use of our screen real estate:

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

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