shukdevdatta123's picture
Update app.py
e39c5f4 verified
raw
history blame
9.3 kB
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.")