Spaces:
Sleeping
Sleeping
import streamlit as st | |
import matplotlib.pyplot as plt | |
import networkx as nx | |
import bz2 | |
import numpy as np | |
# Sidebar for selecting an option | |
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"]) | |
# Helper function to draw and display graph | |
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) | |
# Title/legend | |
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, | |
) | |
# Resize figure for label readability | |
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 | |
# Draw Chess Masters Graph (the main section) | |
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") | |
# identify connected components of the undirected version | |
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()}") | |
# find all games with B97 opening (as described in ECO) | |
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) | |
# Create undirected graph H without multi-edges | |
H = nx.Graph(G) | |
# Edge width is proportional to number of games played | |
edgewidth = [len(G.get_edge_data(u, v)) for u, v in H.edges()] | |
# Node size is proportional to number of games won | |
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] | |
# Generate layout for visualization | |
pos = nx.kamada_kawai_layout(H) | |
# Manually tweak some positions to avoid label overlap | |
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 the graph | |
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) | |
# Identify connected components and draw the graph for the uploaded data | |
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") | |
# Display other sections | |
def display_basic_properties(): | |
st.title("Basic: Properties") | |
option = st.radio("Choose a graph type:", ("Default Example", "Create your own")) | |
# Default example: 5x5 grid graph | |
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) | |
# Display other sections | |
def display_read_write_graph(): | |
st.title("Basic: Read and write graphs") | |
G = nx.karate_club_graph() | |
# Write the graph | |
nx.write_gml(G, "karate_club.gml") | |
st.write("Graph written to 'karate_club.gml'.") | |
# Read the graph back | |
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) | |
# Call the appropriate function based on sidebar selection | |
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.") | |