Files changed (1) hide show
  1. app.py +45 -36
app.py CHANGED
@@ -1,7 +1,12 @@
1
  # app.py
2
- # Mutual Fund Churn Explorer - final version with collapsible sidebar + deep green theme
3
- # Save as app.py and run with: python app.py
4
- # requirements.txt should include: gradio, networkx, plotly, pandas, numpy
 
 
 
 
 
5
 
6
  import gradio as gr
7
  import pandas as pd
@@ -182,8 +187,8 @@ def build_graph(AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY, in
182
  # Plotly visualizer (coerce width/height -> int with minimums)
183
  # ---------------------------
184
  def graph_to_plotly(G,
185
- node_color_amc="#0f5132", # deep green theme: use darker green for AMCs by default
186
- node_color_company="#ffc107", # amber for companies — user can change
187
  node_shape_amc="circle",
188
  node_shape_company="circle",
189
  edge_color_buy="#28a745",
@@ -193,7 +198,7 @@ def graph_to_plotly(G,
193
  show_labels=True,
194
  width=1400,
195
  height=900):
196
- # ensure width/height are native ints and sensible
197
  try:
198
  width = int(float(width))
199
  except Exception:
@@ -207,7 +212,6 @@ def graph_to_plotly(G,
207
  if height < 360:
208
  height = 360
209
 
210
- # layout: spring layout for clarity
211
  pos = nx.spring_layout(G, seed=42, k=1.4)
212
 
213
  node_x = []
@@ -236,14 +240,12 @@ def graph_to_plotly(G,
236
  hoverinfo='text'
237
  )
238
 
239
- # edges rendered individually so they can be styled
240
  edge_traces = []
241
  for u, v, attrs in G.edges(data=True):
242
  x0, y0 = pos[u]
243
  x1, y1 = pos[v]
244
  actions = attrs.get("actions", [])
245
  weight = float(attrs.get("weight", 1.0))
246
- # priority styling
247
  if "complete_exit" in actions:
248
  color = edge_color_sell
249
  dash = "solid"
@@ -359,39 +361,46 @@ def build_initial_graph_and_data():
359
  (AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY, G_initial, initial_fig) = build_initial_graph_and_data()
360
 
361
  # ---------------------------
362
- # GRADIO UI: deep-green theme + collapsible sidebar
363
  # ---------------------------
364
- # Create a deep green theme using gradio theme API (primary hue green, darker accents)
365
  deep_green_theme = gr.themes.Soft(primary_hue="green", secondary_hue="teal", spacing_size="md")
366
 
367
  with gr.Blocks(theme=deep_green_theme) as demo:
368
- gr.Markdown("# Mutual Fund Churn Explorer — Deep Green Theme")
 
369
  with gr.Row():
370
- # Collapsible sidebar using Accordion
371
- with gr.Accordion("⚙️ Settings (click to expand / collapse)", open=False):
372
- with gr.Column():
373
- gr.Markdown("### Data Input")
374
- csv_uploader = gr.File(label="Upload CSV (optional). Columns: AMC,Company,Action", file_types=['.csv'])
375
- gr.Markdown("### Node Appearance")
376
- node_color_company = gr.ColorPicker(value="#ffc107", label="Company node color")
377
- node_color_amc = gr.ColorPicker(value="#0f5132", label="AMC node color")
378
- node_shape_company = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="Company node shape")
379
- node_shape_amc = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="AMC node shape")
380
- gr.Markdown("### Edge Appearance")
381
- edge_color_buy = gr.ColorPicker(value="#28a745", label="BUY edge color")
382
- edge_color_sell = gr.ColorPicker(value="#dc3545", label="SELL edge color")
383
- edge_color_transfer = gr.ColorPicker(value="#6c757d", label="Transfer edge color")
384
- edge_thickness = gr.Slider(minimum=0.5, maximum=8.0, value=1.4, step=0.1, label="Edge thickness base")
385
- include_transfers_chk = gr.Checkbox(value=True, label="Infer AMC→AMC transfers (show loops)")
386
- update_btn = gr.Button("Update network")
387
- gr.Markdown("### Inspect")
388
- # do not set default value to avoid mismatch warnings after dropdown choices update
389
- company_selector = gr.Dropdown(choices=COMPANIES, label="Select Company (show buyers/sellers)")
390
- amc_selector = gr.Dropdown(choices=AMCS, label="Select AMC (inferred transfers)")
391
- # Graph column
392
- with gr.Column():
393
  network_plot = gr.Plot(value=initial_fig, label="Network graph (drag to zoom)")
394
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  # outputs for inspection
396
  company_plot = gr.Plot(label="Company trade summary")
397
  company_table = gr.Dataframe(headers=["Role","AMC"], interactive=False, label="Trades (company)")
@@ -408,7 +417,6 @@ with gr.Blocks(theme=deep_green_theme) as demo:
408
  if isinstance(raw, bytes):
409
  raw = raw.decode('utf-8', errors='ignore')
410
  df = pd.read_csv(io.StringIO(raw))
411
- cols = [c.strip().lower() for c in df.columns]
412
  col_map = {}
413
  for c in df.columns:
414
  if c.strip().lower() in ("amc","fund","manager"):
@@ -449,6 +457,7 @@ with gr.Blocks(theme=deep_green_theme) as demo:
449
  loops_md += f"- Loop {i}: " + " → ".join(loop) + "\n"
450
  else:
451
  loops_md = "No small transfer loops detected (based on current inferred transfer edges)."
 
452
  return fig, loops_md, companies, amcs
453
 
454
  update_btn.click(on_update,
 
1
  # app.py
2
+ # Mutual Fund Churn Explorer Modal settings version (full file)
3
+ # - Uses a Modal for settings so the controls don't reserve horizontal space
4
+ # - Deep-green theme preserved
5
+ # - Robust: safe CSV parsing, defensive dropdown handling, inferred AMC->AMC transfers,
6
+ # loop detection, and export-ready Plotly figure sizing.
7
+ #
8
+ # Save this as app.py and run with: python app.py
9
+ # requirements.txt: gradio, networkx, plotly, pandas, numpy
10
 
11
  import gradio as gr
12
  import pandas as pd
 
187
  # Plotly visualizer (coerce width/height -> int with minimums)
188
  # ---------------------------
189
  def graph_to_plotly(G,
190
+ node_color_amc="#0f5132", # deep green default for AMCs
191
+ node_color_company="#ffc107", # amber for companies
192
  node_shape_amc="circle",
193
  node_shape_company="circle",
194
  edge_color_buy="#28a745",
 
198
  show_labels=True,
199
  width=1400,
200
  height=900):
201
+ # coerce width/height to Python int and enforce sensible minimums
202
  try:
203
  width = int(float(width))
204
  except Exception:
 
212
  if height < 360:
213
  height = 360
214
 
 
215
  pos = nx.spring_layout(G, seed=42, k=1.4)
216
 
217
  node_x = []
 
240
  hoverinfo='text'
241
  )
242
 
 
243
  edge_traces = []
244
  for u, v, attrs in G.edges(data=True):
245
  x0, y0 = pos[u]
246
  x1, y1 = pos[v]
247
  actions = attrs.get("actions", [])
248
  weight = float(attrs.get("weight", 1.0))
 
249
  if "complete_exit" in actions:
250
  color = edge_color_sell
251
  dash = "solid"
 
361
  (AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY, G_initial, initial_fig) = build_initial_graph_and_data()
362
 
363
  # ---------------------------
364
+ # GRADIO UI: Modal settings + deep-green theme
365
  # ---------------------------
 
366
  deep_green_theme = gr.themes.Soft(primary_hue="green", secondary_hue="teal", spacing_size="md")
367
 
368
  with gr.Blocks(theme=deep_green_theme) as demo:
369
+ gr.Markdown("# Mutual Fund Churn Explorer")
370
+ # Top row: tiny settings button on left, graph uses rest of width
371
  with gr.Row():
372
+ with gr.Column(scale=1, min_width=80):
373
+ settings_btn = gr.Button(value="⚙️ Settings", elem_id="settings_btn")
374
+ gr.Markdown("<small style='color:var(--color-text-muted)'>Open settings</small>")
375
+ with gr.Column(scale=11):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  network_plot = gr.Plot(value=initial_fig, label="Network graph (drag to zoom)")
377
 
378
+ # Modal (floating) -- does not reserve space when closed
379
+ settings_modal = gr.Modal(title="Settings", open=False)
380
+ with settings_modal:
381
+ gr.Markdown("### Data Input")
382
+ csv_uploader = gr.File(label="Upload CSV (optional). Columns: AMC,Company,Action", file_types=['.csv'])
383
+ gr.Markdown("### Node Appearance")
384
+ node_color_company = gr.ColorPicker(value="#ffc107", label="Company node color")
385
+ node_color_amc = gr.ColorPicker(value="#0f5132", label="AMC node color")
386
+ node_shape_company = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="Company node shape")
387
+ node_shape_amc = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="AMC node shape")
388
+ gr.Markdown("### Edge Appearance")
389
+ edge_color_buy = gr.ColorPicker(value="#28a745", label="BUY edge color")
390
+ edge_color_sell = gr.ColorPicker(value="#dc3545", label="SELL edge color")
391
+ edge_color_transfer = gr.ColorPicker(value="#6c757d", label="Transfer edge color")
392
+ edge_thickness = gr.Slider(minimum=0.5, maximum=8.0, value=1.4, step=0.1, label="Edge thickness base")
393
+ include_transfers_chk = gr.Checkbox(value=True, label="Infer AMC→AMC transfers (show loops)")
394
+ update_btn = gr.Button("Update network")
395
+ gr.Markdown("### Inspect")
396
+ company_selector = gr.Dropdown(choices=COMPANIES, label="Select Company (show buyers/sellers)")
397
+ amc_selector = gr.Dropdown(choices=AMCS, label="Select AMC (inferred transfers)")
398
+
399
+ # clicking settings button opens modal
400
+ def open_modal():
401
+ return gr.update(open=True)
402
+ settings_btn.click(fn=open_modal, inputs=None, outputs=[settings_modal])
403
+
404
  # outputs for inspection
405
  company_plot = gr.Plot(label="Company trade summary")
406
  company_table = gr.Dataframe(headers=["Role","AMC"], interactive=False, label="Trades (company)")
 
417
  if isinstance(raw, bytes):
418
  raw = raw.decode('utf-8', errors='ignore')
419
  df = pd.read_csv(io.StringIO(raw))
 
420
  col_map = {}
421
  for c in df.columns:
422
  if c.strip().lower() in ("amc","fund","manager"):
 
457
  loops_md += f"- Loop {i}: " + " → ".join(loop) + "\n"
458
  else:
459
  loops_md = "No small transfer loops detected (based on current inferred transfer edges)."
460
+ # also return new choices for selectors to keep them in sync
461
  return fig, loops_md, companies, amcs
462
 
463
  update_btn.click(on_update,