Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -211,12 +211,12 @@ function draw() {
|
|
| 211 |
|
| 212 |
const radius = Math.min(w, h) * 0.36;
|
| 213 |
|
| 214 |
-
//
|
| 215 |
const n = NODES.length;
|
| 216 |
function angleFor(i) { return (i / n) * 2 * Math.PI; }
|
| 217 |
const nodePos = NODES.map((name,i) => {
|
| 218 |
-
const ang = angleFor(i) - Math.PI/2;
|
| 219 |
-
const ab = (name.length > 7 ? name.slice(0,5)
|
| 220 |
return {
|
| 221 |
name: name,
|
| 222 |
abbrev: ab,
|
|
@@ -229,7 +229,6 @@ function draw() {
|
|
| 229 |
const nameToIndex = {};
|
| 230 |
NODES.forEach((nm,i)=> nameToIndex[nm]=i);
|
| 231 |
|
| 232 |
-
// small node circles and labels outside
|
| 233 |
const group = svg.append("g").selectAll("g").data(nodePos).enter().append("g")
|
| 234 |
.attr("transform", d => `translate(${d.x},${d.y})`);
|
| 235 |
|
|
@@ -240,6 +239,7 @@ function draw() {
|
|
| 240 |
.style("stroke-width", 1)
|
| 241 |
.style("cursor", "pointer");
|
| 242 |
|
|
|
|
| 243 |
group.append("text")
|
| 244 |
.attr("x", d => Math.cos(d.angle) * (radius + 26))
|
| 245 |
.attr("y", d => Math.sin(d.angle) * (radius + 26))
|
|
@@ -258,12 +258,11 @@ function draw() {
|
|
| 258 |
.style("cursor","pointer")
|
| 259 |
.text(d => d.abbrev);
|
| 260 |
|
| 261 |
-
//
|
| 262 |
function bezierPath(x0,y0,x1,y1,above=true) {
|
| 263 |
const mx = (x0 + x1)/2;
|
| 264 |
const my = (y0 + y1)/2;
|
| 265 |
-
const dx = mx;
|
| 266 |
-
const dy = my;
|
| 267 |
const len = Math.sqrt(dx*dx + dy*dy) || 1;
|
| 268 |
const ux = dx/len, uy = dy/len;
|
| 269 |
const offset = (above ? -1 : 1) * Math.max(30, radius*0.9);
|
|
@@ -272,80 +271,63 @@ function draw() {
|
|
| 272 |
return `M ${x0} ${y0} Q ${cx} ${cy} ${x1} ${y1}`;
|
| 273 |
}
|
| 274 |
|
| 275 |
-
// stroke width scale
|
| 276 |
const allW = [].concat(BUYS.map(d=>d[2]), SELLS.map(d=>d[2]), TRANSFERS.map(d=>d[2]));
|
| 277 |
const wmin = Math.min(...(allW.length?allW:[1]));
|
| 278 |
const wmax = Math.max(...(allW.length?allW:[1]));
|
| 279 |
const stroke = d3.scaleLinear().domain([wmin, Math.max(wmax,1)]).range([1.0, 6.0]);
|
| 280 |
|
| 281 |
-
//
|
| 282 |
-
const buyGroup = svg.append("g")
|
| 283 |
BUYS.forEach(b => {
|
| 284 |
const a = b[0], c = b[1], wt = b[2];
|
| 285 |
if (!(a in nameToIndex) || !(c in nameToIndex)) return;
|
| 286 |
const s = nodePos[nameToIndex[a]];
|
| 287 |
const t = nodePos[nameToIndex[c]];
|
| 288 |
-
const path = bezierPath(s.x,s.y,t.x,t.y,true);
|
| 289 |
buyGroup.append("path")
|
| 290 |
-
.attr("d",
|
| 291 |
.attr("fill","none")
|
| 292 |
.attr("stroke","#2e8540")
|
| 293 |
.attr("stroke-width", stroke(wt))
|
| 294 |
-
.attr("stroke-linecap","round")
|
| 295 |
.attr("opacity", 0.92)
|
| 296 |
.attr("data-src", a)
|
| 297 |
-
.attr("data-tgt", c)
|
| 298 |
-
.on("mouseover", function() { d3.select(this).attr("opacity",1); })
|
| 299 |
-
.on("mouseout", function() { d3.select(this).attr("opacity",0.92); });
|
| 300 |
});
|
| 301 |
|
| 302 |
-
|
| 303 |
-
const sellGroup = svg.append("g").attr("class","sells");
|
| 304 |
SELLS.forEach(s => {
|
| 305 |
const c = s[0], a = s[1], wt = s[2];
|
| 306 |
if (!(c in nameToIndex) || !(a in nameToIndex)) return;
|
| 307 |
const sp = nodePos[nameToIndex[c]];
|
| 308 |
const tp = nodePos[nameToIndex[a]];
|
| 309 |
-
const path = bezierPath(sp.x,sp.y,tp.x,tp.y,false);
|
| 310 |
sellGroup.append("path")
|
| 311 |
-
.attr("d",
|
| 312 |
.attr("fill","none")
|
| 313 |
.attr("stroke","#c0392b")
|
| 314 |
.attr("stroke-width", stroke(wt))
|
| 315 |
-
.attr("stroke-linecap","round")
|
| 316 |
.attr("stroke-dasharray","4,3")
|
| 317 |
.attr("opacity",0.86)
|
| 318 |
.attr("data-src", c)
|
| 319 |
-
.attr("data-tgt", a)
|
| 320 |
-
.on("mouseover", function() { d3.select(this).attr("opacity",1); })
|
| 321 |
-
.on("mouseout", function() { d3.select(this).attr("opacity",0.86); });
|
| 322 |
});
|
| 323 |
|
| 324 |
-
|
| 325 |
-
const transferGroup = svg.append("g").attr("class","transfers");
|
| 326 |
TRANSFERS.forEach(tr => {
|
| 327 |
const sname = tr[0], tname = tr[1], wt = tr[2];
|
| 328 |
if (!(sname in nameToIndex) || !(tname in nameToIndex)) return;
|
| 329 |
const sp = nodePos[nameToIndex[sname]];
|
| 330 |
const tp = nodePos[nameToIndex[tname]];
|
| 331 |
-
const mx = (sp.x + tp.x)/2;
|
| 332 |
-
const my = (sp.y + tp.y)/2;
|
| 333 |
-
const cx = mx * 0.3, cy = my * 0.3;
|
| 334 |
-
const path = `M ${sp.x} ${sp.y} Q ${cx} ${cy} ${tp.x} ${tp.y}`;
|
| 335 |
transferGroup.append("path")
|
| 336 |
-
.attr("d",
|
| 337 |
.attr("fill","none")
|
| 338 |
.attr("stroke","#7d7d7d")
|
| 339 |
.attr("stroke-width", stroke(wt))
|
| 340 |
.attr("opacity",0.7)
|
| 341 |
.attr("data-src", sname)
|
| 342 |
-
.attr("data-tgt", tname)
|
| 343 |
-
.on("mouseover", function() { d3.select(this).attr("opacity",1); })
|
| 344 |
-
.on("mouseout", function() { d3.select(this).attr("opacity",0.7); });
|
| 345 |
});
|
| 346 |
|
| 347 |
-
|
| 348 |
-
const loopGroup = svg.append("g").attr("class","loops");
|
| 349 |
LOOPS.forEach(lp => {
|
| 350 |
const a = lp[0], c = lp[1], b = lp[2];
|
| 351 |
if (!(a in nameToIndex) || !(b in nameToIndex)) return;
|
|
@@ -353,101 +335,103 @@ function draw() {
|
|
| 353 |
const sb = nodePos[nameToIndex[b]];
|
| 354 |
const mx = (sa.x + sb.x)/2;
|
| 355 |
const my = (sa.y + sb.y)/2;
|
| 356 |
-
const len = Math.sqrt((sa.x
|
| 357 |
-
const outward = Math.max(40, radius*0.28 + len
|
| 358 |
-
const
|
| 359 |
-
const
|
| 360 |
-
const
|
| 361 |
-
const cx = mx + ux * outward;
|
| 362 |
-
const cy = my + uy * outward;
|
| 363 |
-
const path = `M ${sa.x} ${sa.y} Q ${cx} ${cy} ${sb.x} ${sb.y}`;
|
| 364 |
loopGroup.append("path")
|
| 365 |
-
.attr("d",
|
| 366 |
-
.attr("fill",
|
| 367 |
-
.attr("stroke",
|
| 368 |
.attr("stroke-width", 2.8)
|
| 369 |
-
.attr("opacity",0.95)
|
| 370 |
-
.on("mouseover", function() { d3.select(this).attr("opacity",1); })
|
| 371 |
-
.on("mouseout", function() { d3.select(this).attr("opacity",0.95); });
|
| 372 |
});
|
| 373 |
|
| 374 |
-
//
|
| 375 |
function setOpacityFor(nodeName) {
|
| 376 |
-
group.selectAll("circle").style("opacity", d =>
|
| 377 |
-
group.selectAll("text").style("opacity", d =>
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
const
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
});
|
| 388 |
-
transferGroup.selectAll("path").style("opacity", function() {
|
| 389 |
-
const src = this.getAttribute("data-src");
|
| 390 |
-
const tgt = this.getAttribute("data-tgt");
|
| 391 |
-
return (src === nodeName || tgt === nodeName) ? 0.98 : 0.06;
|
| 392 |
-
});
|
| 393 |
-
loopGroup.selectAll("path").style("opacity", 0.95);
|
| 394 |
}
|
| 395 |
|
| 396 |
function resetOpacity() {
|
| 397 |
-
group.selectAll("circle").style("opacity",
|
| 398 |
-
group.selectAll("text").style("opacity",
|
| 399 |
-
buyGroup.selectAll("path").style("opacity",
|
| 400 |
-
sellGroup.selectAll("path").style("opacity",
|
| 401 |
-
transferGroup.selectAll("path").style("opacity",
|
| 402 |
-
loopGroup.selectAll("path").style("opacity",
|
| 403 |
-
|
| 404 |
-
|
| 405 |
}
|
| 406 |
|
| 407 |
-
//
|
| 408 |
function getConnections(nodeName) {
|
| 409 |
-
let buys = BUYS.filter(x => x[0]
|
| 410 |
-
.map(x => (x[0]
|
| 411 |
-
let sells = SELLS.filter(x => x[0]
|
| 412 |
-
.map(x => (x[0]
|
| 413 |
-
let transfers = TRANSFERS.filter(x => x[0]
|
| 414 |
-
.map(x => (x[0]
|
| 415 |
-
let loops = LOOPS.filter(x => x[0]
|
| 416 |
-
.map(x => (x[0]
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
}
|
| 421 |
|
|
|
|
| 422 |
function showInfo(nodeName) {
|
| 423 |
-
const box = document.getElementById("info-box");
|
| 424 |
const con = getConnections(nodeName);
|
|
|
|
| 425 |
box.innerHTML = `
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
<div><b>Transfers:</b> ${con.transfers.length ? con.transfers.join(", ") : "<span style='color:#666'>None</span>"}</div>
|
| 432 |
-
<div><b>Loops:</b> ${con.loops.length ? con.loops.join(", ") : "<span style='color:#666'>None</span>"}</div>
|
| 433 |
-
</div>
|
| 434 |
`;
|
| 435 |
}
|
| 436 |
|
| 437 |
// click handlers
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
});
|
| 443 |
-
group.selectAll("text").
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
if (e && e.stopPropagation) e.stopPropagation();
|
| 447 |
});
|
| 448 |
|
| 449 |
document.getElementById("arc-reset").onclick = resetOpacity;
|
| 450 |
-
svg.on("click", function(event)
|
| 451 |
if (event.target.tagName === "svg") resetOpacity();
|
| 452 |
});
|
| 453 |
}
|
|
|
|
| 211 |
|
| 212 |
const radius = Math.min(w, h) * 0.36;
|
| 213 |
|
| 214 |
+
// nodes with abbrev
|
| 215 |
const n = NODES.length;
|
| 216 |
function angleFor(i) { return (i / n) * 2 * Math.PI; }
|
| 217 |
const nodePos = NODES.map((name,i) => {
|
| 218 |
+
const ang = angleFor(i) - Math.PI/2;
|
| 219 |
+
const ab = (name.length > 7 ? name.slice(0,5)+"…" : name);
|
| 220 |
return {
|
| 221 |
name: name,
|
| 222 |
abbrev: ab,
|
|
|
|
| 229 |
const nameToIndex = {};
|
| 230 |
NODES.forEach((nm,i)=> nameToIndex[nm]=i);
|
| 231 |
|
|
|
|
| 232 |
const group = svg.append("g").selectAll("g").data(nodePos).enter().append("g")
|
| 233 |
.attr("transform", d => `translate(${d.x},${d.y})`);
|
| 234 |
|
|
|
|
| 239 |
.style("stroke-width", 1)
|
| 240 |
.style("cursor", "pointer");
|
| 241 |
|
| 242 |
+
// short label on load
|
| 243 |
group.append("text")
|
| 244 |
.attr("x", d => Math.cos(d.angle) * (radius + 26))
|
| 245 |
.attr("y", d => Math.sin(d.angle) * (radius + 26))
|
|
|
|
| 258 |
.style("cursor","pointer")
|
| 259 |
.text(d => d.abbrev);
|
| 260 |
|
| 261 |
+
// helper curves
|
| 262 |
function bezierPath(x0,y0,x1,y1,above=true) {
|
| 263 |
const mx = (x0 + x1)/2;
|
| 264 |
const my = (y0 + y1)/2;
|
| 265 |
+
const dx = mx, dy = my;
|
|
|
|
| 266 |
const len = Math.sqrt(dx*dx + dy*dy) || 1;
|
| 267 |
const ux = dx/len, uy = dy/len;
|
| 268 |
const offset = (above ? -1 : 1) * Math.max(30, radius*0.9);
|
|
|
|
| 271 |
return `M ${x0} ${y0} Q ${cx} ${cy} ${x1} ${y1}`;
|
| 272 |
}
|
| 273 |
|
|
|
|
| 274 |
const allW = [].concat(BUYS.map(d=>d[2]), SELLS.map(d=>d[2]), TRANSFERS.map(d=>d[2]));
|
| 275 |
const wmin = Math.min(...(allW.length?allW:[1]));
|
| 276 |
const wmax = Math.max(...(allW.length?allW:[1]));
|
| 277 |
const stroke = d3.scaleLinear().domain([wmin, Math.max(wmax,1)]).range([1.0, 6.0]);
|
| 278 |
|
| 279 |
+
// groups
|
| 280 |
+
const buyGroup = svg.append("g");
|
| 281 |
BUYS.forEach(b => {
|
| 282 |
const a = b[0], c = b[1], wt = b[2];
|
| 283 |
if (!(a in nameToIndex) || !(c in nameToIndex)) return;
|
| 284 |
const s = nodePos[nameToIndex[a]];
|
| 285 |
const t = nodePos[nameToIndex[c]];
|
|
|
|
| 286 |
buyGroup.append("path")
|
| 287 |
+
.attr("d", bezierPath(s.x,s.y,t.x,t.y,true))
|
| 288 |
.attr("fill","none")
|
| 289 |
.attr("stroke","#2e8540")
|
| 290 |
.attr("stroke-width", stroke(wt))
|
|
|
|
| 291 |
.attr("opacity", 0.92)
|
| 292 |
.attr("data-src", a)
|
| 293 |
+
.attr("data-tgt", c);
|
|
|
|
|
|
|
| 294 |
});
|
| 295 |
|
| 296 |
+
const sellGroup = svg.append("g");
|
|
|
|
| 297 |
SELLS.forEach(s => {
|
| 298 |
const c = s[0], a = s[1], wt = s[2];
|
| 299 |
if (!(c in nameToIndex) || !(a in nameToIndex)) return;
|
| 300 |
const sp = nodePos[nameToIndex[c]];
|
| 301 |
const tp = nodePos[nameToIndex[a]];
|
|
|
|
| 302 |
sellGroup.append("path")
|
| 303 |
+
.attr("d", bezierPath(sp.x,sp.y,tp.x,tp.y,false))
|
| 304 |
.attr("fill","none")
|
| 305 |
.attr("stroke","#c0392b")
|
| 306 |
.attr("stroke-width", stroke(wt))
|
|
|
|
| 307 |
.attr("stroke-dasharray","4,3")
|
| 308 |
.attr("opacity",0.86)
|
| 309 |
.attr("data-src", c)
|
| 310 |
+
.attr("data-tgt", a);
|
|
|
|
|
|
|
| 311 |
});
|
| 312 |
|
| 313 |
+
const transferGroup = svg.append("g");
|
|
|
|
| 314 |
TRANSFERS.forEach(tr => {
|
| 315 |
const sname = tr[0], tname = tr[1], wt = tr[2];
|
| 316 |
if (!(sname in nameToIndex) || !(tname in nameToIndex)) return;
|
| 317 |
const sp = nodePos[nameToIndex[sname]];
|
| 318 |
const tp = nodePos[nameToIndex[tname]];
|
| 319 |
+
const mx = (sp.x + tp.x)/2, my = (sp.y + tp.y)/2;
|
|
|
|
|
|
|
|
|
|
| 320 |
transferGroup.append("path")
|
| 321 |
+
.attr("d", `M ${sp.x} ${sp.y} Q ${mx*0.3} ${my*0.3} ${tp.x} ${tp.y}`)
|
| 322 |
.attr("fill","none")
|
| 323 |
.attr("stroke","#7d7d7d")
|
| 324 |
.attr("stroke-width", stroke(wt))
|
| 325 |
.attr("opacity",0.7)
|
| 326 |
.attr("data-src", sname)
|
| 327 |
+
.attr("data-tgt", tname);
|
|
|
|
|
|
|
| 328 |
});
|
| 329 |
|
| 330 |
+
const loopGroup = svg.append("g");
|
|
|
|
| 331 |
LOOPS.forEach(lp => {
|
| 332 |
const a = lp[0], c = lp[1], b = lp[2];
|
| 333 |
if (!(a in nameToIndex) || !(b in nameToIndex)) return;
|
|
|
|
| 335 |
const sb = nodePos[nameToIndex[b]];
|
| 336 |
const mx = (sa.x + sb.x)/2;
|
| 337 |
const my = (sa.y + sb.y)/2;
|
| 338 |
+
const len = Math.sqrt((sa.x-sb.x)**2 + (sa.y-sb.y)**2);
|
| 339 |
+
const outward = Math.max(40, radius*0.28 + len*0.12);
|
| 340 |
+
const nlen = Math.sqrt(mx*mx + my*my)||1;
|
| 341 |
+
const ux = mx/nlen, uy = my/nlen;
|
| 342 |
+
const cx = mx + ux*outward, cy = my + uy*outward;
|
|
|
|
|
|
|
|
|
|
| 343 |
loopGroup.append("path")
|
| 344 |
+
.attr("d", `M ${sa.x} ${sa.y} Q ${cx} ${cy} ${sb.x} ${sb.y}`)
|
| 345 |
+
.attr("fill","none")
|
| 346 |
+
.attr("stroke","#227a6d")
|
| 347 |
.attr("stroke-width", 2.8)
|
| 348 |
+
.attr("opacity",0.95);
|
|
|
|
|
|
|
| 349 |
});
|
| 350 |
|
| 351 |
+
// opacity control
|
| 352 |
function setOpacityFor(nodeName) {
|
| 353 |
+
group.selectAll("circle").style("opacity", d => d.name===nodeName ? 1 : 0.18);
|
| 354 |
+
group.selectAll("text").style("opacity", d => d.name===nodeName ? 1 : 0.28);
|
| 355 |
+
|
| 356 |
+
function isConn(path) {
|
| 357 |
+
const src = path.getAttribute("data-src");
|
| 358 |
+
const tgt = path.getAttribute("data-tgt");
|
| 359 |
+
return (src===nodeName || tgt===nodeName);
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
buyGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
|
| 363 |
+
sellGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
|
| 364 |
+
transferGroup.selectAll("path").style("opacity", function(){ return isConn(this)?0.98:0.06; });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
}
|
| 366 |
|
| 367 |
function resetOpacity() {
|
| 368 |
+
group.selectAll("circle").style("opacity",1);
|
| 369 |
+
group.selectAll("text").style("opacity",1);
|
| 370 |
+
buyGroup.selectAll("path").style("opacity",0.92);
|
| 371 |
+
sellGroup.selectAll("path").style("opacity",0.86);
|
| 372 |
+
transferGroup.selectAll("path").style("opacity",0.7);
|
| 373 |
+
loopGroup.selectAll("path").style("opacity",0.95);
|
| 374 |
+
updateLabels(null, new Set());
|
| 375 |
+
document.getElementById("info-box").innerHTML = "<b>Click a node</b> to view details here.";
|
| 376 |
}
|
| 377 |
|
| 378 |
+
// detect connections for label switching
|
| 379 |
function getConnections(nodeName) {
|
| 380 |
+
let buys = BUYS.filter(x => x[0]===nodeName || x[1]===nodeName)
|
| 381 |
+
.map(x => (x[0]===nodeName ? x[1] : x[0]));
|
| 382 |
+
let sells = SELLS.filter(x => x[0]===nodeName || x[1]===nodeName)
|
| 383 |
+
.map(x => (x[0]===nodeName ? x[1] : x[0]));
|
| 384 |
+
let transfers = TRANSFERS.filter(x => x[0]===nodeName || x[1]===nodeName)
|
| 385 |
+
.map(x => (x[0]===nodeName ? x[1] : x[0]));
|
| 386 |
+
let loops = LOOPS.filter(x => x[0]===nodeName || x[2]===nodeName)
|
| 387 |
+
.map(x => (x[0]===nodeName ? x[2] : x[0]));
|
| 388 |
+
|
| 389 |
+
return new Set([].concat(buys, sells, transfers, loops));
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
// dynamic label switching
|
| 393 |
+
function updateLabels(selected, connectedSet) {
|
| 394 |
+
group.selectAll("text").text(d => {
|
| 395 |
+
if (selected && (d.name === selected || connectedSet.has(d.name))) {
|
| 396 |
+
return d.name; // show full name
|
| 397 |
+
}
|
| 398 |
+
return d.abbrev; // otherwise short
|
| 399 |
+
});
|
| 400 |
}
|
| 401 |
|
| 402 |
+
// info box updates
|
| 403 |
function showInfo(nodeName) {
|
|
|
|
| 404 |
const con = getConnections(nodeName);
|
| 405 |
+
const box = document.getElementById("info-box");
|
| 406 |
box.innerHTML = `
|
| 407 |
+
<div style="font-size:14px;"><b>${nodeName}</b></div>
|
| 408 |
+
<div style="margin-top:6px; font-size:12px;"><b>Connected nodes</b></div>
|
| 409 |
+
<div style="margin-top:6px; font-size:12px;">
|
| 410 |
+
<div><b>Buys:</b> ${[...con].join(", ") || "<span style='color:#777'>None</span>"}</div>
|
| 411 |
+
</div>
|
|
|
|
|
|
|
|
|
|
| 412 |
`;
|
| 413 |
}
|
| 414 |
|
| 415 |
// click handlers
|
| 416 |
+
function selectNode(d) {
|
| 417 |
+
const nodeName = d.name;
|
| 418 |
+
setOpacityFor(nodeName);
|
| 419 |
+
showInfo(nodeName);
|
| 420 |
+
const connected = getConnections(nodeName);
|
| 421 |
+
updateLabels(nodeName, connected);
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
group.selectAll("circle").on("click", function(e,d){
|
| 425 |
+
selectNode(d);
|
| 426 |
+
if (e.stopPropagation) e.stopPropagation();
|
| 427 |
});
|
| 428 |
+
group.selectAll("text").on("click", function(e,d){
|
| 429 |
+
selectNode(d);
|
| 430 |
+
if (e.stopPropagation) e.stopPropagation();
|
|
|
|
| 431 |
});
|
| 432 |
|
| 433 |
document.getElementById("arc-reset").onclick = resetOpacity;
|
| 434 |
+
svg.on("click", function(event){
|
| 435 |
if (event.target.tagName === "svg") resetOpacity();
|
| 436 |
});
|
| 437 |
}
|