|
import streamlit as st |
|
import matplotlib.pyplot as plt |
|
import networkx as nx |
|
import bz2 |
|
import numpy as np |
|
|
|
|
|
sidebar_option = st.sidebar.radio("Select an option", |
|
["Select an option", "Basic: Properties", |
|
"Basic: Read and write graphs", "Basic: Simple graph", |
|
"Basic: Simple graph Directed", "Drawing: Custom Node Position", |
|
"Drawing: Chess Masters"]) |
|
|
|
|
|
def draw_graph(G, pos=None, title="Graph Visualization"): |
|
plt.figure(figsize=(12, 12)) |
|
nx.draw_networkx_edges(G, pos, alpha=0.3, width=edgewidth, edge_color="m") |
|
nx.draw_networkx_nodes(G, pos, node_size=nodesize, node_color="#210070", alpha=0.9) |
|
label_options = {"ec": "k", "fc": "white", "alpha": 0.7} |
|
nx.draw_networkx_labels(G, pos, font_size=14, bbox=label_options) |
|
|
|
|
|
font = {"fontname": "Helvetica", "color": "k", "fontweight": "bold", "fontsize": 14} |
|
ax = plt.gca() |
|
ax.set_title(title, font) |
|
ax.text( |
|
0.80, |
|
0.10, |
|
"edge width = # games played", |
|
horizontalalignment="center", |
|
transform=ax.transAxes, |
|
fontdict=font, |
|
) |
|
ax.text( |
|
0.80, |
|
0.06, |
|
"node size = # games won", |
|
horizontalalignment="center", |
|
transform=ax.transAxes, |
|
fontdict=font, |
|
) |
|
|
|
|
|
ax.margins(0.1, 0.05) |
|
plt.axis("off") |
|
st.pyplot(plt) |
|
|
|
|
|
def chess_pgn_graph(pgn_file="chess_masters_WCC.pgn.bz2"): |
|
"""Read chess games in pgn format in pgn_file. |
|
|
|
Filenames ending in .bz2 will be uncompressed. |
|
|
|
Return the MultiDiGraph of players connected by a chess game. |
|
Edges contain game data in a dict. |
|
""" |
|
G = nx.MultiDiGraph() |
|
game = {} |
|
with bz2.BZ2File(pgn_file) as datafile: |
|
lines = [line.decode().rstrip("\r\n") for line in datafile] |
|
for line in lines: |
|
if line.startswith("["): |
|
tag, value = line[1:-1].split(" ", 1) |
|
game[str(tag)] = value.strip('"') |
|
else: |
|
if game: |
|
white = game.pop("White") |
|
black = game.pop("Black") |
|
G.add_edge(white, black, **game) |
|
game = {} |
|
return G |
|
|
|
|
|
|
|
def display_chess_masters_graph(): |
|
st.title("Drawing: Chess Masters") |
|
|
|
option = st.radio("Choose a graph type:", ("Default Example", "Create your own")) |
|
|
|
if option == "Default Example": |
|
G = chess_pgn_graph("chess_masters_WCC.pgn.bz2") |
|
|
|
|
|
H = G.to_undirected() |
|
Gcc = [H.subgraph(c) for c in nx.connected_components(H)] |
|
if len(Gcc) > 1: |
|
st.write(f"Note the disconnected component consisting of:\n{Gcc[1].nodes()}") |
|
|
|
|
|
openings = {game_info["ECO"] for (white, black, game_info) in G.edges(data=True)} |
|
st.write(f"\nFrom a total of {len(openings)} different openings,") |
|
st.write("the following games used the Sicilian opening") |
|
st.write('with the Najdorff 7...Qb6 "Poisoned Pawn" variation.\n') |
|
|
|
for white, black, game_info in G.edges(data=True): |
|
if game_info["ECO"] == "B97": |
|
summary = f"{white} vs {black}\n" |
|
for k, v in game_info.items(): |
|
summary += f" {k}: {v}\n" |
|
summary += "\n" |
|
st.write(summary) |
|
|
|
|
|
H = nx.Graph(G) |
|
|
|
|
|
edgewidth = [len(G.get_edge_data(u, v)) for u, v in H.edges()] |
|
|
|
|
|
wins = dict.fromkeys(G.nodes(), 0.0) |
|
for u, v, d in G.edges(data=True): |
|
r = d["Result"].split("-") |
|
if r[0] == "1": |
|
wins[u] += 1.0 |
|
elif r[0] == "1/2": |
|
wins[u] += 0.5 |
|
wins[v] += 0.5 |
|
else: |
|
wins[v] += 1.0 |
|
nodesize = [wins[v] * 50 for v in H] |
|
|
|
|
|
pos = nx.kamada_kawai_layout(H) |
|
|
|
|
|
pos["Reshevsky, Samuel H"] += (0.05, -0.10) |
|
pos["Botvinnik, Mikhail M"] += (0.03, -0.06) |
|
pos["Smyslov, Vassily V"] += (0.05, -0.03) |
|
|
|
|
|
draw_graph(H, pos, title="World Chess Championship Games: 1886 - 1985") |
|
|
|
elif option == "Create your own": |
|
uploaded_file = st.file_uploader("Upload your own PGN file", type="pgn") |
|
if uploaded_file is not None: |
|
G_custom = chess_pgn_graph(uploaded_file) |
|
|
|
H_custom = G_custom.to_undirected() |
|
edgewidth = [len(G_custom.get_edge_data(u, v)) for u, v in H_custom.edges()] |
|
wins = dict.fromkeys(G_custom.nodes(), 0.0) |
|
for u, v, d in G_custom.edges(data=True): |
|
r = d["Result"].split("-") |
|
if r[0] == "1": |
|
wins[u] += 1.0 |
|
elif r[0] == "1/2": |
|
wins[u] += 0.5 |
|
wins[v] += 0.5 |
|
else: |
|
wins[v] += 1.0 |
|
nodesize = [wins[v] * 50 for v in H_custom] |
|
pos_custom = nx.kamada_kawai_layout(H_custom) |
|
draw_graph(H_custom, pos_custom, title="Custom Chess Game Graph") |
|
|
|
|
|
def display_basic_properties(): |
|
st.title("Basic: Properties") |
|
option = st.radio("Choose a graph type:", ("Default Example", "Create your own")) |
|
|
|
|
|
if option == "Default Example": |
|
G = nx.lollipop_graph(4, 6) |
|
display_graph_properties(G) |
|
|
|
elif option == "Create your own": |
|
num_nodes = st.number_input("Number of nodes:", min_value=2, max_value=50, value=5) |
|
num_edges = st.number_input("Number of edges per group (for lollipop graph):", min_value=1, max_value=10, value=3) |
|
|
|
if st.button("Generate"): |
|
if num_nodes >= 2 and num_edges >= 1: |
|
G_custom = nx.lollipop_graph(num_nodes, num_edges) |
|
display_graph_properties(G_custom) |
|
|
|
def display_graph_properties(G): |
|
pathlengths = [] |
|
st.write("### Source vertex {target:length, }") |
|
for v in G.nodes(): |
|
spl = dict(nx.single_source_shortest_path_length(G, v)) |
|
st.write(f"Vertex {v}: {spl}") |
|
for p in spl: |
|
pathlengths.append(spl[p]) |
|
|
|
avg_path_length = sum(pathlengths) / len(pathlengths) |
|
st.write(f"### Average shortest path length: {avg_path_length}") |
|
|
|
dist = {} |
|
for p in pathlengths: |
|
if p in dist: |
|
dist[p] += 1 |
|
else: |
|
dist[p] = 1 |
|
|
|
st.write("### Length #paths") |
|
for d in sorted(dist.keys()): |
|
st.write(f"Length {d}: {dist[d]} paths") |
|
|
|
st.write("### Properties") |
|
st.write(f"Radius: {nx.radius(G)}") |
|
st.write(f"Diameter: {nx.diameter(G)}") |
|
st.write(f"Eccentricity: {nx.eccentricity(G)}") |
|
st.write(f"Center: {nx.center(G)}") |
|
st.write(f"Periphery: {nx.periphery(G)}") |
|
st.write(f"Density: {nx.density(G)}") |
|
|
|
st.write("### Graph Visualization") |
|
pos = nx.spring_layout(G, seed=3068) |
|
plt.figure(figsize=(8, 6)) |
|
nx.draw(G, pos=pos, with_labels=True, node_color='lightblue', node_size=500, font_size=10, font_weight='bold') |
|
st.pyplot(plt) |
|
|
|
|
|
def display_read_write_graph(): |
|
st.title("Basic: Read and write graphs") |
|
G = nx.karate_club_graph() |
|
|
|
|
|
nx.write_gml(G, "karate_club.gml") |
|
st.write("Graph written to 'karate_club.gml'.") |
|
|
|
|
|
G_new = nx.read_gml("karate_club.gml") |
|
st.write("Graph read back from 'karate_club.gml'.") |
|
nx.draw(G_new, with_labels=True) |
|
st.pyplot(plt) |
|
|
|
def display_simple_graph(): |
|
st.title("Basic: Simple graph") |
|
G = nx.complete_graph(5) |
|
nx.draw(G, with_labels=True) |
|
st.pyplot(plt) |
|
|
|
def display_simple_directed_graph(): |
|
st.title("Basic: Simple graph Directed") |
|
G = nx.complete_graph(5, nx.DiGraph()) |
|
nx.draw(G, with_labels=True) |
|
st.pyplot(plt) |
|
|
|
def display_custom_node_position(): |
|
st.title("Drawing: Custom Node Position") |
|
pos = {"A": (1, 2), "B": (2, 3), "C": (3, 1)} |
|
G = nx.Graph(pos) |
|
nx.draw(G, pos=pos, with_labels=True) |
|
st.pyplot(plt) |
|
|
|
|
|
|
|
if sidebar_option == "Basic: Properties": |
|
display_basic_properties() |
|
elif sidebar_option == "Basic: Read and write graphs": |
|
display_read_write_graph() |
|
elif sidebar_option == "Basic: Simple graph": |
|
display_simple_graph() |
|
elif sidebar_option == "Basic: Simple graph Directed": |
|
display_simple_directed_graph() |
|
elif sidebar_option == "Drawing: Custom Node Position": |
|
display_custom_node_position() |
|
elif sidebar_option == "Drawing: Chess Masters": |
|
display_chess_masters_graph() |
|
else: |
|
st.write("Please select a valid option from the sidebar.") |
|
|