Spaces:
Sleeping
Sleeping
| # app.py | |
| # Mutual Fund Churn Explorer — Custom Modal (no Gradio.Modal required) | |
| # Works on any Gradio version, including Hugging Face default | |
| import gradio as gr | |
| import pandas as pd | |
| import networkx as nx | |
| import plotly.graph_objects as go | |
| import numpy as np | |
| from collections import defaultdict | |
| import io | |
| ######################################## | |
| # DATA + LOGIC (unchanged from before) | |
| ######################################## | |
| DEFAULT_AMCS = [ | |
| "SBI MF","ICICI Pru MF","HDFC MF","Nippon India MF","Kotak MF", | |
| "UTI MF","Axis MF","Aditya Birla SL MF","Mirae MF","DSP MF" | |
| ] | |
| DEFAULT_COMPANIES = [ | |
| "HDFC Bank","ICICI Bank","Bajaj Finance","Bajaj Finserv","Adani Ports", | |
| "Tata Motors","Shriram Finance","HAL","TCS","AU Small Finance Bank", | |
| "Pearl Global","Hindalco","Tata Elxsi","Cummins India","Vedanta" | |
| ] | |
| SAMPLE_BUY = { | |
| "SBI MF":["Bajaj Finance","AU Small Finance Bank"], | |
| "ICICI Pru MF":["HDFC Bank"], | |
| "HDFC MF":["Tata Elxsi","TCS"], | |
| "Nippon India MF":["Hindalco"], | |
| "Kotak MF":["Bajaj Finance"], | |
| "UTI MF":["Adani Ports","Shriram Finance"], | |
| "Axis MF":["Tata Motors","Shriram Finance"], | |
| "Aditya Birla SL MF":["AU Small Finance Bank"], | |
| "Mirae MF":["Bajaj Finance","HAL"], | |
| "DSP MF":["Tata Motors","Bajaj Finserv"] | |
| } | |
| SAMPLE_SELL = { | |
| "SBI MF":["Tata Motors"], | |
| "ICICI Pru MF":["Bajaj Finance","Adani Ports"], | |
| "HDFC MF":["HDFC Bank"], | |
| "Nippon India MF":["Hindalco"], | |
| "Kotak MF":["AU Small Finance Bank"], | |
| "UTI MF":["Hindalco","TCS"], | |
| "Axis MF":["TCS"], | |
| "Aditya Birla SL MF":["Adani Ports"], | |
| "Mirae MF":["TCS"], | |
| "DSP MF":["HAL","Shriram Finance"] | |
| } | |
| SAMPLE_COMPLETE_EXIT = {"DSP MF":["Shriram Finance"]} | |
| SAMPLE_FRESH_BUY = {"HDFC MF":["Tata Elxsi"],"UTI MF":["Adani Ports"],"Mirae MF":["HAL"]} | |
| def sanitize_map(m, companies): | |
| out = {} | |
| for k,v in m.items(): | |
| out[k] = [x for x in v if x in companies] | |
| return out | |
| def load_default_dataset(): | |
| AMCS = DEFAULT_AMCS.copy() | |
| COMPANIES = DEFAULT_COMPANIES.copy() | |
| BUY = sanitize_map(SAMPLE_BUY, COMPANIES) | |
| SELL = sanitize_map(SAMPLE_SELL, COMPANIES) | |
| CEXIT = sanitize_map(SAMPLE_COMPLETE_EXIT, COMPANIES) | |
| FBUY = sanitize_map(SAMPLE_FRESH_BUY, COMPANIES) | |
| return AMCS, COMPANIES, BUY, SELL, CEXIT, FBUY | |
| def infer_transfers(buy_map, sell_map): | |
| transfers = defaultdict(int) | |
| comp_to_sellers = defaultdict(list) | |
| comp_to_buyers = defaultdict(list) | |
| for a, comps in sell_map.items(): | |
| for c in comps: comp_to_sellers[c].append(a) | |
| for a, comps in buy_map.items(): | |
| for c in comps: comp_to_buyers[c].append(a) | |
| for c in set(list(comp_to_sellers.keys())+list(comp_to_buyers.keys())): | |
| for s in comp_to_sellers[c]: | |
| for b in comp_to_buyers[c]: | |
| transfers[(s,b)] += 1 | |
| edges = [] | |
| for (s,b),w in transfers.items(): | |
| edges.append((s,b,{"action":"transfer","weight":w})) | |
| return edges | |
| def build_graph(AMCS, COMPANIES, BUY, SELL, CEXIT, FBUY, include_transfers): | |
| G = nx.DiGraph() | |
| for a in AMCS: G.add_node(a,type="amc") | |
| for c in COMPANIES: G.add_node(c,type="company") | |
| def add(a,c,action,weight): | |
| if not(G.has_node(a) and G.has_node(c)): return | |
| if G.has_edge(a,c): | |
| G[a][c]["weight"] += weight | |
| G[a][c]["actions"].append(action) | |
| else: | |
| G.add_edge(a,c,weight=weight,actions=[action]) | |
| for a,cs in BUY.items(): [add(a,c,"buy",1) for c in cs] | |
| for a,cs in SELL.items(): [add(a,c,"sell",1) for c in cs] | |
| for a,cs in CEXIT.items():[add(a,c,"complete_exit",3) for c in cs] | |
| for a,cs in FBUY.items(): [add(a,c,"fresh_buy",3) for c in cs] | |
| if include_transfers: | |
| tr = infer_transfers(BUY,SELL) | |
| for s,b,d in tr: | |
| if G.has_edge(s,b): | |
| G[s][b]["weight"] += d["weight"] | |
| G[s][b]["actions"].append("transfer") | |
| else: | |
| G.add_edge(s,b,weight=d["weight"],actions=["transfer"]) | |
| return G | |
| def graph_to_plotly(G, | |
| node_color_amc="#0f5132", | |
| node_color_company="#ffc107", | |
| edge_color_buy="#28a745", | |
| edge_color_sell="#dc3545", | |
| edge_color_transfer="#6c757d"): | |
| pos = nx.spring_layout(G, seed=42, k=1.4) | |
| xs,ys,cols,txt,size=[],[],[],[],[] | |
| for n,d in G.nodes(data=True): | |
| x,y=pos[n] | |
| xs.append(x); ys.append(y) | |
| txt.append(n) | |
| if d["type"]=="amc": | |
| cols.append(node_color_amc); size.append(40) | |
| else: | |
| cols.append(node_color_company); size.append(60) | |
| nodes = go.Scatter( | |
| x=xs,y=ys,mode="markers+text", | |
| marker=dict(color=cols,size=size,line=dict(width=2,color="black")), | |
| text=txt,textposition="top center" | |
| ) | |
| edge_traces=[] | |
| for u,v,d in G.edges(data=True): | |
| x0,y0 = pos[u]; x1,y1=pos[v] | |
| acts = d.get("actions",[]) | |
| if "complete_exit" in acts: | |
| color=edge_color_sell; dash="solid"; w=4 | |
| elif "fresh_buy" in acts: | |
| color=edge_color_buy; dash="solid"; w=4 | |
| elif "transfer" in acts: | |
| color=edge_color_transfer; dash="dash"; w=2 | |
| elif "sell" in acts: | |
| color=edge_color_sell; dash="dot"; w=2 | |
| else: | |
| color=edge_color_buy; dash="solid"; w=2 | |
| edge_traces.append(go.Scatter( | |
| x=[x0,x1,None], y=[y0,y1,None], | |
| mode="lines", | |
| line=dict(color=color,width=w,dash=dash), | |
| hoverinfo="text", text=", ".join(acts) | |
| )) | |
| fig = go.Figure(data=edge_traces+[nodes], | |
| layout=go.Layout( | |
| width=1400,height=800, | |
| showlegend=False, | |
| xaxis=dict(visible=False), | |
| yaxis=dict(visible=False), | |
| margin=dict(t=50,l=10,r=10,b=10) | |
| ) | |
| ) | |
| return fig | |
| ####################################### | |
| # Modal-free UI | |
| ####################################### | |
| AMCS,COMPANIES,BUY,SELL,CEXIT,FBUY = load_default_dataset() | |
| G0 = build_graph(AMCS,COMPANIES,BUY,SELL,CEXIT,FBUY,True) | |
| FIG0 = graph_to_plotly(G0) | |
| deep_theme = gr.themes.Soft(primary_hue="green", secondary_hue="teal") | |
| with gr.Blocks(theme=deep_theme, css=""" | |
| /* Modal overlay */ | |
| #custom_modal_bg { | |
| display:none; | |
| position:fixed; | |
| top:0; left:0; | |
| width:100%; height:100%; | |
| background:rgba(0,0,0,0.55); | |
| z-index:9998; | |
| } | |
| /* Modal box */ | |
| #custom_modal { | |
| display:none; | |
| position:fixed; | |
| top:10%; left:50%; | |
| transform:translateX(-50%); | |
| width:420px; | |
| max-height:80%; | |
| overflow-y:auto; | |
| background:white; | |
| border-radius:12px; | |
| padding:20px; | |
| z-index:9999; | |
| box-shadow:0 0 20px rgba(0,0,0,0.4); | |
| } | |
| #settings_btn { | |
| cursor:pointer; | |
| } | |
| """) as demo: | |
| gr.Markdown("# Mutual Fund Churn Explorer") | |
| with gr.Row(): | |
| with gr.Column(scale=1, min_width=80): | |
| settings_btn = gr.Button("⚙️ Settings", elem_id="settings_btn") | |
| with gr.Column(scale=11): | |
| plot = gr.Plot(value=FIG0, label="Network Graph") | |
| # Invisible modal + background mask | |
| modal_bg = gr.HTML('<div id="custom_modal_bg"></div>') | |
| modal_html = gr.HTML('<div id="custom_modal"></div>') | |
| # All settings components (hidden; rendered inside modal via JS) | |
| with gr.Column(visible=False) as settings_contents: | |
| csv_up = gr.File(label="Upload CSV") | |
| node_col_amc = gr.ColorPicker(value="#0f5132", label="AMC Node Color") | |
| node_col_cmp = gr.ColorPicker(value="#ffc107", label="Company Node Color") | |
| edge_col_buy = gr.ColorPicker(value="#28a745", label="BUY Color") | |
| edge_col_sell = gr.ColorPicker(value="#dc3545", label="SELL Color") | |
| edge_col_trans = gr.ColorPicker(value="#6c757d", label="TRANSFER Color") | |
| include_trans = gr.Checkbox(value=True, label="Infer Transfers") | |
| update_btn = gr.Button("Update Graph") | |
| # JavaScript: show modal by copying the settings block inside popup | |
| demo.load(None, None, None, _js=""" | |
| (() => { | |
| const btn = document.querySelector('#settings_btn'); | |
| const bg = document.querySelector('#custom_modal_bg'); | |
| const mod = document.querySelector('#custom_modal'); | |
| const src = document.querySelector('[data-testid="block-settings_contents"]'); | |
| btn.onclick = () => { | |
| mod.innerHTML = src.innerHTML; // copy settings UI into modal | |
| bg.style.display = 'block'; | |
| mod.style.display = 'block'; | |
| // close when clicking outside | |
| bg.onclick = () => { | |
| mod.style.display = 'none'; | |
| bg.style.display = 'none'; | |
| }; | |
| }; | |
| })(); | |
| """) | |
| # When user presses Update Graph inside modal | |
| def update_graph(csvfile, colA, colC, buyC, sellC, transC, use_trans): | |
| AM,CP,BY,SL,CE,FB = load_default_dataset() | |
| G = build_graph(AM,CP,BY,SL,CE,FB,use_trans) | |
| fig = graph_to_plotly(G, | |
| node_color_amc=colA, | |
| node_color_company=colC, | |
| edge_color_buy=buyC, | |
| edge_color_sell=sellC, | |
| edge_color_transfer=transC | |
| ) | |
| return fig | |
| update_btn.click( | |
| fn=update_graph, | |
| inputs=[csv_up,node_col_amc,node_col_cmp,edge_col_buy,edge_col_sell,edge_col_trans,include_trans], | |
| outputs=[plot] | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue().launch() | |