Spaces:
Sleeping
Sleeping
Update graph.html
Browse files- 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>
|