How to Make Peter McKeever’s Diamond Chart in ggplot2

Sezer Unar
6 min readJan 23, 2021

As a football analytics enthusiast, I believe in “Good artists copy, great artists steal” saying to develop myself. Copying is the first step to be the great because you have to look at creative works to create something creative. And today I am going to show how to copy Peter’s one of magnificent graphs, the diamond chart in ggplot2.

The first time I saw the graph, I googled: “How to rotate plot area in ggplot”. Not just me, but also some people thought like me. I found some solutions but they were not useful.

However, I understood that ggplot isn’t appropriate for this purpose, so straightforward logic echoed in my mind: “If you cannot rotate the plot area, then create a fictional plot area and rotate every x-y coordinates.”

Let’s glance at the data set.

> str(dataf)'data.frame': 357 obs. of  3 variables:
$ player : chr "Khouma Babacar" "Tasos Bakasetas" "Mustafa Pektemek" "Francois Moubandje" ...
$ dribbling_ad: num 0.0682 0.0484 0.0631 0.0254 0.0399 ...
$ pass_ad : num 0.00591 0.06016 0.0104 0.04246 0.09331 ...

As we are gonna create a fictional plot area, scaling the data set between 0 and 1 would be easier to define and rotate the points. Now, our plot looks like this.

We are going to rotate every observation about 45 degrees counterclockwise. How can we do this? I am not a mathematician, so if I could imagine it, somebody definitely did it. The solution had to be somewhere in the internet ocean. I can’t say I looked for the solution for days, it only took me five minutes. It is not a success story that will be the subject of movies.

Let’s write rotating functions.

rotate_x <- function(x,y) {
r = 45 * pi / 180
rot_x <- ((x-0.5) * cos(r)) - ((y-0.5) * sin(r) ) + 0.5
return(rot_x)
}
rotate_y <- function(x,y) {
r = 45 * pi / 180
rot_y <- ((x-0.5) * sin(r)) + ((y-0.5) * cos(r)) + 0.5
return(rot_y)
}
#Rotating every observation according to our fictional plot
dataf$passer <- rotate_x(dataf$pass_ad, dataf$dribbling_ad)
dataf$carrier <- rotate_y(dataf$pass_ad, dataf$dribbling_ad)
# Important Note : Firstly scale yout data between 0 and 1, and then use the rotating functions.

Ok, now there is one more problem left. How do we draw new fictional plot ‘s border? After scaling the data, (1,1) point is the best point in terms of x and y axes. It is going to be the top of the diamond when rotating. Let’s have a look at every corner with the functions we wrote.

Still real x and y axes appear. Don’t get confused. Abracadabra!

Who can claim that these are not the real axes?

I think I told the basic logic. Let me share the whole codes.

range01 <- function(x){(x-min(x))/(max(x)-min(x))}#Scaling data
dataf <- dataf %>% mutate_at(c(2,3), range01)
dataf$color = ifelse(dataf$dribbling_ad >= 0.5 & dataf$pass_ad >= 0.5, "both",
ifelse(dataf$dribbling_ad >= 0.5 & dataf$pass_ad < 0.5, "carrier",
ifelse(dataf$dribbling_ad < 0.5 & dataf$pass_ad >= 0.5, "passer", "bad")))
#Roteted points
dataf$passer <- rotate_x(dataf$pass_ad, dataf$dribbling_ad)
dataf$carrier <- rotate_y(dataf$pass_ad, dataf$dribbling_ad)
#Fictional Diamond plot area
diamond_shape <- data.frame(x = c(rotate_x(1,0), rotate_x(1,1), rotate_x(0,1), rotate_x(0,0)),
y = c(rotate_y(1,0), rotate_y(1,1), rotate_y(0,1), rotate_y(0,0)))
#Panel Grids
grids <- data.frame(x = c(rotate_x(seq(0.1,0.9, 0.1), 0), rotate_x(0, seq(0.1, 0.9, 0.1))),
y = c(rotate_y(seq(0.1,0.9, 0.1), 0), rotate_y(0, seq(0.1, 0.9, 0.1))),
xend = c(rotate_x(seq(0.1,0.9, 0.1), 1), rotate_x(1, seq(0.1, 0.9, 0.1))),
yend = c(rotate_y(seq(0.1,0.9, 0.1), 1), rotate_y(1, seq(0.1, 0.9, 0.1))))
p <- ggplot(dataf %>% filter(color != "bad"), aes(passer, carrier)) +
xlim(-0.25, 1.25) +
ylim(-0.3, 1.5) +
geom_polygon(data = diamond_shape, aes(x = x, y = y), fill = "#1a1d2c", color = "#75777f", size = 1) +
geom_segment(data= grids, aes(x = x, y = y, xend = xend, yend = yend), color = "#75777f") +
geom_segment(aes(x = 0.5, y = 0.5, xend = rotate_x(0.5, 1.15), yend = rotate_y(0.5, 1.15)),
color = "white", linetype = 2, size = 1) +
geom_segment(aes(x = 0.5, y = 0.5, xend = rotate_x(1.15, 0.5), yend = rotate_y(1.15, 0.5)),
color = "white", linetype = 2, size = 1) +
geom_point(size = 4, alpha = 0.9, shape = 21, aes(fill = color)) +
scale_fill_manual(values = c("#a9488d", "#17c685", "#fffe02"),
labels = c("High PV in Carries & Passes",
"High Carrying PV per 90",
"High Passing PV per 90")) +
geom_point(data = dataf %>% filter(color == "bad"), aes(passer, carrier), shape = 21, color = "#327b92", fill = "transparent",
size = 4, stroke = 1) +
geom_point(data = dataf %>% filter(player == "Rachid Ghezzal"), aes(x = passer, y = carrier), shape = 21, fill = "transparent",
color = "white", stroke = 2, size = 4) +
geom_text(data = dataf %>% filter(player == "Rachid Ghezzal"), aes(x = passer, y = carrier, label = player),
family = "Bahnschrift", color = "white", vjust = -1) +
labs(x = "Passer",
y = "Carrier",
title = "Possession Value per 90",
subtitle = "Turkey Super League | Season 2020/2021",
caption = "@unarsezer | Inspired by Peter McKeever") +
annotate(geom = "text", x = rotate_x(1, 0), y = rotate_y(1, 0), size = 6.5,
label = "Passer", color = "white", angle = 270, vjust = -1.5, fontface = 2, family = "Bahnschrift") +
annotate(geom = "text", x = rotate_x(0, 1), y = rotate_y(0, 1), size = 6.5,
label = "Carrier", color = "white", angle = 90, vjust = -1, fontface = 2, family = "Bahnschrift") +
annotate(geom = "text", x = rotate_x(1, 1), y = rotate_y(1, 1),
label = "Elit Progressor", color = "white", vjust = -1, fontface = 2, size = 7, family = "Bahnschrift") +
annotate(geom = "text", color = "white", size = 4, x = rotate_x(-0.2, 0.5), y = rotate_y(1.3, 0.5), vjust = -0.5,
family = "Bahnschrift", fontface = 1,
label = "Players above this line score\nhighly in both carries and passes") +
annotate(geom = "curve", color = "white", size = 1,
x = -0.13, y = 1.05,
xend = 0.028, yend = 0.93,
curvature = 0.5, arrow = arrow(length = unit(0.3, "cm"), type = "closed")) +
theme(plot.background = element_rect(fill = "#1a1d2c", color = "#1a1d2c"),
panel.background = element_rect(fill = "#1a1d2c", color = "#1a1d2c"),
plot.title = element_text(family = "Bahnschrift", face = 2, size = 25, color = "white"),
plot.subtitle = element_text(family = "Bahnschrift", face = 1, size = 19, color = "white"),
plot.caption = element_text(family = "Bahnschrift", face = 2, size = 12, color = "white"),
panel.grid = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
legend.position = c(0.8, 0.8),
legend.background = element_blank(),
legend.key = element_blank(),
legend.title = element_blank(),
legend.text = element_text(colour = "white", family = "Bahnschrift", face = 2, size = 10))
ggsave(p, file = "path", dpi = 200, width = 9.7, height = 12, scale = 0.8)
Not bad, is it?

Thanks for reading my blog and also special thanks to Peter for making this great graph.

Hope to be a great artist one day…

--

--