|
import streamlit as st |
|
import networkx as nx |
|
from pyvis.network import Network |
|
import streamlit.components.v1 as components |
|
|
|
def show_network_analysis(data): |
|
""" |
|
Creates and displays an interactive network graph of correspondences (sender -> addressee). |
|
Nodes are colored based on their total number of connections and both nodes and edges are translucent. |
|
|
|
:param data: List of dictionaries, each representing a single letter's data. |
|
""" |
|
st.subheader("--------") |
|
|
|
|
|
G = nx.DiGraph() |
|
|
|
edge_weights = {} |
|
for entry in data: |
|
sender = entry.get('sender_name') |
|
addressee = entry.get('addressee_name') |
|
if sender and addressee: |
|
edge_weights[(sender, addressee)] = edge_weights.get((sender, addressee), 0) + 1 |
|
|
|
|
|
for (sender, addressee), weight in edge_weights.items(): |
|
G.add_edge(sender, addressee, weight=weight) |
|
|
|
|
|
net = Network( |
|
height='600px', |
|
width='100%', |
|
notebook=False, |
|
directed=True, |
|
bgcolor='#ffffff', |
|
font_color='black' |
|
) |
|
net.from_nx(G) |
|
|
|
|
|
degree_dict = {node: G.in_degree(node) + G.out_degree(node) for node in G.nodes()} |
|
|
|
if degree_dict: |
|
min_deg = min(degree_dict.values()) |
|
max_deg = max(degree_dict.values()) |
|
else: |
|
min_deg, max_deg = 0, 1 |
|
|
|
def get_node_color(degree): |
|
""" |
|
Generates a translucent color between light blue and dark blue based on the node's degree. |
|
|
|
:param degree: The degree of the node. |
|
:return: Dictionary with background and border colors including opacity. |
|
""" |
|
if min_deg == max_deg: |
|
color_hex = '#5b86c5' |
|
else: |
|
ratio = (degree - min_deg) / (max_deg - min_deg) |
|
|
|
r1, g1, b1 = 173, 216, 230 |
|
r2, g2, b2 = 0, 0, 139 |
|
r = int(r1 + (r2 - r1) * ratio) |
|
g = int(g1 + (g2 - g1) * ratio) |
|
b = int(b1 + (b2 - b1) * ratio) |
|
color_hex = f'#{r:02x}{g:02x}{b:02x}' |
|
|
|
return { |
|
'background': color_hex, |
|
'border': color_hex, |
|
'highlight': { |
|
'background': color_hex, |
|
'border': color_hex |
|
}, |
|
'opacity': 0.8, |
|
'borderWidth': 2 |
|
} |
|
|
|
|
|
for node in net.nodes: |
|
node_label = node["id"] |
|
degree = degree_dict.get(node_label, 0) |
|
node["color"] = get_node_color(degree) |
|
|
|
node["title"] = f"{node_label} (Connections: {degree})" |
|
|
|
|
|
def get_edge_color(): |
|
""" |
|
Returns a consistent translucent color for all edges. |
|
|
|
:return: Dictionary with edge color and opacity. |
|
""" |
|
return { |
|
'color': '#888888', |
|
'opacity': 0.5 |
|
} |
|
|
|
|
|
for edge in net.edges: |
|
edge["color"] = get_edge_color() |
|
src, dst = edge["from"], edge["to"] |
|
weight = G[src][dst].get('weight', 1) |
|
edge["title"] = f"Letters exchanged: {weight}" |
|
|
|
|
|
net.set_options(""" |
|
var options = { |
|
"physics": { |
|
"enabled": true, |
|
"solver": "forceAtlas2Based", |
|
"forceAtlas2Based": { |
|
"gravitationalConstant": -50, |
|
"centralGravity": 0.003, |
|
"springConstant": 0.08, |
|
"springLength": 100, |
|
"damping": 0.4 |
|
}, |
|
"stabilization": { |
|
"iterations": 1000 |
|
} |
|
}, |
|
"nodes": { |
|
"shape": "dot", |
|
"font": { |
|
"size": 12 |
|
} |
|
}, |
|
"edges": { |
|
"arrows": { |
|
"to": { "enabled": true } |
|
}, |
|
"smooth": false |
|
} |
|
} |
|
""") |
|
|
|
|
|
try: |
|
|
|
html_content = net.generate_html() |
|
|
|
|
|
|
|
|
|
resizable_html = f""" |
|
<div style=" |
|
resize: both; |
|
overflow: auto; |
|
width: 100%; |
|
height: 600px; |
|
border: 1px solid #ccc; |
|
padding: 10px; |
|
"> |
|
{html_content} |
|
</div> |
|
""" |
|
|
|
|
|
components.html(resizable_html, height=620, scrolling=True) |
|
except AttributeError: |
|
try: |
|
|
|
html_content = net.to_html() |
|
resizable_html = f""" |
|
<div style=" |
|
resize: both; |
|
overflow: auto; |
|
width: 100%; |
|
height: 600px; |
|
border: 1px solid #ccc; |
|
padding: 10px; |
|
"> |
|
{html_content} |
|
</div> |
|
""" |
|
components.html(resizable_html, height=620, scrolling=True) |
|
except Exception as e: |
|
st.error(f"⚠️ An error occurred while generating the network chart: {e}") |
|
except Exception as e: |
|
st.error(f"⚠️ An error occurred while generating the network chart: {e}") |
|
|
|
|
|
if __name__ == "__main__": |
|
st.title("📬-------") |
|
|
|
|
|
|