singhn9 commited on
Commit
ce7273b
·
verified ·
1 Parent(s): 10676c2

Update graph.html

Browse files
Files changed (1) hide show
  1. graph.html +418 -0
graph.html CHANGED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>MF Churn Diagram</title>
6
+ <script src="https://d3js.org/d3.v7.min.js"></script>
7
+
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ background: white;
12
+ font-family: sans-serif;
13
+ }
14
+ </style>
15
+
16
+ </head>
17
+ <body>
18
+
19
+ <!-- Main container -->
20
+ <div id="arc-container" style="width:100%; height:800px;"></div>
21
+
22
+ <!-- Reset button -->
23
+ <div style="margin:12px;">
24
+ <button id="arc-reset"
25
+ style="padding:8px 14px; font-size:15px; border-radius:6px; background:#eee;">
26
+ Reset View
27
+ </button>
28
+ </div>
29
+
30
+ <!-- Legend -->
31
+ <div style="margin:12px; font-size:14px; line-height:1.5;">
32
+ <b>Legend</b><br>
33
+ <span style="color:#1a9850; font-weight:bold;">BUY</span>: green solid<br>
34
+ <span style="color:#d73027; font-weight:bold;">SELL</span>: red dotted<br>
35
+ <span style="color:#636363; font-weight:bold;">TRANSFER</span>: grey (inferred)<br>
36
+ <span style="color:#1f9e89; font-weight:bold;">LOOP</span>: teal external arc<br>
37
+ <small style="color:#666;">Click a node → show full labels outside the circle for that node & connected nodes.</small>
38
+ </div>
39
+
40
+
41
+ <script>
42
+ // =============================
43
+ // DATA injected inline
44
+ // =============================
45
+
46
+ const NODES = [
47
+ "SBI MF","HDFC Bank","ICICI Pru MF","ICICI Bank","HDFC MF","Bajaj Finance",
48
+ "Nippon India MF","Bajaj Finserv","Kotak MF","Adani Ports",
49
+ "UTI MF","Tata Motors","Axis MF","Shriram Finance","Aditya Birla SL MF","HAL",
50
+ "Mirae MF","TCS","DSP MF","AU Small Finance Bank",
51
+ "Pearl Global","Hindalco","Tata Elxsi","Cummins India","Vedanta"
52
+ ];
53
+
54
+ const NODE_TYPE = {
55
+ "SBI MF":"amc","HDFC Bank":"company","ICICI Pru MF":"amc","ICICI Bank":"company",
56
+ "HDFC MF":"amc","Bajaj Finance":"company","Nippon India MF":"amc","Bajaj Finserv":"company",
57
+ "Kotak MF":"amc","Adani Ports":"company","UTI MF":"amc","Tata Motors":"company",
58
+ "Axis MF":"amc","Shriram Finance":"company","Aditya Birla SL MF":"amc","HAL":"company",
59
+ "Mirae MF":"amc","TCS":"company","DSP MF":"amc","AU Small Finance Bank":"company",
60
+ "Pearl Global":"company","Hindalco":"company","Tata Elxsi":"company","Cummins India":"company",
61
+ "Vedanta":"company"
62
+ };
63
+
64
+ // BUY edges: (amc, company, weight)
65
+ const BUYS = [
66
+ ["SBI MF","Bajaj Finance",1],["SBI MF","AU Small Finance Bank",1],
67
+ ["ICICI Pru MF","HDFC Bank",1],["HDFC MF","Tata Elxsi",3],["HDFC MF","TCS",1],
68
+ ["Nippon India MF","Hindalco",1],["Kotak MF","Bajaj Finance",1],
69
+ ["UTI MF","Adani Ports",3],["UTI MF","Shriram Finance",1],
70
+ ["Axis MF","Tata Motors",1],["Axis MF","Shriram Finance",1],
71
+ ["Aditya Birla SL MF","AU Small Finance Bank",1],
72
+ ["Mirae MF","Bajaj Finance",1],["Mirae MF","HAL",3],
73
+ ["DSP MF","Tata Motors",1],["DSP MF","Bajaj Finserv",1]
74
+ ];
75
+
76
+ // SELL edges reversed to: (company, amc, weight)
77
+ const SELLS = [
78
+ ["Tata Motors","SBI MF",1],
79
+ ["Bajaj Finance","ICICI Pru MF",1],["Adani Ports","ICICI Pru MF",1],
80
+ ["HDFC Bank","HDFC MF",1],["Hindalco","Nippon India MF",1],
81
+ ["AU Small Finance Bank","Kotak MF",1],["Hindalco","UTI MF",1],
82
+ ["TCS","UTI MF",1],["TCS","Axis MF",1],["Adani Ports","Aditya Birla SL MF",1],
83
+ ["TCS","Mirae MF",1],["HAL","DSP MF",1],["Shriram Finance","DSP MF",3]
84
+ ];
85
+
86
+ // Transfers: (amc → amc, weight)
87
+ const TRANSFERS = [
88
+ ["Kotak MF","SBI MF",1],
89
+ ["Axis MF","SBI MF",2],
90
+ ["DSP MF","Axis MF",1],
91
+ ["DSP MF","UTI MF",1],
92
+ ["Axis MF","HDFC MF",1],
93
+ ["Nippon India MF","UTI MF",1],
94
+ ["DSP MF","ICICI Pru MF",1],
95
+ ["UTI MF","Nippon India MF",1],
96
+ ["ICICI Pru MF","Axis MF",1],
97
+ ["Mirae MF","UTI MF",1],
98
+ ["DSP MF","Aditya Birla SL MF",1],
99
+ ["HDFC MF","UTI MF",1],
100
+ ["ICICI Pru MF","UTI MF",1],
101
+ ["IIT??","??",1] // Safety note: ignore if malformed
102
+ ].filter(e => NODE_TYPE[e[0]] && NODE_TYPE[e[1]]); // Remove junk if any
103
+
104
+ // Loop edges: (amc, company, amc)
105
+ const LOOPS = [
106
+ ["SBI MF","Bajaj Finance","ICICI Pru MF"],
107
+ ["Kotak MF","Bajaj Finance","ICICI Pru MF"],
108
+ ["Mirae MF","Bajaj Finance","ICICI Pru MF"],
109
+ ["UTI MF","Adani Ports","ICICI Pru MF"],
110
+ ["Aditya Birla SL MF","Adani Ports","ICICI Pru MF"],
111
+ ["HDFC MF","HDFC Bank","ICICI Pru MF"],
112
+ ["Nippon India MF","Hindalco","UTI MF"],
113
+ ["UTI MF","Hindalco","Nippon India MF"],
114
+ ["UTI MF","TCS","HDFC MF"],
115
+ ["Axis MF","TCS","HDFC MF"],
116
+ ["Mirae MF","TCS","HDFC MF"],
117
+ ["DSP MF","HAL","Mirae MF"],
118
+ ["Axis MF","Shriram Finance","DSP MF"],
119
+ ["UTI MF","Shriram Finance","DSP MF"]
120
+ ];
121
+
122
+ const SHORT_LABEL = {
123
+ "SBI MF":"SBI","HDFC Bank":"HDFCB","ICICI Pru MF":"ICICI","ICICI Bank":"ICICB",
124
+ "HDFC MF":"HDFC","Bajaj Finance":"BajFin","Nippon India MF":"NIPP","Bajaj Finserv":"BajFsv",
125
+ "Kotak MF":"KOT","Adani Ports":"AdPorts","UTI MF":"UTI","Tata Motors":"TataM",
126
+ "Axis MF":"AXIS","Shriram Finance":"ShrFin","Aditya Birla SL MF":"ABSL","HAL":"HAL",
127
+ "Mirae MF":"MIR","TCS":"TCS","DSP MF":"DSP","AU Small Finance Bank":"AUSFB",
128
+ "Pearl Global":"PearlG","Hindalco":"Hind","Tata Elxsi":"Elxsi","Cummins India":"Cumm",
129
+ "Vedanta":"Ved"
130
+ };
131
+
132
+ // full labels
133
+ const FULL_LABEL = {};
134
+ for (const k in SHORT_LABEL) FULL_LABEL[k] = k;
135
+
136
+
137
+ // =====================================================
138
+ // Draw the radial arc diagram
139
+ // =====================================================
140
+
141
+ function drawArc() {
142
+
143
+ const container = document.getElementById("arc-container");
144
+ container.innerHTML = "";
145
+
146
+ const w = Math.min(1100, container.clientWidth || 1000);
147
+ const h = Math.max(650, Math.floor(w * 0.75));
148
+
149
+ const svg = d3.select(container).append("svg")
150
+ .attr("width", "100%")
151
+ .attr("height", h)
152
+ .attr("viewBox", [-w/2, -h/2, w, h].join(" "));
153
+
154
+ const radius = Math.min(w, h) * 0.36;
155
+ const n = NODES.length;
156
+
157
+ function angle(i){
158
+ return (i / n) * 2 * Math.PI;
159
+ }
160
+
161
+ const pos = NODES.map((name, i) => {
162
+ const ang = angle(i) - Math.PI/2;
163
+ return {
164
+ name,
165
+ angle: ang,
166
+ x: Math.cos(ang) * radius,
167
+ y: Math.sin(ang) * radius
168
+ };
169
+ });
170
+
171
+ const index = {};
172
+ NODES.forEach((nm, i) => index[nm] = i);
173
+
174
+
175
+ // Node group
176
+ const nodeG = svg.append("g")
177
+ .selectAll("g")
178
+ .data(pos)
179
+ .enter()
180
+ .append("g")
181
+ .attr("transform", d => `translate(${d.x},${d.y})`);
182
+
183
+ nodeG.append("circle")
184
+ .attr("r", 17)
185
+ .style("fill", d => NODE_TYPE[d.name] === "amc" ? "#003f5c" : "#f59e0b")
186
+ .style("stroke", "#111")
187
+ .style("stroke-width", 1)
188
+ .style("cursor", "pointer");
189
+
190
+ nodeG.append("text")
191
+ .attr("dy", "0.35em")
192
+ .style("font-size", "10px")
193
+ .style("fill", "#fff")
194
+ .style("text-anchor", "middle")
195
+ .style("pointer-events", "none")
196
+ .text(d => SHORT_LABEL[d.name]);
197
+
198
+
199
+ // Function for arc path
200
+ function arcPath(x0, y0, x1, y1, above) {
201
+ const mx = (x0 + x1) / 2;
202
+ const my = (y0 + y1) / 2;
203
+ const len = Math.sqrt(mx*mx + my*my) || 1;
204
+ const ux = mx / len, uy = my / len;
205
+ const offset = (above ? -1 : 1) * Math.max(40, radius * 0.9);
206
+ const cx = mx + ux * offset;
207
+ const cy = my + uy * offset;
208
+ return `M ${x0} ${y0} Q ${cx} ${cy} ${x1} ${y1}`;
209
+ }
210
+
211
+ const allWeights = [
212
+ ...BUYS.map(d => d[2]),
213
+ ...SELLS.map(d => d[2]),
214
+ ...TRANSFERS.map(d => d[2])
215
+ ];
216
+
217
+ const sw = d3.scaleLinear()
218
+ .domain([1, Math.max(...allWeights, 1)])
219
+ .range([1.2, 6]);
220
+
221
+
222
+ // BUY arcs
223
+ const buyG = svg.append("g");
224
+ BUYS.forEach(([a, c, w])=>{
225
+ if (!(a in index) || !(c in index)) return;
226
+ const s = pos[index[a]], t = pos[index[c]];
227
+ buyG.append("path")
228
+ .attr("d", arcPath(s.x, s.y, t.x, t.y, true))
229
+ .attr("stroke", "#1a9850")
230
+ .attr("fill", "none")
231
+ .attr("stroke-width", sw(w))
232
+ .attr("data-src", a)
233
+ .attr("data-tgt", c)
234
+ .attr("opacity", 0.9);
235
+ });
236
+
237
+
238
+ // SELL arcs
239
+ const sellG = svg.append("g");
240
+ SELLS.forEach(([c, a, w])=>{
241
+ if (!(a in index) || !(c in index)) return;
242
+ const s = pos[index[c]], t = pos[index[a]];
243
+ sellG.append("path")
244
+ .attr("d", arcPath(s.x, s.y, t.x, t.y, false))
245
+ .attr("stroke", "#d73027")
246
+ .attr("stroke-width", sw(w))
247
+ .attr("stroke-dasharray", "4,3")
248
+ .attr("fill", "none")
249
+ .attr("data-src", c)
250
+ .attr("data-tgt", a)
251
+ .attr("opacity", 0.85);
252
+ });
253
+
254
+
255
+ // TRANSFERS (grey)
256
+ const trG = svg.append("g");
257
+ TRANSFERS.forEach(([s, b, w]) => {
258
+ if (!(s in index) || !(b in index)) return;
259
+ const sp = pos[index[s]], tp = pos[index[b]];
260
+ const mx = (sp.x + tp.x)/2, my = (sp.y + tp.y)/2;
261
+ trG.append("path")
262
+ .attr("d", `M ${sp.x} ${sp.y} Q ${mx*0.3} ${my*0.3} ${tp.x} ${tp.y}`)
263
+ .attr("stroke", "#636363")
264
+ .attr("fill", "none")
265
+ .attr("stroke-width", sw(w))
266
+ .attr("opacity", 0.7)
267
+ .attr("data-src", s)
268
+ .attr("data-tgt", b);
269
+ });
270
+
271
+
272
+ // LOOPS (teal)
273
+ const loopG = svg.append("g");
274
+ LOOPS.forEach(([a,c,b])=>{
275
+ if (!(a in index) || !(b in index)) return;
276
+ const s = pos[index[a]], t = pos[index[b]];
277
+ const mx = (s.x + t.x)/2, my = (s.y + t.y)/2;
278
+ const len = Math.sqrt((s.x - t.x)**2 + (s.y - t.y)**2);
279
+ const outward = Math.max(50, radius * 0.25 + len * 0.12);
280
+ const dx = mx, dy = my;
281
+ const dl = Math.sqrt(dx*dx + dy*dy) || 1;
282
+ const ux = dx/dl, uy = dy/dl;
283
+ const cx = mx + ux*outward, cy = my + uy*outward;
284
+ loopG.append("path")
285
+ .attr("d", `M ${s.x} ${s.y} Q ${cx} ${cy} ${t.x} ${t.y}`)
286
+ .attr("stroke", "#1f9e89")
287
+ .attr("fill", "none")
288
+ .attr("stroke-width", 3)
289
+ .attr("opacity", 0.95);
290
+ });
291
+
292
+
293
+ // Outside label layer
294
+ const outsideLayer = svg.append("g");
295
+
296
+
297
+ // Expand labels on click
298
+ function expandLabels(nodeName){
299
+
300
+ // reset
301
+ outsideLayer.selectAll("*").remove();
302
+
303
+ // Dim all nodes
304
+ nodeG.selectAll("circle").style("opacity", d => d.name === nodeName ? 1 : 0.2);
305
+ nodeG.selectAll("text").style("opacity", 0);
306
+
307
+ const connected = new Set([nodeName]);
308
+
309
+ buyG.selectAll("path").each(function(){
310
+ const s = this.getAttribute("data-src");
311
+ const t = this.getAttribute("data-tgt");
312
+ if (s === nodeName || t === nodeName){
313
+ connected.add(s);
314
+ connected.add(t);
315
+ }
316
+ });
317
+
318
+ sellG.selectAll("path").each(function(){
319
+ const s = this.getAttribute("data-src");
320
+ const t = this.getAttribute("data-tgt");
321
+ if (s === nodeName || t === nodeName){
322
+ connected.add(s);
323
+ connected.add(t);
324
+ }
325
+ });
326
+
327
+ trG.selectAll("path").each(function(){
328
+ const s = this.getAttribute("data-src");
329
+ const t = this.getAttribute("data-tgt");
330
+ if (s === nodeName || t === nodeName){
331
+ connected.add(s);
332
+ connected.add(t);
333
+ }
334
+ });
335
+
336
+ // Show outward labels
337
+ connected.forEach(nm => {
338
+ const p = pos[index[nm]];
339
+ const offset = radius + 30;
340
+ const x = p.x + Math.cos(p.angle)*offset;
341
+ const y = p.y + Math.sin(p.angle)*offset;
342
+
343
+ const deg = p.angle * 180 / Math.PI;
344
+ const anchor = (deg > -90 && deg < 90) ? "start" : "end";
345
+
346
+ outsideLayer.append("text")
347
+ .attr("x", x)
348
+ .attr("y", y)
349
+ .attr("dy", "0.35em")
350
+ .text(FULL_LABEL[nm] || nm)
351
+ .style("font-size","12px")
352
+ .style("font-weight","600")
353
+ .style("fill", "#0c0c10")
354
+ .style("text-anchor", anchor);
355
+ });
356
+
357
+
358
+ // Dim unrelated arcs
359
+ buyG.selectAll("path")
360
+ .style("opacity", function(){
361
+ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt");
362
+ return (s===nodeName || t===nodeName) ? 0.95 : 0.05;
363
+ });
364
+
365
+ sellG.selectAll("path")
366
+ .style("opacity", function(){
367
+ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt");
368
+ return (s===nodeName || t===nodeName) ? 0.95 : 0.05;
369
+ });
370
+
371
+ trG.selectAll("path")
372
+ .style("opacity", function(){
373
+ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt");
374
+ return (s===nodeName || t===nodeName) ? 0.9 : 0.05;
375
+ });
376
+
377
+ loopG.selectAll("path")
378
+ .style("opacity", 0.95);
379
+ }
380
+
381
+
382
+ // Reset function
383
+ function resetView(){
384
+ outsideLayer.selectAll("*").remove();
385
+
386
+ nodeG.selectAll("circle").style("opacity", 1);
387
+ nodeG.selectAll("text").style("opacity", 1);
388
+
389
+ buyG.selectAll("path").style("opacity", 0.9);
390
+ sellG.selectAll("path").style("opacity", 0.85);
391
+ trG.selectAll("path").style("opacity", 0.7);
392
+ loopG.selectAll("path").style("opacity", 0.95);
393
+ }
394
+
395
+ nodeG.selectAll("circle")
396
+ .on("click", (event, d) => {
397
+ event.stopPropagation();
398
+ expandLabels(d.name);
399
+ });
400
+
401
+ nodeG.selectAll("text")
402
+ .on("click", (event, d) => {
403
+ event.stopPropagation();
404
+ expandLabels(d.name);
405
+ });
406
+
407
+ document.getElementById("arc-reset").onclick = resetView;
408
+
409
+ svg.on("click", () => resetView());
410
+ }
411
+
412
+ drawArc();
413
+ window.addEventListener("resize", drawArc);
414
+
415
+ </script>
416
+
417
+ </body>
418
+ </html>