Files changed (1) hide show
  1. app.py +124 -188
app.py CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import networkx as nx
@@ -5,10 +9,9 @@ import plotly.graph_objects as go
5
  import numpy as np
6
  from collections import defaultdict
7
 
8
- # ============================================================
9
- # DATA
10
- # ============================================================
11
-
12
  AMCS = [
13
  "SBI MF", "ICICI Pru MF", "HDFC MF", "Nippon India MF", "Kotak MF",
14
  "UTI MF", "Axis MF", "Aditya Birla SL MF", "Mirae MF", "DSP MF"
@@ -46,15 +49,8 @@ SELL_MAP = {
46
  "DSP MF": ["HAL", "Shriram Finance"]
47
  }
48
 
49
- COMPLETE_EXIT = {
50
- "DSP MF": ["Shriram Finance"]
51
- }
52
-
53
- FRESH_BUY = {
54
- "HDFC MF": ["Tata Elxsi"],
55
- "UTI MF": ["Adani Ports"],
56
- "Mirae MF": ["HAL"]
57
- }
58
 
59
 
60
  def sanitize_map(m):
@@ -69,10 +65,9 @@ SELL_MAP = sanitize_map(SELL_MAP)
69
  COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT)
70
  FRESH_BUY = sanitize_map(FRESH_BUY)
71
 
72
- # ============================================================
73
- # GRAPH BUILDING
74
- # ============================================================
75
-
76
  company_edges = []
77
  for amc, comps in BUY_MAP.items():
78
  for c in comps:
@@ -92,22 +87,18 @@ def infer_amc_transfers(buy_map, sell_map):
92
  transfers = defaultdict(int)
93
  company_to_sellers = defaultdict(list)
94
  company_to_buyers = defaultdict(list)
95
-
96
  for amc, comps in sell_map.items():
97
  for c in comps:
98
  company_to_sellers[c].append(amc)
99
-
100
  for amc, comps in buy_map.items():
101
  for c in comps:
102
  company_to_buyers[c].append(amc)
103
-
104
  for c in set(company_to_sellers.keys()) | set(company_to_buyers.keys()):
105
  sellers = company_to_sellers[c]
106
  buyers = company_to_buyers[c]
107
  for s in sellers:
108
  for b in buyers:
109
  transfers[(s, b)] += 1
110
-
111
  edge_list = []
112
  for (s, b), w in transfers.items():
113
  edge_list.append((s, b, {"action": "transfer", "weight": w}))
@@ -119,13 +110,10 @@ transfer_edges = infer_amc_transfers(BUY_MAP, SELL_MAP)
119
 
120
  def build_graph(include_transfers=True):
121
  G = nx.DiGraph()
122
-
123
  for a in AMCS:
124
  G.add_node(a, type="amc")
125
-
126
  for c in COMPANIES:
127
  G.add_node(c, type="company")
128
-
129
  for u, v, attr in company_edges:
130
  if u in G.nodes and v in G.nodes:
131
  if G.has_edge(u, v):
@@ -133,7 +121,6 @@ def build_graph(include_transfers=True):
133
  G[u][v]["actions"].append(attr["action"])
134
  else:
135
  G.add_edge(u, v, weight=attr["weight"], actions=[attr["action"]])
136
-
137
  if include_transfers:
138
  for s, b, attr in transfer_edges:
139
  if s in G.nodes and b in G.nodes:
@@ -142,111 +129,74 @@ def build_graph(include_transfers=True):
142
  G[s][b]["actions"].append("transfer")
143
  else:
144
  G.add_edge(s, b, weight=attr["weight"], actions=["transfer"])
145
-
146
  return G
147
 
148
- # ============================================================
149
- # PLOTLY NETWORK DRAWING
150
- # ============================================================
151
-
152
-
153
  def graph_to_plotly(
154
- G,
155
- node_color_amc="#9EC5FF",
156
- node_color_company="#FFCF9E",
157
- node_shape_amc="circle",
158
- node_shape_company="circle",
159
- edge_color_buy="#2ca02c",
160
- edge_color_sell="#d62728",
161
- edge_color_transfer="#888888",
162
- edge_thickness_base=1.4,
163
- show_labels=True
164
  ):
 
165
  pos = nx.spring_layout(G, seed=42, k=1.2)
166
 
167
  node_x, node_y, node_text, node_color, node_size = [], [], [], [], []
168
-
169
  for n, d in G.nodes(data=True):
170
  x, y = pos[n]
171
- node_x.append(x)
172
- node_y.append(y)
173
- node_text.append(n)
174
-
175
  if d["type"] == "amc":
176
- node_color.append(node_color_amc)
177
- node_size.append(40)
178
  else:
179
- node_color.append(node_color_company)
180
- node_size.append(60)
181
 
182
  node_trace = go.Scatter(
183
  x=node_x, y=node_y,
184
  mode="markers+text" if show_labels else "markers",
185
- marker=dict(
186
- color=node_color,
187
- size=node_size,
188
- line=dict(width=2, color="#222")
189
- ),
190
- text=node_text if show_labels else None,
191
- textposition="top center"
192
  )
193
 
194
  edge_traces = []
195
-
196
  for u, v, attrs in G.edges(data=True):
197
  acts = attrs.get("actions", [])
198
  weight = attrs.get("weight", 1)
199
-
200
- x0, y0 = pos[u]
201
- x1, y1 = pos[v]
202
-
203
  if "complete_exit" in acts:
204
- color = edge_color_sell
205
- dash = "solid"
206
- width = edge_thickness_base * 3
207
  elif "fresh_buy" in acts:
208
- color = edge_color_buy
209
- dash = "solid"
210
- width = edge_thickness_base * 3
211
  elif "transfer" in acts:
212
- color = edge_color_transfer
213
- dash = "dash"
214
- width = edge_thickness_base * (1 + np.log1p(weight))
215
  elif "sell" in acts:
216
- color = edge_color_sell
217
- dash = "dot"
218
- width = edge_thickness_base * (1 + np.log1p(weight))
219
  else:
220
- color = edge_color_buy
221
- dash = "solid"
222
- width = edge_thickness_base * (1 + np.log1p(weight))
223
-
224
- edge_traces.append(
225
- go.Scatter(
226
- x=[x0, x1, None],
227
- y=[y0, y1, None],
228
- mode="lines",
229
- line=dict(color=color, width=width, dash=dash),
230
- hoverinfo="none"
231
- )
232
- )
233
 
234
  fig = go.Figure(data=edge_traces + [node_trace])
235
- fig.update_layout(
236
- showlegend=False,
237
- height=900,
238
- width=1400,
239
- margin=dict(l=5, r=5, t=40, b=20),
240
- xaxis=dict(visible=False),
241
- yaxis=dict(visible=False)
242
- )
243
  return fig
244
 
245
- # ============================================================
246
- # COMPANY & AMC INSPECTION
247
- # ============================================================
248
-
249
-
250
  def company_trade_summary(company_name):
251
  buyers = [a for a, comps in BUY_MAP.items() if company_name in comps]
252
  sellers = [a for a, comps in SELL_MAP.items() if company_name in comps]
@@ -254,142 +204,123 @@ def company_trade_summary(company_name):
254
  exits = [a for a, comps in COMPLETE_EXIT.items() if company_name in comps]
255
 
256
  df = pd.DataFrame({
257
- "Role": ["Buyer"] * len(buyers) + ["Seller"] * len(sellers)
258
- + ["Fresh buy"] * len(fresh) + ["Complete exit"] * len(exits),
259
  "AMC": buyers + sellers + fresh + exits
260
  })
261
 
262
  if df.empty:
263
  return None, pd.DataFrame([], columns=["Role", "AMC"])
264
-
265
  counts = df.groupby("Role").size().reset_index(name="Count")
266
-
267
- fig = go.Figure(go.Bar(
268
- x=counts["Role"],
269
- y=counts["Count"],
270
- marker_color=["green", "red", "orange", "black"][:len(counts)]
271
- ))
272
- fig.update_layout(
273
- title_text=f"Trade summary for {company_name}",
274
- height=300
275
- )
276
  return fig, df
277
 
278
 
279
  def amc_transfer_summary(amc_name):
280
  sold = SELL_MAP.get(amc_name, [])
281
  transfers = []
282
-
283
  for s in sold:
284
  buyers = [a for a, comps in BUY_MAP.items() if s in comps]
285
  for b in buyers:
286
  transfers.append({"security": s, "buyer_amc": b})
287
-
288
  df = pd.DataFrame(transfers)
289
-
290
  if df.empty:
291
  return None, pd.DataFrame([], columns=["security", "buyer_amc"])
292
-
293
  counts = df["buyer_amc"].value_counts().reset_index()
294
  counts.columns = ["Buyer AMC", "Count"]
295
-
296
- fig = go.Figure(go.Bar(
297
- x=counts["Buyer AMC"],
298
- y=counts["Count"],
299
- marker_color="lightslategray"
300
- ))
301
- fig.update_layout(
302
- title_text=f"Inferred transfers from {amc_name}",
303
- height=300
304
- )
305
  return fig, df
306
 
307
- # ============================================================
308
- # INITIAL GRAPH
309
- # ============================================================
310
-
311
  initial_graph = build_graph(include_transfers=True)
312
  initial_fig = graph_to_plotly(initial_graph)
313
 
314
- # ============================================================
315
- # GRADIO UI CLEAN, FULL-WIDTH LAYOUT
316
- # ============================================================
 
 
 
 
 
 
 
 
 
317
 
318
- with gr.Blocks() as demo:
319
- gr.Markdown("## Mutual Fund Churn Explorer — Full Network & Transfer Analysis")
 
 
320
 
321
- # === FULL-WIDTH NETWORK GRAPH AT THE TOP ===
322
- network_plot = gr.Plot(
323
- value=initial_fig,
324
- label="Network graph (drag to zoom)"
325
- )
326
 
327
- # === SETTINGS BELOW THE GRAPH ===
328
- with gr.Accordion("Network Customization", open=True):
329
- node_color_company = gr.ColorPicker("#FFCF9E", label="Company node color")
330
- node_color_amc = gr.ColorPicker("#9EC5FF", label="AMC node color")
 
 
331
 
332
- node_shape_company = gr.Dropdown(["circle", "square", "diamond"], value="circle",
333
- label="Company node shape")
334
- node_shape_amc = gr.Dropdown(["circle", "square", "diamond"], value="circle",
335
- label="AMC node shape")
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  edge_color_buy = gr.ColorPicker("#2ca02c", label="BUY edge color")
338
  edge_color_sell = gr.ColorPicker("#d62728", label="SELL edge color")
339
  edge_color_transfer = gr.ColorPicker("#888888", label="Transfer edge color")
340
-
341
  edge_thickness = gr.Slider(0.5, 6.0, value=1.4, step=0.1, label="Edge thickness base")
342
  include_transfers = gr.Checkbox(value=True, label="Show AMC→AMC inferred transfers")
 
343
 
344
- update_button = gr.Button("Update Network Graph")
345
-
346
- gr.Markdown("### Inspect a Company (buyers / sellers)")
347
- select_company = gr.Dropdown(choices=COMPANIES, label="Select company")
348
  company_out_plot = gr.Plot(label="Company trade summary")
349
- company_out_table = gr.DataFrame(label="Company table")
350
 
351
- gr.Markdown("### Inspect an AMC (transfer analysis)")
352
- select_amc = gr.Dropdown(choices=AMCS, label="Select AMC")
353
  amc_out_plot = gr.Plot(label="AMC transfer summary")
354
  amc_out_table = gr.DataFrame(label="AMC transfer table")
355
 
356
- # === CALLBACKS ===
357
-
 
358
  def update_network(node_color_company_val, node_color_amc_val,
359
  node_shape_company_val, node_shape_amc_val,
360
  edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val,
361
  edge_thickness_val, include_transfers_val):
362
-
363
  G = build_graph(include_transfers=include_transfers_val)
364
- fig = graph_to_plotly(
365
- G,
366
- node_color_amc=node_color_amc_val,
367
- node_color_company=node_color_company_val,
368
- node_shape_amc=node_shape_amc_val,
369
- node_shape_company=node_shape_company_val,
370
- edge_color_buy=edge_color_buy_val,
371
- edge_color_sell=edge_color_sell_val,
372
- edge_color_transfer=edge_color_transfer_val,
373
- edge_thickness_base=edge_thickness_val,
374
- )
375
  return fig
376
 
377
- update_button.click(
378
- update_network,
379
- [
380
- node_color_company,
381
- node_color_amc,
382
- node_shape_company,
383
- node_shape_amc,
384
- edge_color_buy,
385
- edge_color_sell,
386
- edge_color_transfer,
387
- edge_thickness,
388
- include_transfers,
389
- ],
390
- [network_plot]
391
- )
392
-
393
  def handle_company(company):
394
  fig, df = company_trade_summary(company)
395
  return fig, df
@@ -398,9 +329,14 @@ with gr.Blocks() as demo:
398
  fig, df = amc_transfer_summary(amc)
399
  return fig, df
400
 
 
 
 
 
 
401
  select_company.change(handle_company, select_company, [company_out_plot, company_out_table])
402
  select_amc.change(handle_amc, select_amc, [amc_out_plot, amc_out_table])
403
 
404
-
405
  if __name__ == "__main__":
406
  demo.launch()
 
1
+ # app.py — Mobile-first, HF-iframe-friendly Gradio app
2
+ # Paste this into your Hugging Face Space (Gradio). Uses inline CSS to handle iframe constraints.
3
+ # Requirements: gradio, networkx, plotly, pandas, numpy
4
+
5
  import gradio as gr
6
  import pandas as pd
7
  import networkx as nx
 
9
  import numpy as np
10
  from collections import defaultdict
11
 
12
+ # ---------------------------
13
+ # Data (same sample dataset)
14
+ # ---------------------------
 
15
  AMCS = [
16
  "SBI MF", "ICICI Pru MF", "HDFC MF", "Nippon India MF", "Kotak MF",
17
  "UTI MF", "Axis MF", "Aditya Birla SL MF", "Mirae MF", "DSP MF"
 
49
  "DSP MF": ["HAL", "Shriram Finance"]
50
  }
51
 
52
+ COMPLETE_EXIT = {"DSP MF": ["Shriram Finance"]}
53
+ FRESH_BUY = {"HDFC MF": ["Tata Elxsi"], "UTI MF": ["Adani Ports"], "Mirae MF": ["HAL"]}
 
 
 
 
 
 
 
54
 
55
 
56
  def sanitize_map(m):
 
65
  COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT)
66
  FRESH_BUY = sanitize_map(FRESH_BUY)
67
 
68
+ # ---------------------------
69
+ # Graph construction
70
+ # ---------------------------
 
71
  company_edges = []
72
  for amc, comps in BUY_MAP.items():
73
  for c in comps:
 
87
  transfers = defaultdict(int)
88
  company_to_sellers = defaultdict(list)
89
  company_to_buyers = defaultdict(list)
 
90
  for amc, comps in sell_map.items():
91
  for c in comps:
92
  company_to_sellers[c].append(amc)
 
93
  for amc, comps in buy_map.items():
94
  for c in comps:
95
  company_to_buyers[c].append(amc)
 
96
  for c in set(company_to_sellers.keys()) | set(company_to_buyers.keys()):
97
  sellers = company_to_sellers[c]
98
  buyers = company_to_buyers[c]
99
  for s in sellers:
100
  for b in buyers:
101
  transfers[(s, b)] += 1
 
102
  edge_list = []
103
  for (s, b), w in transfers.items():
104
  edge_list.append((s, b, {"action": "transfer", "weight": w}))
 
110
 
111
  def build_graph(include_transfers=True):
112
  G = nx.DiGraph()
 
113
  for a in AMCS:
114
  G.add_node(a, type="amc")
 
115
  for c in COMPANIES:
116
  G.add_node(c, type="company")
 
117
  for u, v, attr in company_edges:
118
  if u in G.nodes and v in G.nodes:
119
  if G.has_edge(u, v):
 
121
  G[u][v]["actions"].append(attr["action"])
122
  else:
123
  G.add_edge(u, v, weight=attr["weight"], actions=[attr["action"]])
 
124
  if include_transfers:
125
  for s, b, attr in transfer_edges:
126
  if s in G.nodes and b in G.nodes:
 
129
  G[s][b]["actions"].append("transfer")
130
  else:
131
  G.add_edge(s, b, weight=attr["weight"], actions=["transfer"])
 
132
  return G
133
 
134
+ # ---------------------------
135
+ # Plotly drawing helper
136
+ # ---------------------------
 
 
137
  def graph_to_plotly(
138
+ G,
139
+ node_color_amc="#9EC5FF",
140
+ node_color_company="#FFCF9E",
141
+ edge_color_buy="#2ca02c",
142
+ edge_color_sell="#d62728",
143
+ edge_color_transfer="#888888",
144
+ edge_thickness_base=1.4,
145
+ show_labels=True,
 
 
146
  ):
147
+ # spring layout - deterministic seed
148
  pos = nx.spring_layout(G, seed=42, k=1.2)
149
 
150
  node_x, node_y, node_text, node_color, node_size = [], [], [], [], []
 
151
  for n, d in G.nodes(data=True):
152
  x, y = pos[n]
153
+ node_x.append(x); node_y.append(y); node_text.append(n)
 
 
 
154
  if d["type"] == "amc":
155
+ node_color.append(node_color_amc); node_size.append(36)
 
156
  else:
157
+ node_color.append(node_color_company); node_size.append(56)
 
158
 
159
  node_trace = go.Scatter(
160
  x=node_x, y=node_y,
161
  mode="markers+text" if show_labels else "markers",
162
+ marker=dict(color=node_color, size=node_size, line=dict(width=2, color="#222")),
163
+ text=node_text if show_labels else None, textposition="top center", hoverinfo="text"
 
 
 
 
 
164
  )
165
 
166
  edge_traces = []
 
167
  for u, v, attrs in G.edges(data=True):
168
  acts = attrs.get("actions", [])
169
  weight = attrs.get("weight", 1)
170
+ x0, y0 = pos[u]; x1, y1 = pos[v]
 
 
 
171
  if "complete_exit" in acts:
172
+ color = edge_color_sell; dash = "solid"; width = edge_thickness_base * 3
 
 
173
  elif "fresh_buy" in acts:
174
+ color = edge_color_buy; dash = "solid"; width = edge_thickness_base * 3
 
 
175
  elif "transfer" in acts:
176
+ color = edge_color_transfer; dash = "dash"; width = edge_thickness_base * (1 + np.log1p(weight))
 
 
177
  elif "sell" in acts:
178
+ color = edge_color_sell; dash = "dot"; width = edge_thickness_base * (1 + np.log1p(weight))
 
 
179
  else:
180
+ color = edge_color_buy; dash = "solid"; width = edge_thickness_base * (1 + np.log1p(weight))
181
+
182
+ edge_traces.append(go.Scatter(
183
+ x=[x0, x1, None], y=[y0, y1, None],
184
+ mode="lines", line=dict(color=color, width=width, dash=dash),
185
+ hoverinfo="none"
186
+ ))
 
 
 
 
 
 
187
 
188
  fig = go.Figure(data=edge_traces + [node_trace])
189
+ # use autosize for better responsiveness inside iframe
190
+ fig.update_layout(showlegend=False,
191
+ autosize=True,
192
+ margin=dict(l=8, r=8, t=36, b=8),
193
+ xaxis=dict(visible=False),
194
+ yaxis=dict(visible=False))
 
 
195
  return fig
196
 
197
+ # ---------------------------
198
+ # Summaries (company/amc)
199
+ # ---------------------------
 
 
200
  def company_trade_summary(company_name):
201
  buyers = [a for a, comps in BUY_MAP.items() if company_name in comps]
202
  sellers = [a for a, comps in SELL_MAP.items() if company_name in comps]
 
204
  exits = [a for a, comps in COMPLETE_EXIT.items() if company_name in comps]
205
 
206
  df = pd.DataFrame({
207
+ "Role": ["Buyer"] * len(buyers) + ["Seller"] * len(sellers) + ["Fresh buy"] * len(fresh) + ["Complete exit"] * len(exits),
 
208
  "AMC": buyers + sellers + fresh + exits
209
  })
210
 
211
  if df.empty:
212
  return None, pd.DataFrame([], columns=["Role", "AMC"])
 
213
  counts = df.groupby("Role").size().reset_index(name="Count")
214
+ colors = ["green", "red", "orange", "black"][:len(counts)]
215
+ fig = go.Figure(go.Bar(x=counts["Role"], y=counts["Count"], marker_color=colors))
216
+ fig.update_layout(title_text=f"Trade summary for {company_name}", autosize=True, margin=dict(t=30,b=10))
 
 
 
 
 
 
 
217
  return fig, df
218
 
219
 
220
  def amc_transfer_summary(amc_name):
221
  sold = SELL_MAP.get(amc_name, [])
222
  transfers = []
 
223
  for s in sold:
224
  buyers = [a for a, comps in BUY_MAP.items() if s in comps]
225
  for b in buyers:
226
  transfers.append({"security": s, "buyer_amc": b})
 
227
  df = pd.DataFrame(transfers)
 
228
  if df.empty:
229
  return None, pd.DataFrame([], columns=["security", "buyer_amc"])
 
230
  counts = df["buyer_amc"].value_counts().reset_index()
231
  counts.columns = ["Buyer AMC", "Count"]
232
+ fig = go.Figure(go.Bar(x=counts["Buyer AMC"], y=counts["Count"], marker_color="lightslategray"))
233
+ fig.update_layout(title_text=f"Inferred transfers from {amc_name}", autosize=True, margin=dict(t=30,b=10))
 
 
 
 
 
 
 
 
234
  return fig, df
235
 
236
+ # ---------------------------
237
+ # Initial graph
238
+ # ---------------------------
 
239
  initial_graph = build_graph(include_transfers=True)
240
  initial_fig = graph_to_plotly(initial_graph)
241
 
242
+ # ---------------------------
243
+ # Mobile-first CSS (override HF iframe quirks)
244
+ # ---------------------------
245
+ responsive_css = """
246
+ /* Remove excessive padding inside HF iframe */
247
+ .gradio-container { padding: 0 !important; margin: 0 !important; }
248
+
249
+ /* Make the plot area truly full-width */
250
+ .plotly-graph-div, .js-plotly-plot, .output_plot {
251
+ width: 100% !important;
252
+ max-width: 100% !important;
253
+ }
254
 
255
+ /* Ensure plot will shrink on small screens but remain legible */
256
+ .js-plotly-plot {
257
+ height: 460px !important;
258
+ }
259
 
260
+ /* Make controls compact and finger-friendly */
261
+ .gradio-container .gr-input, .gradio-container .gr-button {
262
+ width: 100% !important;
263
+ }
 
264
 
265
+ /* Accordion collapsed by default on mobile; larger touch targets */
266
+ @media only screen and (max-width: 780px) {
267
+ .js-plotly-plot { height: 420px !important; }
268
+ .gr-accordion { font-size: 15px; }
269
+ .gradio-container { padding: 6px !important; }
270
+ }
271
 
272
+ /* Avoid horizontal scroll and ensure content uses available width */
273
+ body, html { overflow-x: hidden !important; }
274
+ """
 
275
 
276
+ # ---------------------------
277
+ # Gradio UI (Blocks) — accordion closed by default (mobile-first)
278
+ # ---------------------------
279
+ with gr.Blocks(css=responsive_css, title="MF Churn Explorer (mobile-first)") as demo:
280
+ gr.Markdown("## Mutual Fund Churn Explorer — Mobile Friendly")
281
+ # Full-width network on top
282
+ network_plot = gr.Plot(value=initial_fig, label="Network graph (tap to zoom)")
283
+
284
+ # Controls in a collapsed accordion (closed by default to save vertical space)
285
+ with gr.Accordion("Network Customization — expand to edit", open=False):
286
+ node_color_company = gr.ColorPicker("#FFCF9E", label="Company node color")
287
+ node_color_amc = gr.ColorPicker("#9EC5FF", label="AMC node color")
288
+ node_shape_company = gr.Dropdown(["circle", "square", "diamond"], value="circle", label="Company node shape")
289
+ node_shape_amc = gr.Dropdown(["circle", "square", "diamond"], value="circle", label="AMC node shape")
290
  edge_color_buy = gr.ColorPicker("#2ca02c", label="BUY edge color")
291
  edge_color_sell = gr.ColorPicker("#d62728", label="SELL edge color")
292
  edge_color_transfer = gr.ColorPicker("#888888", label="Transfer edge color")
 
293
  edge_thickness = gr.Slider(0.5, 6.0, value=1.4, step=0.1, label="Edge thickness base")
294
  include_transfers = gr.Checkbox(value=True, label="Show AMC→AMC inferred transfers")
295
+ update_button = gr.Button("Update network")
296
 
297
+ gr.Markdown("### Quick inspection (mobile)")
298
+ select_company = gr.Dropdown(choices=COMPANIES, label="Select company (buyers / sellers)")
 
 
299
  company_out_plot = gr.Plot(label="Company trade summary")
300
+ company_out_table = gr.DataFrame(label="Company trade table")
301
 
302
+ select_amc = gr.Dropdown(choices=AMCS, label="Select AMC (inferred transfers)")
 
303
  amc_out_plot = gr.Plot(label="AMC transfer summary")
304
  amc_out_table = gr.DataFrame(label="AMC transfer table")
305
 
306
+ # ---------------------------
307
+ # Callbacks
308
+ # ---------------------------
309
  def update_network(node_color_company_val, node_color_amc_val,
310
  node_shape_company_val, node_shape_amc_val,
311
  edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val,
312
  edge_thickness_val, include_transfers_val):
 
313
  G = build_graph(include_transfers=include_transfers_val)
314
+ fig = graph_to_plotly(G,
315
+ node_color_amc=node_color_amc_val,
316
+ node_color_company=node_color_company_val,
317
+ edge_color_buy=edge_color_buy_val,
318
+ edge_color_sell=edge_color_sell_val,
319
+ edge_color_transfer=edge_color_transfer_val,
320
+ edge_thickness_base=edge_thickness_val,
321
+ show_labels=True)
 
 
 
322
  return fig
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  def handle_company(company):
325
  fig, df = company_trade_summary(company)
326
  return fig, df
 
329
  fig, df = amc_transfer_summary(amc)
330
  return fig, df
331
 
332
+ update_button.click(update_network,
333
+ inputs=[node_color_company, node_color_amc, node_shape_company, node_shape_amc,
334
+ edge_color_buy, edge_color_sell, edge_color_transfer, edge_thickness, include_transfers],
335
+ outputs=[network_plot])
336
+
337
  select_company.change(handle_company, select_company, [company_out_plot, company_out_table])
338
  select_amc.change(handle_amc, select_amc, [amc_out_plot, amc_out_table])
339
 
340
+ # Launch
341
  if __name__ == "__main__":
342
  demo.launch()