A New Way to Visualize UFC Fighter Data using p5.js
Tags: Development, JavaScript, Data Visualization, UFC, Sports

READING THE QUAD PLOT

The graphic above is based on real values taken from the UFC’s official statistics page. The quad plot’s most important function, I believe, is the ability to show whether a fighter’s strength is in his/her ground game or standing game (takedowns and striking, respectively).

The plot above shows the difficulty in landing accurate shots, regardless of the quality of fighter. The attacking statistic displayed is a percentage of the total shots landed on opponent / total shots attempted on opponent. Jon Jones has a greater striking accuracy than the other fighters, however, Khabib has greater Takedown accuracy (probably in no small part due to his bear wrestling experience).

Given the relative and rate-based nature of the statistics, this plot can be used as a comparison tool to show how fighters compare against each other. Skilled fighters will be those whose lines are furthest extended to their respective corners (corresponding to 100% defense and offense), giving us a quick overview of a fighter’s strengths and weaknesses.

Of course, this type of quad plot has its limitations; it’s certainly not a visual that serves as a deep dive, rather encouraging further exploration by showing the surface-level summary of their abilities. Since the quad plot is a percentage-based figure scaled on “total” values, it can sometimes be misleading without context of the absolute number of hits faced or attempted. For example, a fighter totaling 5 shots landed / 5 shots will display the same figure as one with 100 shots / 100 attempts, despite the relative inexperience of the first fighter, who may have only been in one fight! That’s why this graph is more useful as a 10,000ft view and starting point rather than as an in-depth analysis.

DESIGNING THE QUAD PLOT

This sketch took an hour and a half to complete (and that’s without any prior knowledge of p5.js). Learning it is super simple in my opinion, and just as an artist paints on a canvas, a data analyst can use p5.js as his/her canvas to create new and interesting visuals.

If I’m being honest, I’m not much of a designer; I only took one design course (read about my experience here), and starting off, I was unsure as how to bring out the most important abilities for an individual fighter. While I settled on using different color schemes, there were also alternative considerations, such as adjusting the thickness of lines to show the fighters strengths and weaknesses, or showing actual percentage values along with the lines. These are all alternatives that I designed for a full comparison. Ultimately, using the varied color scheme really brought out the elegance and simplicity I was looking for.

To create this plot, I broke it down into two parts, starting with drawing the corners, and then drawing the inner lines representing the four statistics. Let’s take a look at how these were done.

SET TING UP THE WORKSPACE

View Project on Github

Most beginner tutorials set up p5.js with a simple HTML file and a local JS file in which you write the code. I decided to create a simple flask application to have a server running in the background instead because I wanted to be able to extend the functionality by calling on an API in the future and make the visualization interactive within a webpage.

You can view the project from the GitHub repository by running the following:

pipenv install pipenv run python server.py

I won’t get too deep into the setup here. Let’s go straight to the creation of the plot.

SETTING UP THE CODE

There are two main internal methods that P5.js recognizes:

1. setup()

2. draw()

The setup method initializes the canvas and is run only once, while the draw method is continually refreshing the canvas. This is not always the case, however, as you can add a noLoop( ) method in the setup which will freeze the draw methods’ continuous refresh rate.

function setup( ) {

createCanvas(1500, 1500)

}

The snippet above initializes a canvas size of 1500 pixels by 1500 pixels, and as we’ll soon see, the draw method will leverage a custom function in order to draw the quad plot.

DRAWING THE PLOT

function draw() {

drawQuadrant(300, 300 ,0.67, 0.50,0.45,0.85,”Khabib Nurmagomedov”);

drawQuadrant(680, 300 ,0.7, 0.51,0.37,0.70, “Conor McGregor”);

drawQuadrant(300, 680 ,0.66, 0.57,0.41,0.95,”Jon Jones”);

drawQuadrant(680, 680 ,0.54, 0.51,0.37,0.78, “Daniel Cormier”);

}

The drawQuadrant function is responsible for creating the corners of the plot. It takes several parameters: the first two are points that locate the middle of the quad plot, so that the edges can be drawn equidistant to the mid point.

The rest of the parameters are rate-based values for the Striking Defence, Striking Accuracy, Takedown Accuracy and Takedown Defence respectively.

The corners are drawn by calling the built-in vertex function. It takes two floating point parameters, which correlate to the x and y position on the canvas, in which a line is connected between the two.

beginShape(LINES);

stroke(colors.STRIKING_ACC);

vertex(mid.x+125, mid.y-125);

vertex(mid.x+125, mid.y-75);

vertex(mid.x+75, mid.y-125);

endShape(CLOSE);

When drawing a vertex you need to specify the entire shape by calling beginShape(LINES) which means the connecting point between the two vertices will be a line. The beginShape function also accepts POINTS, TRIANGLES, etc. to connect the vertices. Refer to the API docs for more information. The stroke function then sets the color of the line that you’re adding. I repeated this step 3 more times to get all four corners of the Quad Plot.

The next stage of the sketch was to draw the lines that extend out to the corners based on the four statistics. This was done by creating four vectors, each one relating to the specific parameter, and an additional vector to locate the midpoint. As vectors are objects in p5.js, they have their own functions and parameters built in. When you create a vector, you can always keep track of its x and y coordinates.

In order to get the direction and length to which the line extends, I used the Linear Interpolate lerp(v1, v2, amt) function, where v1 and v2 are vectors, and amt is a percentage-based value.

let mid_vec = createVector(mid.x, mid.y);

let td_vec = createVector(mid.x+125, mid.y+125);

let ta_vec = createVector(mid.x-125, mid.y+125);

let sa_vec = createVector(mid.x+125, mid.y-125);

let sd_vec = createVector(mid.x-125, mid.y-125); let takedown_def = p5.Vector.lerp(mid_vec, td_vec, td);

let takedown_acc = p5.Vector.lerp(mid_vec, ta_vec, ta);

let striking_def = p5.Vector.lerp(mid_vec, sd_vec, sd);

let striking_acc = p5.Vector.lerp(mid_vec, sa_vec, sa);

After determining the interpolation between the midpoint and the parameter, we draw the line that extends to the four parameters’ respective corners. This is accomplished using the same technique in the drawQuadrant method from earlier, except this time, we simply draw a line from the midpoint to the corner with a distance calculated by the linear interpolation method.

beginShape(LINES);

stroke(colors.TAKEDOWN_DEF);

vertex(mid_vec.x, mid_vec.y);

vertex(takedown_def.x, takedown_def.y);

endShape(CLOSE);

WRAPPING UP

All in all, given the relative ease of drawing these Quad Plots, I’d certainly have to say that p5.js makes it very easy to prototype visualizations to get a feel for what certain graphics might look like. My next steps are to continue exploring p5.js to make the graph interactive, so that you’ll be able to enter a fighter’s name and get his/her respective plot.

I had a lot of fun making this graph, and I look forward to extending it and seeing how it evolves over time. You can view the gist of the code here. Any help and suggestions on my code and design process would be much appreciated. After all, like I mentioned at the top, I am new to p5.js and would love to find out other ways of plotting this data and designing it in more creative and effective ways!