singhn9 commited on
Commit
b580f79
·
verified ·
1 Parent(s): 5bac8a4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -63
app.py CHANGED
@@ -85,7 +85,7 @@ def infer_amc_transfers(buy_map, sell_map):
85
  TRANSFER_COUNTS = infer_amc_transfers(BUY_MAP, SELL_MAP)
86
 
87
  # ---------------------------
88
- # Mixed ordering to reduce crossings
89
  # ---------------------------
90
  def build_mixed_ordering(amcs, companies):
91
  mixed = []
@@ -109,46 +109,55 @@ def build_flows():
109
  for c in comps:
110
  w = 3 if (amc in FRESH_BUY and c in FRESH_BUY.get(amc, [])) else 1
111
  buys.append((amc, c, w))
 
112
  sells = []
113
  for amc, comps in SELL_MAP.items():
114
  for c in comps:
115
  w = 3 if (amc in COMPLETE_EXIT and c in COMPLETE_EXIT.get(amc, [])) else 1
116
  sells.append((c, amc, w))
 
117
  transfers = []
118
  for (s,b), w in TRANSFER_COUNTS.items():
119
  transfers.append((s, b, w))
 
120
  loops = []
121
- # loops: a -> c -> b (buy from a into c, sell from b of c)
122
  for a,c,w1 in buys:
123
  for c2,b,w2 in sells:
124
  if c == c2:
125
  loops.append((a, c, b))
126
- # dedupe
127
  loops = list({(a,c,b) for (a,c,b) in loops})
128
  return buys, sells, transfers, loops
129
 
130
  BUYS, SELLS, TRANSFERS, LOOPS = build_flows()
131
 
132
  # ---------------------------
133
- # Inspector summaries (return Plotly figures)
134
  # ---------------------------
135
  def company_trade_summary(company):
136
  buyers = [a for a, cs in BUY_MAP.items() if company in cs]
137
  sellers = [a for a, cs in SELL_MAP.items() if company in cs]
138
  fresh = [a for a, cs in FRESH_BUY.items() if company in cs]
139
  exits = [a for a, cs in COMPLETE_EXIT.items() if company in cs]
 
140
  df = pd.DataFrame({
141
- "Role": (["Buyer"] * len(buyers)) + (["Seller"] * len(sellers)) +
142
- (["Fresh buy"] * len(fresh)) + (["Complete exit"] * len(exits)),
 
 
 
143
  "AMC": buyers + sellers + fresh + exits
144
  })
 
145
  if df.empty:
146
  return go.Figure(), pd.DataFrame([], columns=["Role", "AMC"])
 
147
  counts = df.groupby("Role").size().reset_index(name="Count")
148
- fig = go.Figure(data=[go.Bar(x=counts["Role"].tolist(), y=counts["Count"].tolist())])
149
  fig.update_layout(title=f"Trades for {company}", margin=dict(l=20,r=20,t=40,b=20))
150
  return fig, df
151
 
 
152
  def amc_transfer_summary(amc):
153
  sold = SELL_MAP.get(amc, [])
154
  transfers = []
@@ -156,18 +165,21 @@ def amc_transfer_summary(amc):
156
  buyers = [a for a, cs in BUY_MAP.items() if s in cs]
157
  for b in buyers:
158
  transfers.append({"security": s, "buyer_amc": b})
 
159
  df = pd.DataFrame(transfers)
160
  if df.empty:
161
  return go.Figure(), pd.DataFrame([], columns=["security", "buyer_amc"])
 
162
  counts = df["buyer_amc"].value_counts().reset_index()
163
  counts.columns = ["Buyer AMC", "Count"]
164
- fig = go.Figure(data=[go.Bar(x=counts["Buyer AMC"].tolist(), y=counts["Count"].tolist())])
165
  fig.update_layout(title=f"Inferred transfers from {amc}", margin=dict(l=20,r=20,t=40,b=20))
166
  return fig, df
167
 
168
- # ---------------------------
169
- # HTML template (JS inserted safely via replace)
170
- # ---------------------------
 
171
  JS_TEMPLATE = """
172
  <div id="arc-container" style="width:100%; height:720px;"></div>
173
  <div style="margin-top:8px;">
@@ -179,12 +191,12 @@ JS_TEMPLATE = """
179
  <span style="display:inline-block;width:12px;height:8px;background:#2e8540;margin-right:6px;"></span> BUY (green solid)<br/>
180
  <span style="display:inline-block;width:12px;height:8px;background:#c0392b;margin-right:6px;border-bottom:3px dotted #c0392b;"></span> SELL (red dotted)<br/>
181
  <span style="display:inline-block;width:12px;height:8px;background:#7d7d7d;margin-right:6px;"></span> TRANSFER (grey, inferred)<br/>
182
- <span style="display:inline-block;width:12px;height:8px;background:#227a6d;margin-right:6px;"></span> LOOP (external arc)<br/>
183
  <div style="margin-top:6px;color:#666;font-size:12px;">Note: Transfers are inferred by matching sells and buys across AMCs. Thickness shows relative weight.</div>
184
  </div>
185
 
186
- <div id="info-box" style="margin-top:12px; padding:10px;
187
- border:1px solid #ddd; border-radius:8px; font-family:sans-serif;
188
  font-size:13px; background:#fbfbfb;">
189
  <b>Click a node</b> to view details here.
190
  </div>
@@ -253,7 +265,6 @@ function draw() {
253
  // --------------------------------------------------------------
254
  // ARC DRAWING (middle layers)
255
  // --------------------------------------------------------------
256
-
257
  function bezierPath(x0,y0,x1,y1,above=true){
258
  const mx = (x0+x1)/2;
259
  const my = (y0+y1)/2;
@@ -322,6 +333,8 @@ function draw() {
322
  .attr("data-tgt", tname);
323
  });
324
 
 
 
325
  const loopGroup = svg.append("g").attr("class", "loops");
326
  LOOPS.forEach(lp => {
327
  const a = lp[0], c = lp[1], b = lp[2];
@@ -329,19 +342,49 @@ function draw() {
329
  const sa = nodePos[nameToIndex[a]];
330
  const sb = nodePos[nameToIndex[b]];
331
  const mx = (sa.x+sb.x)/2, my = (sa.y+sb.y)/2;
332
- const len = Math.sqrt(mx*mx+my*my)||1;
333
- const offset = Math.max(40, radius*0.28 + len*0.12);
334
- const ux = mx/len, uy = my/len;
 
 
 
 
335
  loopGroup.append("path")
336
  .attr("d", path)
337
  .attr("fill", "none")
338
- .attr("stroke", "#9b59b6") // bright violet
339
- .attr("stroke-width", 4.5) // thicker so stands out
340
  .attr("opacity", 1)
341
- .attr("stroke-linecap","round")
342
- .attr("stroke-linejoin","round");
343
  });
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  // --------------------------------------------------------------
346
  // 2. LABELS AT THE TOP (always visible)
347
  // --------------------------------------------------------------
@@ -382,34 +425,35 @@ function draw() {
382
  });
383
  }
384
 
385
- function setOpacityFor(nodeName) {
386
  const connected = getConnections(nodeName);
387
-
388
- // highlight circles
389
  nodeCircleGroup.selectAll("circle")
390
- .style("opacity", d =>
391
- d.name === nodeName || connected.has(d.name) ? 1 : 0.18
392
- );
393
-
394
- // highlight labels
395
- labelGroup.style("opacity", d =>
396
- d.name === nodeName || connected.has(d.name) ? 1 : 0.28
397
- );
398
-
399
- // highlight arcs
400
- function isConn(path){
401
- const src = path.getAttribute("data-src");
402
- const tgt = path.getAttribute("data-tgt");
403
- return src === nodeName || tgt === nodeName ||
404
- connected.has(src) || connected.has(tgt);
405
  }
406
-
407
  buyGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
408
  sellGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
409
  transferGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
410
- loopGroup.selectAll("path").style("opacity",0.95); // loops unchanged
411
- }
412
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
  function resetOpacity(){
415
  nodeCircleGroup.selectAll("circle").style("opacity",1);
@@ -417,27 +461,25 @@ function draw() {
417
  buyGroup.selectAll("path").style("opacity",0.92);
418
  sellGroup.selectAll("path").style("opacity",0.86);
419
  transferGroup.selectAll("path").style("opacity",0.7);
420
- loopGroup.selectAll("path").style("opacity",0.95);
 
 
421
  updateLabels(null, new Set());
422
  document.getElementById("info-box").innerHTML =
423
  "<b>Click a node</b> to view details here.";
424
  }
425
 
426
- function showInfo(nodeName) {
427
  const buys = BUYS.filter(x => x[0] === nodeName || x[1] === nodeName)
428
  .map(x => x[0] === nodeName ? x[1] : x[0]);
429
-
430
  const sells = SELLS.filter(x => x[0] === nodeName || x[1] === nodeName)
431
  .map(x => x[0] === nodeName ? x[1] : x[0]);
432
-
433
  const transfers = TRANSFERS.filter(x => x[0] === nodeName || x[1] === nodeName)
434
  .map(x => x[0] === nodeName ? x[1] : x[0]);
435
-
436
  const loops = LOOPS.filter(x => x[0] === nodeName || x[2] === nodeName)
437
  .map(x => x[0] === nodeName ? x[2] : x[0]);
438
-
439
  const box = document.getElementById("info-box");
440
-
441
  box.innerHTML = `
442
  <div style="font-size:14px;"><b>${nodeName}</b></div>
443
  <div style="margin-top:8px; font-size:13px;">
@@ -449,26 +491,26 @@ function draw() {
449
  `;
450
  }
451
 
452
-
453
  function selectNode(d){
454
  const name = d.name;
455
  setOpacityFor(name);
456
  showInfo(name);
457
  const connected = getConnections(name);
458
  updateLabels(name, connected);
 
459
  }
460
 
461
  nodeCircleGroup.on("click", function(e,d){
462
  selectNode(d);
463
- e.stopPropagation();
464
  });
465
  labelGroup.on("click", function(e,d){
466
  selectNode(d);
467
- e.stopPropagation();
468
  });
469
 
470
  document.getElementById("arc-reset").onclick = resetOpacity;
471
- svg.on("click", function(evt){ if(evt.target.tagName==="svg") resetOpacity(); });
472
 
473
  }
474
 
@@ -477,9 +519,7 @@ window.addEventListener("resize", draw);
477
  </script>
478
  """
479
 
480
-
481
  def make_arc_html(nodes, node_type, buys, sells, transfers, loops):
482
- # prepare JSON strings
483
  nodes_json = json.dumps(nodes)
484
  node_type_json = json.dumps(node_type)
485
  buys_json = json.dumps(buys)
@@ -496,12 +536,10 @@ def make_arc_html(nodes, node_type, buys, sells, transfers, loops):
496
 
497
  initial_html = make_arc_html(NODES, NODE_TYPE, BUYS, SELLS, TRANSFERS, LOOPS)
498
 
499
- # ---------------------------
500
- # Gradio UI
501
- # ---------------------------
502
- # ---------------------------
503
  # Gradio UI
504
- # ---------------------------
 
505
  responsive_css = """
506
  #arc-container { padding:0; margin:0; }
507
  svg { font-family: sans-serif; }
@@ -512,11 +550,13 @@ with gr.Blocks(css=responsive_css, title="MF Churn — Semi-layer Arc Diagram (L
512
  gr.HTML(initial_html)
513
 
514
  gr.Markdown("### Inspect Company / AMC")
515
-
 
516
  select_company = gr.Dropdown(choices=COMPANIES, label="Select company")
517
  company_plot = gr.Plot()
518
  company_table = gr.DataFrame()
519
-
 
520
  select_amc = gr.Dropdown(choices=AMCS, label="Select AMC")
521
  amc_plot = gr.Plot()
522
  amc_table = gr.DataFrame()
 
85
  TRANSFER_COUNTS = infer_amc_transfers(BUY_MAP, SELL_MAP)
86
 
87
  # ---------------------------
88
+ # Mixed ordering
89
  # ---------------------------
90
  def build_mixed_ordering(amcs, companies):
91
  mixed = []
 
109
  for c in comps:
110
  w = 3 if (amc in FRESH_BUY and c in FRESH_BUY.get(amc, [])) else 1
111
  buys.append((amc, c, w))
112
+
113
  sells = []
114
  for amc, comps in SELL_MAP.items():
115
  for c in comps:
116
  w = 3 if (amc in COMPLETE_EXIT and c in COMPLETE_EXIT.get(amc, [])) else 1
117
  sells.append((c, amc, w))
118
+
119
  transfers = []
120
  for (s,b), w in TRANSFER_COUNTS.items():
121
  transfers.append((s, b, w))
122
+
123
  loops = []
 
124
  for a,c,w1 in buys:
125
  for c2,b,w2 in sells:
126
  if c == c2:
127
  loops.append((a, c, b))
128
+
129
  loops = list({(a,c,b) for (a,c,b) in loops})
130
  return buys, sells, transfers, loops
131
 
132
  BUYS, SELLS, TRANSFERS, LOOPS = build_flows()
133
 
134
  # ---------------------------
135
+ # Inspector summaries
136
  # ---------------------------
137
  def company_trade_summary(company):
138
  buyers = [a for a, cs in BUY_MAP.items() if company in cs]
139
  sellers = [a for a, cs in SELL_MAP.items() if company in cs]
140
  fresh = [a for a, cs in FRESH_BUY.items() if company in cs]
141
  exits = [a for a, cs in COMPLETE_EXIT.items() if company in cs]
142
+
143
  df = pd.DataFrame({
144
+ "Role":
145
+ (["Buyer"] * len(buyers))
146
+ + (["Seller"] * len(sellers))
147
+ + (["Fresh buy"] * len(fresh))
148
+ + (["Complete exit"] * len(exits)),
149
  "AMC": buyers + sellers + fresh + exits
150
  })
151
+
152
  if df.empty:
153
  return go.Figure(), pd.DataFrame([], columns=["Role", "AMC"])
154
+
155
  counts = df.groupby("Role").size().reset_index(name="Count")
156
+ fig = go.Figure(data=[go.Bar(x=counts["Role"], y=counts["Count"])])
157
  fig.update_layout(title=f"Trades for {company}", margin=dict(l=20,r=20,t=40,b=20))
158
  return fig, df
159
 
160
+
161
  def amc_transfer_summary(amc):
162
  sold = SELL_MAP.get(amc, [])
163
  transfers = []
 
165
  buyers = [a for a, cs in BUY_MAP.items() if s in cs]
166
  for b in buyers:
167
  transfers.append({"security": s, "buyer_amc": b})
168
+
169
  df = pd.DataFrame(transfers)
170
  if df.empty:
171
  return go.Figure(), pd.DataFrame([], columns=["security", "buyer_amc"])
172
+
173
  counts = df["buyer_amc"].value_counts().reset_index()
174
  counts.columns = ["Buyer AMC", "Count"]
175
+ fig = go.Figure(data=[go.Bar(x=counts["Buyer AMC"], y=counts["Count"])])
176
  fig.update_layout(title=f"Inferred transfers from {amc}", margin=dict(l=20,r=20,t=40,b=20))
177
  return fig, df
178
 
179
+
180
+ # ---------------------------------------------------------------------
181
+ # JS_TEMPLATE - single triple-quoted string (complete HTML + JS)
182
+ # ---------------------------------------------------------------------
183
  JS_TEMPLATE = """
184
  <div id="arc-container" style="width:100%; height:720px;"></div>
185
  <div style="margin-top:8px;">
 
191
  <span style="display:inline-block;width:12px;height:8px;background:#2e8540;margin-right:6px;"></span> BUY (green solid)<br/>
192
  <span style="display:inline-block;width:12px;height:8px;background:#c0392b;margin-right:6px;border-bottom:3px dotted #c0392b;"></span> SELL (red dotted)<br/>
193
  <span style="display:inline-block;width:12px;height:8px;background:#7d7d7d;margin-right:6px;"></span> TRANSFER (grey, inferred)<br/>
194
+ <span style="display:inline-block;width:12px;height:8px;background:#9b59b6;margin-right:6px;"></span> LOOP (violet external arc)<br/>
195
  <div style="margin-top:6px;color:#666;font-size:12px;">Note: Transfers are inferred by matching sells and buys across AMCs. Thickness shows relative weight.</div>
196
  </div>
197
 
198
+ <div id="info-box" style="margin-top:12px; padding:10px;
199
+ border:1px solid #ddd; border-radius:8px; font-family:sans-serif;
200
  font-size:13px; background:#fbfbfb;">
201
  <b>Click a node</b> to view details here.
202
  </div>
 
265
  // --------------------------------------------------------------
266
  // ARC DRAWING (middle layers)
267
  // --------------------------------------------------------------
 
268
  function bezierPath(x0,y0,x1,y1,above=true){
269
  const mx = (x0+x1)/2;
270
  const my = (y0+y1)/2;
 
333
  .attr("data-tgt", tname);
334
  });
335
 
336
+
337
+ // original loop arcs (keep logic but style to violet and slightly thicker)
338
  const loopGroup = svg.append("g").attr("class", "loops");
339
  LOOPS.forEach(lp => {
340
  const a = lp[0], c = lp[1], b = lp[2];
 
342
  const sa = nodePos[nameToIndex[a]];
343
  const sb = nodePos[nameToIndex[b]];
344
  const mx = (sa.x+sb.x)/2, my = (sa.y+sb.y)/2;
345
+ const len = Math.sqrt((sa.x - sb.x)*(sa.x - sb.x) + (sa.y - sb.y)*(sa.y - sb.y));
346
+ const outward = Math.max(40, radius*0.28 + len * 0.12);
347
+ const nlen = Math.sqrt(mx*mx + my*my) || 1;
348
+ const ux = mx / nlen, uy = my / nlen;
349
+ const cx = mx + ux * outward;
350
+ const cy = my + uy * outward;
351
+ const path = `M ${sa.x} ${sa.y} Q ${cx} ${cy} ${sb.x} ${sb.y}`;
352
  loopGroup.append("path")
353
  .attr("d", path)
354
  .attr("fill", "none")
355
+ .attr("stroke", "#9b59b6") // violet to stand out
356
+ .attr("stroke-width", 4.2)
357
  .attr("opacity", 1)
358
+ .attr("stroke-linecap", "round")
359
+ .attr("stroke-linejoin", "round");
360
  });
361
 
362
+ // --------------------------------------------------------------
363
+ // Overlay group for dynamic highlighted loop paths (drawn on click)
364
+ // --------------------------------------------------------------
365
+ const loopPathGroup = svg.append("g").attr("class", "loop-path-highlight");
366
+
367
+ function drawLoopPathsFor(nodeName) {
368
+ loopPathGroup.selectAll("*").remove();
369
+ LOOPS.forEach(lp => {
370
+ const amcA = lp[0], company = lp[1], amcB = lp[2];
371
+ if (!(amcA in nameToIndex) || !(company in nameToIndex) || !(amcB in nameToIndex)) return;
372
+ if (nodeName !== amcA && nodeName !== company && nodeName !== amcB) return;
373
+ const pA = nodePos[nameToIndex[amcA]];
374
+ const pC = nodePos[nameToIndex[company]];
375
+ const pB = nodePos[nameToIndex[amcB]];
376
+ // draw two-segment polyline that follows straight segments A->C and C->B
377
+ loopPathGroup.append("path")
378
+ .attr("d", `M${pA.x},${pA.y} L${pC.x},${pC.y} L${pB.x},${pB.y}`)
379
+ .attr("fill", "none")
380
+ .attr("stroke", "#8e44ad") // slightly different violet for highlight
381
+ .attr("stroke-width", 4.5)
382
+ .attr("opacity", 0.98)
383
+ .attr("stroke-linecap", "round")
384
+ .attr("stroke-linejoin", "round");
385
+ });
386
+ }
387
+
388
  // --------------------------------------------------------------
389
  // 2. LABELS AT THE TOP (always visible)
390
  // --------------------------------------------------------------
 
425
  });
426
  }
427
 
428
+ function setOpacityFor(nodeName){
429
  const connected = getConnections(nodeName);
430
+
 
431
  nodeCircleGroup.selectAll("circle")
432
+ .style("opacity", d => (d.name===nodeName || connected.has(d.name)) ? 1 : 0.18);
433
+
434
+ labelGroup.style("opacity", d => (d.name===nodeName || connected.has(d.name)) ? 1 : 0.28);
435
+
436
+ function isConn(p){
437
+ const src = p.getAttribute("data-src");
438
+ const tgt = p.getAttribute("data-tgt");
439
+ return src===nodeName || tgt===nodeName || connected.has(src) || connected.has(tgt);
 
 
 
 
 
 
 
440
  }
441
+
442
  buyGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
443
  sellGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
444
  transferGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
 
 
445
 
446
+ // keep loopGroup visible but slightly dim non-relevant ones
447
+ loopGroup.selectAll("path").style("opacity", function(){
448
+ // loops don't have data-src/data-tgt; approximate by checking endpoints
449
+ try {
450
+ const dstr = this.getAttribute("d") || "";
451
+ // simple heuristic: if path contains nodeName coordinate string, highlight it
452
+ // (we'll just keep them at 0.9 for readability)
453
+ return 0.9;
454
+ } catch(e){ return 0.9; }
455
+ });
456
+ }
457
 
458
  function resetOpacity(){
459
  nodeCircleGroup.selectAll("circle").style("opacity",1);
 
461
  buyGroup.selectAll("path").style("opacity",0.92);
462
  sellGroup.selectAll("path").style("opacity",0.86);
463
  transferGroup.selectAll("path").style("opacity",0.7);
464
+ loopGroup.selectAll("path").style("opacity",1);
465
+
466
+ loopPathGroup.selectAll("*").remove(); // clear highlight overlay
467
  updateLabels(null, new Set());
468
  document.getElementById("info-box").innerHTML =
469
  "<b>Click a node</b> to view details here.";
470
  }
471
 
472
+ function showInfo(nodeName){
473
  const buys = BUYS.filter(x => x[0] === nodeName || x[1] === nodeName)
474
  .map(x => x[0] === nodeName ? x[1] : x[0]);
 
475
  const sells = SELLS.filter(x => x[0] === nodeName || x[1] === nodeName)
476
  .map(x => x[0] === nodeName ? x[1] : x[0]);
 
477
  const transfers = TRANSFERS.filter(x => x[0] === nodeName || x[1] === nodeName)
478
  .map(x => x[0] === nodeName ? x[1] : x[0]);
 
479
  const loops = LOOPS.filter(x => x[0] === nodeName || x[2] === nodeName)
480
  .map(x => x[0] === nodeName ? x[2] : x[0]);
481
+
482
  const box = document.getElementById("info-box");
 
483
  box.innerHTML = `
484
  <div style="font-size:14px;"><b>${nodeName}</b></div>
485
  <div style="margin-top:8px; font-size:13px;">
 
491
  `;
492
  }
493
 
 
494
  function selectNode(d){
495
  const name = d.name;
496
  setOpacityFor(name);
497
  showInfo(name);
498
  const connected = getConnections(name);
499
  updateLabels(name, connected);
500
+ drawLoopPathsFor(name); // draw overlay highlights for loops involving this node
501
  }
502
 
503
  nodeCircleGroup.on("click", function(e,d){
504
  selectNode(d);
505
+ if (e && e.stopPropagation) e.stopPropagation();
506
  });
507
  labelGroup.on("click", function(e,d){
508
  selectNode(d);
509
+ if (e && e.stopPropagation) e.stopPropagation();
510
  });
511
 
512
  document.getElementById("arc-reset").onclick = resetOpacity;
513
+ svg.on("click", function(evt){ if(evt.target.tagName === "svg") resetOpacity(); });
514
 
515
  }
516
 
 
519
  </script>
520
  """
521
 
 
522
  def make_arc_html(nodes, node_type, buys, sells, transfers, loops):
 
523
  nodes_json = json.dumps(nodes)
524
  node_type_json = json.dumps(node_type)
525
  buys_json = json.dumps(buys)
 
536
 
537
  initial_html = make_arc_html(NODES, NODE_TYPE, BUYS, SELLS, TRANSFERS, LOOPS)
538
 
539
+ # ---------------------------------------------------------------------
 
 
 
540
  # Gradio UI
541
+ # ---------------------------------------------------------------------
542
+
543
  responsive_css = """
544
  #arc-container { padding:0; margin:0; }
545
  svg { font-family: sans-serif; }
 
550
  gr.HTML(initial_html)
551
 
552
  gr.Markdown("### Inspect Company / AMC")
553
+
554
+ # Company inspector
555
  select_company = gr.Dropdown(choices=COMPANIES, label="Select company")
556
  company_plot = gr.Plot()
557
  company_table = gr.DataFrame()
558
+
559
+ # AMC inspector
560
  select_amc = gr.Dropdown(choices=AMCS, label="Select AMC")
561
  amc_plot = gr.Plot()
562
  amc_table = gr.DataFrame()