gmedin commited on
Commit
292c185
·
verified ·
1 Parent(s): 122c116

edge and node size reflect popularity

Browse files
Files changed (1) hide show
  1. app.py +54 -73
app.py CHANGED
@@ -3,6 +3,7 @@ import networkx as nx
3
  from pyvis.network import Network
4
  import pickle
5
  import math
 
6
 
7
  # Dictionary to map brands to their respective pickle files
8
  BRAND_GRAPHS = {
@@ -16,10 +17,6 @@ BRAND_GRAPHS = {
16
  def load_graph(brand):
17
  """
18
  Load the graph for the selected brand.
19
- Parameters:
20
- brand (str): The brand name corresponding to the graph to load.
21
- Returns:
22
- nx.DiGraph: The loaded graph.
23
  """
24
  with open(BRAND_GRAPHS[brand], 'rb') as f:
25
  return pickle.load(f)
@@ -27,32 +24,21 @@ def load_graph(brand):
27
  def filter_graph(graph, node_threshold=10, edge_threshold=5):
28
  """
29
  Filters the graph to include only popular nodes and edges.
30
-
31
- Parameters:
32
- graph (nx.DiGraph): The original graph.
33
- node_threshold (int): Minimum degree for a node to be included.
34
- edge_threshold (int): Minimum weight for an edge to be included.
35
-
36
- Returns:
37
- nx.DiGraph: A filtered graph with popular nodes and edges.
38
  """
39
- # Identify popular nodes based on their degree
40
  popular_nodes = [
41
  node for node in graph.nodes
42
  if graph.degree(node) >= node_threshold
43
  ]
44
 
45
- # Create a subgraph with only popular nodes
46
  filtered_graph = graph.subgraph(popular_nodes).copy()
47
 
48
- # Remove edges that don't meet the weight threshold
49
  for u, v, data in list(filtered_graph.edges(data=True)):
50
  if data.get("weight", 0) < edge_threshold:
51
  filtered_graph.remove_edge(u, v)
52
 
53
  return filtered_graph
54
 
55
- def dynamic_visualize_graph(graph, start_node, layers=3, top_k=5):
56
  net = Network(notebook=False, width="100%", height="600px", directed=True)
57
  net.set_options("""
58
  var options = {
@@ -66,12 +52,22 @@ def dynamic_visualize_graph(graph, start_node, layers=3, top_k=5):
66
  """)
67
 
68
  visited_nodes = set()
69
- added_edges = set() # Track edges to avoid duplicates
70
- current_nodes = [int(start_node)] # Convert start_node to int
71
 
72
  # Add the starting node, color it red, and include a tooltip
73
- start_title = graph.nodes[int(start_node)].get('title', 'No title available') # Get the title attribute
74
- net.add_node(int(start_node), label=str(start_node), color="red", title=start_title)
 
 
 
 
 
 
 
 
 
 
75
  visited_nodes.add(int(start_node))
76
 
77
  for layer in range(layers):
@@ -81,75 +77,58 @@ def dynamic_visualize_graph(graph, start_node, layers=3, top_k=5):
81
  [(int(neighbor), data['weight']) for neighbor, data in graph[node].items()],
82
  key=lambda x: x[1],
83
  reverse=True
84
- )[:top_k] # Get top_k neighbors for this node
85
 
86
  for neighbor, weight in neighbors:
87
  if neighbor not in visited_nodes:
88
- neighbor_title = graph.nodes[neighbor].get('title', 'No title available') # Get tooltip
89
- net.add_node(neighbor, label=str(neighbor), title=neighbor_title) # Add node with tooltip
90
- edge = (node, neighbor) # Represent the edge as a tuple
 
 
 
 
 
 
 
 
 
 
 
 
91
  if edge not in added_edges:
92
- net.add_edge(node, neighbor, label=f"w:{weight}") # Add the edge only if not already added
93
- added_edges.add(edge) # Track this edge as added
 
94
  visited_nodes.add(neighbor)
95
- next_nodes.append(neighbor) # Always add to next_nodes for further expansion
96
 
97
- current_nodes = next_nodes # Move to the next layer
98
 
99
- # Generate the final visualization
100
  html_content = net.generate_html()
101
  st.components.v1.html(html_content, height=600, scrolling=False)
102
 
103
 
104
- def display_node_info(graph, node_id):
105
- """
106
- Display all attributes of a node and its edges in the graph.
107
-
108
- Parameters:
109
- graph (nx.DiGraph): The graph containing the node.
110
- node_id (int or str): The ID of the node to inspect.
111
-
112
- Returns:
113
- None
114
- """
115
- if node_id not in graph:
116
- print(f"Node {node_id} does not exist in the graph.")
117
- return
118
-
119
- # Display node attributes
120
- print(f"Attributes of node {node_id}:")
121
- for attr, value in graph.nodes[node_id].items():
122
- print(f" {attr}: {value}")
123
-
124
- # Display incoming edges
125
- print(f"\nIncoming edges to node {node_id}:")
126
- for u, v, data in graph.in_edges(node_id, data=True):
127
- print(f" From {u} to {v} with attributes: {data}")
128
-
129
- # Display outgoing edges
130
- print(f"\nOutgoing edges from node {node_id}:")
131
- for u, v, data in graph.out_edges(node_id, data=True):
132
- print(f" From {u} to {v} with attributes: {data}")
133
-
134
-
135
- # Streamlit interface
136
- st.title("Interactive Graph Expansion with Tooltips")
137
 
138
  # Brand Selection
139
  selected_brand = st.selectbox("Select a brand:", options=list(BRAND_GRAPHS.keys()))
140
 
141
- import random
142
-
143
- # Check if the brand has changed
144
  if "selected_brand" not in st.session_state or st.session_state.selected_brand != selected_brand:
145
- # Load the new graph and reset the start node
146
  st.session_state.selected_brand = selected_brand
147
  G = load_graph(selected_brand)
148
- st.session_state.start_node = random.choice(list(G.nodes))
 
 
 
 
149
  else:
150
- # Use the existing graph
151
  G = load_graph(selected_brand)
152
 
 
 
 
 
153
  # Input: Starting node
154
  start_node = st.number_input(
155
  "Enter the starting node ID:",
@@ -157,17 +136,19 @@ start_node = st.number_input(
157
  step=1
158
  )
159
 
160
- # Filter the graph for popular nodes and edges
161
- node_degree_threshold = 1 # Minimum degree for nodes
162
- edge_weight_threshold = 1 # Minimum weight for edges
 
 
 
163
  G_filtered = filter_graph(G, node_threshold=node_degree_threshold, edge_threshold=edge_weight_threshold)
164
 
165
  layers = st.slider("Depth to explore:", 1, 6, value=3)
166
  top_k = st.slider("Branching factor (per node):", 1, 6, value=3)
167
 
168
- # Trigger the visualization
169
  if st.button("Expand Graph"):
170
  if start_node in G_filtered:
171
- dynamic_visualize_graph(G_filtered, start_node, layers=layers, top_k=top_k)
172
  else:
173
  st.error("The starting node is not in the graph!")
 
3
  from pyvis.network import Network
4
  import pickle
5
  import math
6
+ import random
7
 
8
  # Dictionary to map brands to their respective pickle files
9
  BRAND_GRAPHS = {
 
17
  def load_graph(brand):
18
  """
19
  Load the graph for the selected brand.
 
 
 
 
20
  """
21
  with open(BRAND_GRAPHS[brand], 'rb') as f:
22
  return pickle.load(f)
 
24
  def filter_graph(graph, node_threshold=10, edge_threshold=5):
25
  """
26
  Filters the graph to include only popular nodes and edges.
 
 
 
 
 
 
 
 
27
  """
 
28
  popular_nodes = [
29
  node for node in graph.nodes
30
  if graph.degree(node) >= node_threshold
31
  ]
32
 
 
33
  filtered_graph = graph.subgraph(popular_nodes).copy()
34
 
 
35
  for u, v, data in list(filtered_graph.edges(data=True)):
36
  if data.get("weight", 0) < edge_threshold:
37
  filtered_graph.remove_edge(u, v)
38
 
39
  return filtered_graph
40
 
41
+ def dynamic_visualize_graph(graph, start_node, layers=3, top_k=5, show_titles=False):
42
  net = Network(notebook=False, width="100%", height="600px", directed=True)
43
  net.set_options("""
44
  var options = {
 
52
  """)
53
 
54
  visited_nodes = set()
55
+ added_edges = set()
56
+ current_nodes = [int(start_node)]
57
 
58
  # Add the starting node, color it red, and include a tooltip
59
+ start_title = graph.nodes[int(start_node)].get('title', 'No title available')
60
+ start_in_degree = graph.in_degree(int(start_node))
61
+ start_out_degree = graph.out_degree(int(start_node))
62
+ start_node_size = (start_in_degree + start_out_degree) * 0.15
63
+ label = str(start_node) if not show_titles else f"{str(start_node)}: {start_title[:15]}..." # Adjust title length
64
+ net.add_node(
65
+ int(start_node),
66
+ label=label,
67
+ color="darkblue",
68
+ title=f"{start_title} In-degree: {start_in_degree}, Out-degree: {start_out_degree}",
69
+ size=start_node_size
70
+ )
71
  visited_nodes.add(int(start_node))
72
 
73
  for layer in range(layers):
 
77
  [(int(neighbor), data['weight']) for neighbor, data in graph[node].items()],
78
  key=lambda x: x[1],
79
  reverse=True
80
+ )[:top_k]
81
 
82
  for neighbor, weight in neighbors:
83
  if neighbor not in visited_nodes:
84
+ neighbor_title = graph.nodes[neighbor].get('title', 'No title available')
85
+ neighbor_in_degree = graph.in_degree(neighbor)
86
+ neighbor_out_degree = graph.out_degree(neighbor)
87
+ neighbor_size = (neighbor_in_degree + neighbor_out_degree) * 0.15
88
+ node_color = 'red' if neighbor_in_degree > neighbor_out_degree * 1.5 else \
89
+ 'green' if neighbor_out_degree > neighbor_in_degree * 1.5 else 'lightblue'
90
+ label = str(neighbor) if not show_titles else f"{str(neighbor)}: {neighbor_title[:15]}..."
91
+ net.add_node(
92
+ neighbor,
93
+ label=label,
94
+ title=f"{neighbor_title} In-degree: {neighbor_in_degree}, Out-degree: {neighbor_out_degree}",
95
+ size=neighbor_size,
96
+ color=node_color
97
+ )
98
+ edge = (node, neighbor)
99
  if edge not in added_edges:
100
+ edge_width = math.log(weight + 1) * 8
101
+ net.add_edge(node, neighbor, label=f"w:{weight}", width=edge_width, color='lightblue')
102
+ added_edges.add(edge)
103
  visited_nodes.add(neighbor)
104
+ next_nodes.append(neighbor)
105
 
106
+ current_nodes = next_nodes
107
 
 
108
  html_content = net.generate_html()
109
  st.components.v1.html(html_content, height=600, scrolling=False)
110
 
111
 
112
+ st.title("Interactive Graph Expansion with Toggle for Content Titles")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  # Brand Selection
115
  selected_brand = st.selectbox("Select a brand:", options=list(BRAND_GRAPHS.keys()))
116
 
 
 
 
117
  if "selected_brand" not in st.session_state or st.session_state.selected_brand != selected_brand:
 
118
  st.session_state.selected_brand = selected_brand
119
  G = load_graph(selected_brand)
120
+
121
+ # Sort nodes by popularity (in-degree + out-degree) and select from top 20
122
+ popular_nodes = sorted(G.nodes, key=lambda n: G.in_degree(n) + G.out_degree(n), reverse=True)
123
+ top_20_nodes = popular_nodes[:20] if len(popular_nodes) > 20 else popular_nodes
124
+ st.session_state.start_node = random.choice(top_20_nodes)
125
  else:
 
126
  G = load_graph(selected_brand)
127
 
128
+ # Random Selection Button
129
+ if st.button("Random Selection"):
130
+ st.session_state.start_node = random.choice(list(G.nodes))
131
+
132
  # Input: Starting node
133
  start_node = st.number_input(
134
  "Enter the starting node ID:",
 
136
  step=1
137
  )
138
 
139
+ # Toggle for showing content titles
140
+ show_titles = st.checkbox("Show content titles", value=False)
141
+
142
+ # Filter the graph
143
+ node_degree_threshold = 1
144
+ edge_weight_threshold = 1
145
  G_filtered = filter_graph(G, node_threshold=node_degree_threshold, edge_threshold=edge_weight_threshold)
146
 
147
  layers = st.slider("Depth to explore:", 1, 6, value=3)
148
  top_k = st.slider("Branching factor (per node):", 1, 6, value=3)
149
 
 
150
  if st.button("Expand Graph"):
151
  if start_node in G_filtered:
152
+ dynamic_visualize_graph(G_filtered, start_node, layers=layers, top_k=top_k, show_titles=show_titles)
153
  else:
154
  st.error("The starting node is not in the graph!")