How to display a bracket ?

The purpose of displaying a bracket is to describe how participants progress in the competition, how they move from one match to another. Sometimes, a bracket might be a bit too complex or too big to be displayed in a readable way. In this case, it is better to revert to a simplified view (such as a list of matches) instead of trying to display an accurate bracket in an unreadable way.

This guide will focus on duel-based brackets (single elimination, double elimination, gauntlet and custom brackets). FFA-based brackets are not covered in this guide because they are usually too complex to be properly displayed with a bracket display.

A bracket is first and foremost a graph: an abstract model for representing relationships between objects. We will later refer to nodes for the objects and links for the relationships. In the case of a bracket, nodes represent matches and links represent the connections between these matches.

A bracket is more precisely a directed acyclic graph. Directed means that the links have a direction (competitors move from one match to another in one direction). Acyclic means that there can be no cycle within the bracket (competitors may not be moved to a match that they have already played).

Note: People might sometimes confuse a bracket with a tree because it looks like a tree and, in some cases, functions like a tree. This is the case for a duel-based single elimination bracket. But this is not the case for any bracket that sends more than one participant somewhere else in the bracket (such as a double elimination bracket, FFA-based brackets or even a single elimination bracket with a 3rd/4th place decider)

Although a bracket is technically a graph (and sometimes a tree), it is usually displayed as if it were a tree. For readability purposes, the display of a bracket often focuses on drawing only winner links. Loser links are never drawn, as if they did not exist. By ignoring the losers links, it allows us to treat the bracket as a directed binary tree, some very specific kind of graph.

Note: When dealing with directed trees (or directed graphs), the direction is usually inverted when compared to the flow of competitors progressing in the bracket.

A directed binary tree allows us to describe more precisely the bracket structure. A node can have at most one parent and up to two children. From the node's perspective, there can be up to one link that arrives in the node (coming from the parent), and there can be up to two links that leaves the node (targeting the child nodes)

Diagram of a directed binary tree
A directed binary tree

Treating a bracket as a tree greatly helps the task of displaying duel-based brackets, as we can now use tools adapted to trees, rather than only graph-based tools.

Displaying a bracket implies three major steps. The first step is to load the complete bracket data, including nodes, links and match data. The second step is to place nodes and links on a plane using abstract coordinates. The third step is to use these coordinates to render the bracket in HTML.

  1. Load the complete bracket data
  2. Calculate coordinates
  3. Render the bracket in HTML

To display a bracket, we first need to load all the data required to draw the bracket. This implies both the tree structure, including nodes and links, and the match data. The tree structure will help us in drawing the shape of the bracket. The match data is required to display each match with the needed information (opponents, outcome, ...)

Data structure

As seen previously, nodes are actually matches and links represent the connections between these matches. This data is stored in bracket nodes and bracket links.

A bracket node is nothing more than a match in which we have added some extra node data. It therefore contains all match-related data, including the opponents, participants and outcome. It also contains some tree-specific information such as depth and branch.

A bracket link represents a connection between two nodes. It describes how participants move from one node to another and is therefore defined by a source and a target. The source is defined by a node and a match outcome (example: "Winner of match A"). The target is defined by the opponent of a match. (example: "Opponent 2 of match B").

Since links within a bracket can never point to the same source or the same target, the bracket link is directly stored in the target match opponent. A bracket link is therefore a match opponent with some extra link data that describe the source node id and type.

Learn more about the bracket data structure.

Fetch bracket data

Since bracket nodes and bracket links are stored in matches and match opponents, it is possible to load them using a single endpoint:

Several filters are available to reduce the amount of data fetched in the API. This might be useful when displaying only part of a bracket or a single bracket group. You can also fetch several stages or groups at once even if they are technically different brackets.

Notice that the endpoint involves a Range that can not exceed 128. We do not recommend displaying large brackets for readability purposes. However, if you need to display a large bracket involving more than 128 nodes (matches), you will need to perform multiple requests to fetch all nodes.

This second step is probably the least obvious one. Its main purpose is to place all the nodes on an abstract plane using {x,y} coordinates. These coordinates do not need to be set using pixels or any other unit. They represent abstract positions that allow us to see more or less where the matches should be placed.

Links do not need any coordinates because they connect two nodes that will already have {x,y} coordinates. The way links are drawn is handled in the second step (rendering in HTML). No additional calculation is required for links.

There are several ways to calculate such coordinates. We will focus on using a simple method to properly handle all duel-based brackets, but consider that other methods may be used to display a bracket differently. The method we will focus on is a graph traversal.

Graph traversal

A graph traversal is a process that aims to visit each node of a graph by using the links to follow a path in the graph. The visiting of a node is an abstract notion used to indicate that a process will take place on each node.

This traversal will run a "visiting process" on each node in a particular order. This order is determined by a traversal algorithm and by the direction in the links. There are several algorithms applicable to graphs (notably depth-first search and breadth-first search).

But, as seen previously, when displaying a bracket, we can treat it as if it were a tree (by ignoring the losers' links). It can then be treated as a rooted directed binary tree. This allows us to use a more specific traversal method, adapted to trees: the in-order tree traversal.

In-order tree traversal

The in-order tree traversal starts on a root node (node without parent). Each time a node is reached, it first starts by walking the link of child 1, also called left child, to visit its entire subtree. It then visits the node. It finishes by walking the link of child 2, also called right child, to visit it entire subtree.

Note: When dealing with child 1 and child 2, treat them as slots, not as a Nth child. Each node has therefore two possible slots that could be filled with a link to another node.

A tree traversal usually starts from a root node (node without parent) up to the leaf nodes (nodes without children). This creates a notion of depth representing the number of links walked from the root node to the visited node

Diagram of an in-order tree traversal
An in-order tree traversal (with the order of traversal)

Applied to a bracket

Now that we have a basic understanding of how the in-order tree traversal functions, we will apply it on a bracket.

Each time a node is visited, a number is incremented to represent the order of the visit. The {x,y} coordinates can then be easily assigned the following way:

x = depth
y = order of visit

Once the tree traversal is finished, each node will have {x,y} coordinates with the following representation. If you look closely, you will see the shape of a single elimination bracket. It is in the wrong direction because we used the depth of the traversal (which is in reverse order).

Table with node coordinates
Displaying {x,y} coordinates in a table

You can easily invert the bracket by calculating the maximum depth during the tree traversal. After the travel, you can simply iterate through all nodes again to update the x position using the max depth:

x = max depth - depth
Conversion of coordinates to a bracket display
Inverting the x value to display the bracket in the correct direction.

Note that this technique works even for brackets that do not have a “standard” size (a power of 2). It works well for brackets of any size, whether odd or non-power of 2.

The bracket is almost ready. It can still be improved a little though.

Improvement 1: reduce the spacing

You might consider that there is a little too much space between the nodes (vertically). This is because the order of visit during the tree traversal ensures that each node is positioned on a different row. This inevitably creates a gap between the nodes.

One way to alleviate this problem is to compact the bracket vertically. This can be done by dividing the y value by 2. Matches are still the same size, so this will just compact vertically the bracket.

Diagram to display the reduction of space

Improvement 2: centering parents

When displaying unbalanced brackets, one part of the bracket might be smaller than another. In such a case, you will realize that some nodes (parent nodes) are closer to the smaller bracket part.

One way to fix this is to perform a second tree traversal, using the post-order, to recalculate the y position of each node that has two children. A new y position is calculated using the average of the y positions of both children, centering the parent towards the children.

Note that it is important to use a post-order traversal because children need to be calculated first to ensure that the parents use their children’s y position after they have been re-calculated.

Diagram to display the centering of parents

The third step might have different implementations depending on how you want to display the bracket and what tools or constraints you have (such as a design system). The interesting point of separating the bracket display with a rendering step is to make it easier to implement your own rendering.

Several techniques can be used to achieve that. You can implement a full HTML version using some kind of grid-like table filled with matches at their {x,y} coordinates. You can use SVG to draw the whole bracket with the appropriate coordinates. You can also use a mix of both, matches using HTML/CSS and links using SVG.

In this guide, we will cover one technique that uses a combination of HTML, CSS and SVG. We will display all bracket nodes on a 1st layer (inside a <div> element) and we will then display all links on a 2nd layer using SVG.

Configuring the layout, sizes and spacing

Before rendering brackets, we need to define the sizes and spacing of several elements in our bracket display, notably the width/height of a node, and the width of a link.

Here is an example of a bracket display configuration:

node width = 192 px
node height = 64 px
link width = 32 px

The node width and node height represent the size of a match. Note that if you want to avoid the matches sticking together, you might need to include some spacing in the height. The node needs therefore to be able to display a match and include some vertical spacing.

The link width represents the horizontal space you leave between two matches to draw the links. You can adjust this width if you want shorter or longer links. We do not need a link height because we will draw the links from the center of one node to the center of another. We therefore will use the node height to identify these coordinates.

Initializing the view

Before displaying nodes and links, it is important to create a root <div> element that will contain all the bracket. Since we will use "absolute" positioning, it is essential that this element is "relative" positioned with a width/height set to the bracket size.

If you need to calculate the width/height of the bracket, you can look for the maximum {x,y} values and apply the bracket configuration (node width/height and link height) to calculate the max width/height of the bracket.

bracket width = (node width + link width) * (max x + 1) - link width
bracket height = node height * (max y + 1)

The bracket width is calculated by including both node width and link width for each x. We remove one link width because the root node (on the right) does not need any link space. The max x is incremented by 1 to take into consideration the size of a node.

The bracket height is calculated from the node height for each y. The max y is incremented by 1 to take into consideration the size of a node.

This size needs to be set on the main element because the two child elements will be "absolute" positioned in order to create two layers. It will also need to be set on the two child elements to ensure they are properly overlapping.

Initial view with max width/height.

Positioning the nodes

The bracket nodes need to be displayed within a single <div> element. It will represent our 1st layer and therefore needs to be properly configured (width, height, absolute positioning and z-index).

Each bracket node can then be placed inside by using "absolute" positioned and the following top/left values. (or inset-inline-start and inset-block-start if using CSS logical properties)

top = node height * y
left = (node width + link width) * x

You can possibly add some padding at this stage if you want to ensure there will be some vertical spacing between the bracket nodes.

Placing the bracket nodes using {x,y} coordinates.

Drawing the links

The links will be displayed within another <div> element. It will represent our 2nd layer and therefore needs to be properly configured (width, height, absolute positioning and z-index).

The links will be drawn using SVG. We therefore need to create a single svg element that will contain all our links. The SVG element needs to match the size of layer and configure a view box that will make it easy for us to convert our {x,y} coordinates into SVG coordinates. We suggest configuring the view box with the same width/height (in pixels) than the HTML size.

Each link can then be drawn using a <polyline> element. The idea is to draw a line going from the source node to the target node. It therefore starts from the "center right" of the source node and ends at the "center left" of the target node.

You are free to draw straight diagonal lines, or to build some kind of connector by drawing only horizontal and vertical lines. You can also add a CSS class to the polyline element to customize the style of the line (color, width, ...)

Drawing links between bracket nodes using SVG.

Conclusion

This guide describes a way of displaying a bracket using one of the simple methods. It still leaves room for improvements or customizations on how you want to display your brackets.

A number of improvements can be made when calculating coordinates to deal with specific use cases, such as displaying a grand final or the losers' bracket in a double elimination. However, most of these problems can be solved by performing additional tree or graph traversals when calculating node coordinates.

By dividing the process into three steps, you can choose which area you want to improve without affecting the other areas. You can improve the style of the bracket, make it responsive and mobile-friendly without questioning the algorithms used to place the matches.