AUS Open is a tennis tournament attended by $𝑁=128=2^7$ women players. It is a balanced knokout tournament, consisting in 7 rounds. Before the starting a draw is released for the first round. Then succesivelly, the $(2𝑘-1)^{th}$ and $(2𝑘)^{th}$ winners in a round, $r\geq 1$, $𝑘=1,...,2^{7-r}$ play together in the $(r+1)^{th}$ round. After the $6^{th}$ round the last two players have the final match, and the winner is the winner of the Grand Slam. The whole process is visualized by WTA as a balanced binary tree, having as root the tournament's winner, and the as tree nodes, the players in each round. The parent of a pair (2𝑘-1,2𝑘) playing in a round r is the winner of the corresponding match.
We define as tree leafs the players from the second round, to avoid a cluttered tree visualization. The node positions, assigned by Shell network layout, are rotated by an angle to get a symmetric circular tree. The edges emanating from the a node pair (2k-1, 2k), representing players playing each other in the $r^{th}$, are drawn as a continuous/dotted line, depending on whether the match has been won/lost by that player.
The matches results are recorded in a csv file of 127 rows. The players from the second round are recorded in the lines $2^6:2^7-1$, i.e. 64:127, and similarly in the $r^{th}$, round, r=3, from $2^5:2^6-1$, and so on. In the $6^{th}$ round the finalists are recorded in the line 2 and 3, and the winner in the first line.
Information on the match result is recorded in the column "won_lost". 1, repsectively 2, is the code for a won/lost match.
using DataFrames, CSV, Graphs, PlotlyJS
import NetworkLayout:Shell
import LinearAlgebra:norm
include("myutils.jl");
The WebIO Jupyter extension was not detected. See the WebIO Jupyter integration documentation for more information.
function rot2d(t)
return [cos(t) -sin(t); sin(t) cos(t)]
end
function set_annotation(x, y, anno_text, textangle, fontsize=11, color="white")
return attr(x= x,
y= y,
text= anno_text,
textangle=textangle,#angle with horizontal line through (x,y), in degrees;
#+ =clockwise, -=anti-clockwise
font= attr(size=fontsize, color=color),
showarrow=false)
end
set_annotation (generic function with 3 methods)
h = 6
N = 2^(h+1)-1
V = collect(1:N)
#get edges of a perfect balanced tree
E = perfect_balanced_tree_edges();
El = vcat([(0,0)], E);
df = CSV.File("data/AUS-open-2022.csv") |> DataFrame ;
@assert(size(df, 1)==N)
G = SimpleGraph(N)
for e in E[1:end-1]
add_edge!(G, e[1], e[2])
end
n = vcat([[1]], [collect(2^k:2^(k+1)-1) for k in 1:h])
A = Matrix{Int}(adjacency_matrix(G)) #adjacency_matrix(G) is a sparse matrix
node_pos = Shell(; nlist=n)(A)
node_pos = mapreduce(permutedims, vcat, node_pos);
rnode_pos = copy(node_pos')
for (k, nodes) in enumerate(n[2:end])
I = 2^k:2^(k+1)-1
rnode_pos[:, I] = rot2d(π/length(nodes)) * node_pos'[:, I]
end
#retrieve indices for players which lost/won a match
I = findall(idx->idx == 2, df[!, :won_lost]);
J = findall(idx->idx == 1, df[!, :won_lost]);
#coordinates of nodes representing players which lost/won the match that leads to "this" node
xel, yel = get_plotly_data(El[I], rnode_pos'[:,1], rnode_pos'[:,2]);
xew, yew = get_plotly_data(El[J], rnode_pos'[:,1], rnode_pos'[:,2]);
node_tr = get_node_trace(rnode_pos'[:,1], rnode_pos'[:,2], String.(df[!,:name]);
marker_color="rgb(255, 200, 0)", marker_size=10,
linecolor="rgb(255, 200, 0)")
# an edge emanating from a player that lost "this" match in the r^th round
# to the winner in the (r+1)^th round
# is a dotted line, and a continuous one from winner to herself:
edgel_tr = get_edge_trace(xel, yel; linecolor="rgb(210,210,210)" , linewidth=1)
push!(edgel_tr["line"], "dash"=>"dot") #dotted edge
edgew_tr = get_edge_trace(xew, yew; linecolor="rgb(210,210,210)" , linewidth=1)# continuous edge
# draw circles through the nodes representing players which played in quarterfinal, semifinal, resp final
shapes = [attr(type="circle",
xref="x", yref="y",
x0=-1.0, y0=-1.0, x1=1.0, y1=1.0,
line=attr(color="rgb(210,210,210)", width=0.5 )),
attr(type="circle",
xref="x", yref="y",
x0=-2.0, y0=-2.0, x1=2.0, y1=2.0,
line=attr(color="rgb(210,210,210)", width=0.5 )),
attr(type="circle",
xref="x", yref="y",
x0=-3.0, y0=-3.0, x1=3.0, y1=3.0,
line=attr(color="rgb(210,210,210)", width=0.5))]
angles = []# angle of the radius direction through each leaf (player in the 2nd round)
for k in 64:127
push!(angles, -(180*atan(rnode_pos[2, k]/rnode_pos[1, k])/pi))
end
# to display player names approximately at the same distance from leafs
# extract the maximum length, depending on leaf index
# and add some spaces for shorter names
pos_text = 1.2* rnode_pos[:, 64:127]
slength1 = []
for s in df[64:79, :name]
push!(slength1, length(s))
end
for s in df[112:127, :name]
push!(slength1, length(s))
end
l1 = maximum(slength1)
slength2 = []
for s in df[80:111, :name]
push!(slength2, length(s))
end
l2 = maximum(slength2)
player_name=[]
for pname in df[64:79, :name]
push!(player_name, " "^(2)*pname*" "^(l1-length(pname)))
end
for pname in df[80:111, :name]
push!(player_name," "^(l2+2-length(pname))*pname*" ")
end
for pname in df[112:127, :name]
push!(player_name, " "^(2)*pname*" "^(l1-length(pname)))
end
annotations = []
for k in 64:127
push!(annotations,
set_annotation(pos_text[1, k-63],
pos_text[2, k-63][1],
player_name[k-63],
angles[k-63]))
end
pl = Plot([edgew_tr, edgel_tr, node_tr],
Layout(title_text="Australian Open, 2022. Women's singles",
title_x=0.5, width=750, height=730, showlegend=false,
xaxis_visible=false, yaxis_visible=false,
shapes=shapes, annotations=annotations,
plot_bgcolor="rgb(0,0,0)" ))