1window.initCgSubgraph = function ({visState, renderAll, data, cgSel}) { 2 var subgraphSel = cgSel.select('.subgraph') 3 subgraphSel.datum().resizeFn = renderSubgraph 4 5 var nodeIdToNode = {} 6 var sgNodes = [] 7 var sgLinks = [] 8 9 let nodeSel = null 10 let memberNodeSel = null 11 let simulation = null 12 13 var nodeWidth = 75 14 var nodeHeight = 25 15 16 function supernodesToUrl() { 17 // util.params.set('supernodes', JSON.stringify(subgraphState.supernodes)) 18 } 19 20 var subgraphState = visState.subgraph = visState.subgraph || { 21 sticky: true, 22 dagrefy: true, 23 supernodes: visState.supernodes || [], 24 activeGrouping: { 25 isActive: false, 26 selectedNodeIds: new Set(), 27 } 28 } 29 30 d3.select('body') 31 .on('keydown.grouping' + data.metadata.slug, ev => { 32 if (ev.repeat) return 33 if (!visState.isEditMode || ev.key != 'g') return 34 subgraphState.activeGrouping.isActive = true 35 styleNodes() 36 37 subgraphSel.classed('is-grouping', true) 38 }) 39 .on('keyup.grouping' + data.metadata.slug, ev => { 40 if (!visState.isEditMode || ev.key != 'g') return 41 if (subgraphState.activeGrouping.selectedNodeIds.size > 1){ 42 var allSelectedIds = [] 43 var prevSupernodeLabel = '' 44 subgraphState.activeGrouping.selectedNodeIds.forEach(id => { 45 var node = nodeIdToNode[id] 46 if (!node?.memberNodeIds) return allSelectedIds.push(id) 47 prevSupernodeLabel = node.ppClerp 48 49 // if a supernode is selected, remove the previous super node 50 subgraphState.supernodes = subgraphState.supernodes.filter(([label, ...nodeIds]) => 51 !nodeIds.every(d => node.memberNodeIds.includes(d)) 52 ) 53 // and adds its member nodes to selection 54 node.memberNodeIds.forEach(id => allSelectedIds.push(id)) 55 }) 56 57 var label = prevSupernodeLabel || allSelectedIds 58 .map(id => nodeIdToNode[id]?.ppClerp) 59 .find(d => d) || 'supernode' 60 subgraphState.supernodes.push([label, ...new Set(allSelectedIds)]) 61 supernodesToUrl() 62 } 63 subgraphState.activeGrouping.isActive = false 64 subgraphState.activeGrouping.selectedNodeIds.clear() 65 renderSubgraph() 66 67 subgraphSel.classed('is-grouping', false) 68 }) 69 70 let {nodes, links} = data 71
72 function renderSubgraph() { 73 var c = d3.conventions({ 74 sel: subgraphSel.html(''), 75 margin: {top: 26, bottom: 5, left: visState.isHideLayer ? 0 : 30}, 76 layers: 'sd', 77 }) 78 // subgraphSel.st({borderTop: '1px solid #eee'}) 79 80 c.svg.append('text.section-title').text('Subgraph').translate(-16, 1) 81 c.svg.append('g.border-path').append('path') 82 .at({stroke: '#eee', d: 'M 0 -10 H ' + c.width}) 83 84 85 var [svg, div] = c.layers 86 87 // // set up arrowheads 88 // svg.appendMany('marker', [{id: 'mid-negative', color: '#40004b'},{id: 'mid-positive', color: '#00441b'}]) 89 // .at({id: d => d.id, orient: 'auto', refX: .1, refY: 1}) // marker-height/marker-width? 90 // .append('path') 91 // .at({d: 'M0,0 V2 L1,1 Z', fill: d => d.color}) 92 93 94 // pick out the subgraph and do supernode surgery 95 nodes.forEach(d => d.supernodeId = null) 96 var pinnedIds = visState.pinnedIds.slice(0, 200) // max of 200 nodes 97 var pinnedNodes = nodes.filter(d => pinnedIds.includes(d.nodeId)) 98 99 // create supernodes and mark their children 100 nodeIdToNode = Object.fromEntries(pinnedNodes.map(d => [d.nodeId, d])) 101 var supernodes = subgraphState.supernodes 102 .map(([label, ...nodeIds], i) => { 103 var nodeId = nodeIdToNode[label] ? `supernode-${i}` : label 104 var memberNodes = nodeIds.map(id => nodeIdToNode[id]).filter(d => d) 105 memberNodes.forEach(d => d.supernodeId = nodeId) 106 107 var rv = { 108 nodeId, 109 featureId: `supernode-${i}`, 110 ppClerp: label, 111 layer: d3.mean(memberNodes, d => +d.layer), 112 ctx_idx: d3.mean(memberNodes, d => d.ctx_idx), 113 ppLayer: d3.extent(memberNodes, d => +d.layer).join('â'), 114 streamIdx: d3.mean(memberNodes, d => d.streamIdx), 115 memberNodeIds: nodeIds, 116 memberNodes, 117 isSuperNode: true, 118 }
119 nodeIdToNode[rv.nodeId] = rv 120 121 return rv 122 }) 123 .filter(d => d.memberNodes.length) 124 125 // update clerps â fragile hack if hClerpUpdate changes 126 nodes.forEach(d => d.ppClerp = d.clerp) 127 supernodes.forEach(({ppClerp, memberNodes}) => { 128 if (memberNodes.length == 1 && ppClerp == memberNodes[0].ppClerp) return 129 130 memberNodes.forEach(d => { 131 d.ppClerp = `[${ppClerp}]` + (ppClerp != d.ppClerp ? ' ' + d.ppClerp : '') 132 }) 133 }) 134 135 // inputAbsSumExternalSn: the abs sum of input links from outside the supernode 136 pinnedNodes.forEach(d => { 137 d.inputAbsSumExternalSn = d3.sum(d.sourceLinks, e => { 138 if (!e.sourceNode.supernodeId) return Math.abs(e.weight) 139 return e.sourceNode.supernodeId == d.supernodeId ? 0 : Math.abs(e.weight) 140 }) 141 d.sgSnInputWeighting = d.inputAbsSumExternalSn/d.inputAbsSum 142 }) 143 144 // subgraph plots pinnedNodes not in a supernode and supernodes 145 sgNodes = pinnedNodes.filter(d => !d.supernodeId).concat(supernodes) 146 sgNodes.forEach(d => { 147 // for supernodes, sum up values from member nodes 148 if (d.isSuperNode) { 149 d.inputAbsSum = d3.sum(d.memberNodes, e => e.inputAbsSum) 150 d.inputAbsSumExternalSn = d3.sum(d.memberNodes, e => e.inputAbsSumExternalSn) 151 } else { 152 d.memberNodes = [d] 153 } 154 155 var sum = d3.sum(d.memberNodes, e => e.sgSnInputWeighting) 156 d.memberNodes.forEach(e => e.sgSnInputWeighting = e.sgSnInputWeighting/sum) 157 }) 158 159 // select subgraph links 160 sgLinks = links 161 .filter(d => nodeIdToNode[d.sourceNode.nodeId] && nodeIdToNode[d.targetNode.nodeId]) 162 .map(d => ({ 163 source: d.sourceNode.nodeId, 164 target: d.targetNode.nodeId, 165 weight: d.weight, 166 color: d.pctInputColor, 167 ogLink: d, 168 })) 169 170 // then remap source/target to supernodes 171 sgLinks.forEach(link => { 172 if (nodeIdToNode[link.source]?.supernodeId) link.source = nodeIdToNode[link.source].supernodeId 173 if (nodeIdToNode[link.target]?.supernodeId) link.target = nodeIdToNode[link.target].supernodeId 174 }) 175 176 // finally combine parallel links and remove self-links 177 sgLinks = d3.nestBy(sgLinks, d => d.source + '-' + d.target) 178 .map(links => { 179 var weight = d3.sum(links, link => { 180 var {inputAbsSumExternalSn, sgSnInputWeighting} = link.ogLink.targetNode 181 return link.weight/inputAbsSumExternalSn*sgSnInputWeighting 182 }) 183 184 return { 185 source: links[0].source, 186 target: links[0].target, 187 weight, 188 color: utilCg.pctInputColorFn(weight), 189 pctInput: weight, 190 pctInputColor: utilCg.pctInputColorFn(weight), 191 ogLinks: links 192 } 193 }) 194 .filter(d => d.source !== d.target) 195 sgLinks = d3.sort(sgLinks, d => Math.abs(d.weight)) 196 197 let xScale = d3.scaleLinear() 198 .domain(d3.extent(sgNodes.map(d => d.ctx_idx))) 199 .range([0, c.width*3/4]) 200 let yScale = d3.scaleLinear() 201 .domain(d3.extent(sgNodes.map(d => d.streamIdx)).toReversed()) 202 .range([0, c.height - nodeHeight]) 203 204 // d3.force is impure, need copy 205 // Also want to persist these positions across node changes 206 const existingNodes = window.selForceNodes && new Map(window.selForceNodes.map(n => [n.node.nodeId, n])) 207 window.selForceNodes = sgNodes.map(node => { 208 const existing = existingNodes?.get(node.nodeId) 209 return { 210 x: existing ? existing.x : xScale(node.ctx_idx), 211 y: existing ? existing.y : yScale(node.streamIdx), 212 fx: existing?.fx, 213 fy: existing?.fy, 214 nodeId: node.nodeId, // for addFeatureEvents 215 featureId: node.featureId, // for addFeatureEvents 216 node, 217 sortedSlug: d3.sort(node.memberNodes.map(d => d.featureIndex).join(' ')), 218 } 219 }) 220 221 222 var selForceNodes = window.selForceNodes = d3.sort(window.selForceNodes, d => d.sortedSlug) 223 window._exportSubgraphPos = function(){ 224 return selForceNodes.map(d => [d.x/c.width*1000, d.y/c.height*1000].map(Math.round)).flat().join(' ') 225 } 226 227 if (simulation) simulation.stop() 228 simulation = d3.forceSimulation(selForceNodes) 229 .force('link', d3.forceLink(sgLinks).id(d => d.node.nodeId)) 230 .force('charge', d3.forceManyBody())
231 .force('collide', d3.forceCollide(Math.sqrt(nodeHeight ** 2 + nodeWidth ** 2) / 2)) 232 .force('container', forceContainer([[-10, 0], [c.width - nodeHeight, c.height - nodeHeight]])) 233 .force('x', d3.forceX(d => xScale(d.node.ctx_idx)).strength(.1)) 234 .force('y', d3.forceY(d => yScale(d.node.streamIdx)).strength(2)) 235 236 var svgPaths = svg.appendMany('path.link-path', sgLinks).at({ 237 fill: 'none', 238 markerMid: d => d.weight > 0 ? 'url(#mid-positive)' : 'url(#mid-negative)', 239 strokeWidth: d => Math.abs(d.weight)*15, 240 stroke: d => d.color, 241 opacity: 0.8, 242 strokeLinecap: 'round', 243 }) 244 245 var edgeLabels = svg.appendMany('text.weight-label', sgLinks) 246 // .text(d => d3.format('+.2f')(d.weight)) 247 248 simulation.on('tick', renderForce) 249 250 var drag = d3.drag() 251 .on('drag', (ev) => { 252 // Only when actually dragging, mark as no longer dagre positioned and restart sim 253 ev.subject.dagrePositioned = false 254 if (!ev.active) simulation.alphaTarget(0.3).restart() 255 ev.subject.fx = ev.subject.x = ev.x 256 ev.subject.fy = ev.subject.y = ev.y 257 renderForce() 258 }) 259 .on('end', (ev) => { 260 if (!ev.active) simulation.alphaTarget(0) 261 if (!subgraphState.sticky && !ev.subject.dagrePositioned){ 262 ev.subject.fx = null 263 ev.subject.fy = null 264 } 265 }) 266 267 nodeSel = div 268 .appendMany('div.supernode-container', selForceNodes) 269 .translate(d => [d.x, d.y]) 270 .st({width: nodeWidth, height: nodeHeight}) 271 .call(utilCg.addFeatureEvents(visState, renderAll, ev => ev.shiftKey)) 272 .on('click.group', (ev, d) => { 273 var {isActive, selectedNodeIds} = subgraphState.activeGrouping 274 if (!isActive) return 275 276 // If it's a child node, use its parent supernode's ID instead 277 var nodeId = d.supernodeId || d.nodeId 278 selectedNodeIds.has(nodeId) ? selectedNodeIds.delete(nodeId) : selectedNodeIds.add(nodeId) 279 280 styleNodes() 281 ev.stopPropagation() 282 ev.preventDefault() 283 }) 284 .call(drag) 285 286 selForceNodes.forEach(d => { 287 if (!d.node.memberNodes) d.node.memberNodes = [d.node] 288 }) 289 290 var supernodeSel = nodeSel//.filter(d => d.node.isSuperNode) 291 .classed('is-supernode', true) 292 .st({height: nodeHeight + 12}) 293 294 memberNodeSel = supernodeSel.append('div.member-circles') 295 .st({ 296 width: d => d.node.memberNodes.length <= 4 ? 'auto' : 'calc(32px + 12px)', 297 gap: d => d.node.memberNodes.length <= 4 ? 4 : 0, 298 }) 299 .appendMany('div.member-circle', d => d.node.memberNodes) 300 .classed('not-clt-feature', d => d.feature_type != 'cross layer transcoder') 301 .st({marginLeft: function(d, i) { 302 var n = this.parentNode.childNodes.length 303 return n <= 4 ? 0 : i == 0 ? 0 : -((n - 4)*8)/(n - 1) 304 }}) 305 .call(utilCg.addFeatureEvents(visState, renderAll, ev => ev.shiftKey)) 306 .on('click.stop-parent', ev => { 307 if (!subgraphState.activeGrouping.isActive) ev.stopPropagation() 308 }) 309 .on('mouseover.stop-parent', ev => ev.stopPropagation()) 310 .at({title: d => d.ppClerp}) 311 312 if (visState.isEditMode) { 313 // TODO: enable 314 supernodeSel.select('.member-circles') 315 .filter(d => d.node.isSuperNode) 316 .append('div.ungroup-btn') 317 .text('Ã').st({top: 2, left: -15, position: 'absolute'}) 318 .on('click', (ev, d) => { 319 ev.stopPropagation() 320 321 subgraphState.supernodes = subgraphState.supernodes.filter(([label, ...nodeIds]) => 322 !nodeIds.every(id => d.node.memberNodeIds.includes(id)) 323 ) 324 supernodesToUrl() 325 renderSubgraph() 326 }) 327 } 328 329 var nodeTextSel = nodeSel.append('div.node-text-container') 330 nodeTextSel.append('span') 331 .text(d => d.node.ppClerp) 332 .on('click', (ev, d) => { 333 if (!visState.isEditMode) return 334 if (!d.node.isSuperNode) return 335 // TODO: enable? 336 return 337 ev.stopPropagation() 338 339 var spanSel = d3.select(ev.target).st({display: 'none'}) 340 var input = d3.select(spanSel.node().parentNode).append('input') 341 .at({class: 'temp-edit', value: spanSel.text()}) 342 .on('blur', save) 343 .on('keydown', ev => { 344 if (ev.key === 'Enter'){ 345 save() 346 input.node().blur() 347 } 348 ev.stopPropagation() 349 }) 350 351 input.node().focus() 352 353 function save(){ 354 var idx = subgraphState.supernodes.findIndex(([label, ...nodeIds]) => 355 nodeIds.every(id => d.node.memberNodeIds.includes(id)) 356 ) 357 if (idx >= 0){ 358 subgraphState.supernodes[idx][0] = input.node().value || 'supernode' 359 supernodesToUrl() 360 renderSubgraph() 361 } 362 } 363 }) 364 365 366 nodeTextSel.each(function(d) { 367 d.textHeight = this.getBoundingClientRect().height || -8 368 }) 369 370 nodeSel.append('div.clicked-weight.source') 371 nodeSel.append('div.clicked-weight.target') 372 styleNodes() 373 374 375 var checkboxes = Object.entries({ 376 sticky: () => { 377 // simulation.alphaTarget(0.3).restart() 378 if (!subgraphState.sticky) unsticky() 379 }, 380 dagrefy: () => { 381 subgraphState.dagrefy ? dagrefy() : selForceNodes.forEac
381h(d => d.dagrePositioned = null) 382 }, 383 }).map(([key, fn]) => ({key, fn})) 384 385 386 if (visState.isEditMode && false) { 387 div.append('div.checkbox-container').translate([-c.margin.left, c.margin.bottom]) 388 .appendMany('label', checkboxes).append('input') 389 .at({type: 'checkbox'}) 390 .property('checked', d => subgraphState[d.key]) 391 .on('change', function(ev, d){ 392 subgraphState[d.key] = this.checked 393 d.fn() 394 }) 395 .parent().append('span').text(d => d.key) 396 } 397 398 checkboxes.forEach(d => d.fn()) 399 400 function unsticky(){ 401 selForceNodes.forEach(d => (d.fx = d.fy = null)) 402 simulation.alphaTarget(0.3).restart() 403 if (subgraphState.dagrefy) { 404 subgraphState.dagrefy = false 405 d3.select('.checkbox-container').selectAll('input').filter(d => d.key == 'dagrefy').property('checked', 0) 406 checkboxes.find(d => d.key == 'dagrefy').fn() 407 } 408 } 409 410 function dagrefy(){ 411 if (visState.sg_pos){ 412 var nums = visState.sg_pos.split(' ').map(d => +d) 413 selForceNodes.forEach((d, i) => { 414 d.fx = d.x = nums[i*2 + 0]/1000*c.width 415 d.fy = d.y = nums[i*2 + 1]/1000*c.height 416 }) 417 418 nodeSel.translate(d => [d.x, d.y]) 419 styleNodes() 420 renderEdges() 421 422 visState.og_sg_pos = visState.sg_pos 423 delete visState.sg_pos 424 } 425 if (visState.og_sg_pos) return 426 427 428 var g = new window.dagre.graphlib.Graph() 429 g.setGraph({rankdir: 'BT', nodesep: 20, ranksep: 20}) 430 g.setDefaultEdgeLabel(() => ({})) 431 432 sgLinks.forEach(d =>{ 433 if (Math.abs(d.weight) < .003) return 434 // set width and height to make dagre return x and y for edges 435 g.setEdge(d.source.nodeId, d.target.nodeId, {width: 1, height: 1, labelpos: 'c', weight: Math.abs(d.weight)}) 436 }) 437 sgNodes.forEach(d => { 438 g.setNode(d.nodeId, {width: nodeWidth, height: nodeHeight}) 439 }) 440 441 window.dagre.layout(g) 442 443 // rescale to fit container 444 var xs = d3.scaleLinear([0, g.graph().width], [0, Math.min(c.width, g.graph().width)]) 445 var ys = d3.scaleLinear([0, g.graph().height], [0, Math.min(c.height, g.graph().height)]) 446 447 // flip to make ctx_idx left to right and streamIdx bottom to top 448 var w0 = d3.mean(selForceNodes, d => g.node(d.nodeId).x*d.node.ctx_idx) 449 var w1 = d3.mean(selForceNodes, d => -g.node(d.nodeId).x*d.node.ctx_idx) 450 if (w0 < w1) xs.range(xs.range().reverse()) 451 452 var w0 = d3.mean(selForceNodes, d => g.node(d.nodeId).y*d.node.streamIdx) 453 var w1 = d3.mean(selForceNodes, d => -g.node(d.nodeId).y*d.node.streamIdx) 454 if (w0 < w1) ys.range(ys.range().reverse()) 455 456 for (var node of window.selForceNodes) { 457 var pos = g.node(node.nodeId) 458 node.fx = node.x = xs(pos.x) - nodeWidth/2 459 node.fy = node.y = ys(pos.y) - nodeHeight/2 460 node.dagrePositioned = true 461 } 462 463 // var curveFactory = d3.line(d => d.x, d => d.y).curve(d3.curveBasis) 464 // svgPaths.at({d: d => { 465 // var points = g.edge(d.source.nodeId, d.target.nodeId)?.points 466 // if (!points) return '' 467 // return curveFactory(points.map(p => ({x: xs(p.x), y: ys(p.y)}))) 468 // }}) 469 renderEdges() 470 471 // edgeLabels.translate(d => { 472 // var pos = g.edge(d.source.nodeId, d.target.nodeId) 473 // if (!pos) return [-100, -100] 474 // return [xs(pos.x), ys(pos.y)] 475 // }) 476 styleNodes() 477 } 478 479 function renderForce(){ 480 nodeSel.translate(d => [d.x, d.y]) 481 482 renderEdges() 483 484 edgeLabels 485 .filter(d => !(d.source.dagrePositioned && d.target.dagrePositioned)) 486 .translate(d => [ 487 (d.source.x + d.target.x) / 2 + nodeWidth / 2, 488 (d.source.y + d.target.y) / 2 + nodeHeight / 2 489 ]) 490 } 491 492 function renderEdges(){ 493 494 // TODO: use actual strokeWidth to spread 495 d3.nestBy(sgLinks, d => d.source).forEach(links => { 496 // if (links[0].source.nodeId == '6_12890134_-0') debugger 497 var numSlots = links[0].source.node.memberNodes.length 498 var totalWidth = (Math.min(4, numSlots))*8 499 d3.sort(links, d => Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x)) 500 .forEach((d, i) => d.sourceOffsetX = (i - links.length/2)*totalWidth/links.length) 501 }) 502 503 d3.nestBy(sgLinks, d => d.target).forEach(links => { 504 var numSlots = links[0].target.node.memberNodes.length 505 var totalWidth = (Math.min(4, numSlots) + 1)*3 506 d3.sort(links, d => -Math.atan2(d.source.y - d.target.y, d.source.x - d.target.x)) 507 .forEach((d, i) => d.targetOffsetX = (i - links.length/2)*totalWidth/links.length) 508 }) 509 510 svgPaths.at({ 511 d: d => { 512 var x0 = d.source.x + nodeWidth/2 + d.sourceOffsetX 513 var y0 = d.source.y 514 var x1 = d.target.x + nodeWidth/2 + d.targetOffsetX 515 var y1 = d.target.y + d.target.textHeight + 28 516 517 return `M${x0},${y0} L${x1},${y1}` 518 }, 519 }) 520 } 521 } 522 523 524 function styleNodes() { 525 if (!nodeSel) return 526 527 nodeSel 528 .classed('clicked', d => d.nodeId == visState.clickedId) 529 .classed('hovered', d => d.featureId == visState.hoveredId) 530 .st({zIndex: d => Math.round(d.x*20 + d.y) + 1000}) 531 .classed('grouping-selected', d => subgraphState.activeGrouping.selectedNodeIds.has(d.nodeId)) 532 533 memberNodeSel 534 .classed('clicked', d => d.nodeId == visState.clickedId) 535 .classed('hovered', d => d.featureId == visState.hoveredId) 536 .st({ 537 background: d => d.tmpClickedLink?.pctInputColor, 538 color: d => utilCg.bgColorToTextColor(d.tmpClickedLink?.pctInputColor) 539 }) 540 // .at({title: d => d3.format('.1%')(d.tmpClickedLink?.pctInput)}) 541 542 543 544 // style clicked links using supernode adjusted graph when possible
545 sgNodes.forEach(d => { 546 d.tmpClickedSgSource = d.tmpClickedLink?.sourceNode == d ? d.tmpClickedLink : null 547 d.tmpClickedSgTarget = d.tmpClickedLink?.targetNode == d ? d.tmpClickedLink : null 548 }) 549 550 if (visState.clickedId) { 551 sgLinks.forEach(d => { 552 if (d.source.nodeId == visState.clickedId) nodeIdToNode[d.target.nodeId].tmpClickedSgTarget = d 553 if (d.target.nodeId == visState.clickedId) nodeIdToNode[d.source.nodeId].tmpClickedSgSource = d 554 }) 555 } 556 557 // nodeSel.selectAll('.clicked-weight.source') 558 // .st({display: d => d.node.tmpClickedSgSource ? '' : 'none'}) 559 // .filter(d => d.node.tmpClickedSgSource) 560 // .text(d => d3.format('.1%')(d.node.tmpClickedSgSource.pctInput)) 561 // .st({ 562 // background: d => d.node.tmpClickedSgSource.pctInputColor, 563 // color: d => utilCg.bgColorToTextColor(d.node.tmpClickedSgSource.pctInputColor) 564 // }) 565 566 // nodeSel.selectAll('.clicked-weight.target') 567 // .st({display: d => d.node.tmpClickedSgTarget ? '' : 'none'}) 568 // .filter(d => d.node.tmpClickedSgTarget) 569 // .text(d => d3.format('.1%')(d.node.tmpClickedSgTarget.pctInput)) 570 // .st({ 571 // background: d => d.node.tmpClickedSgTarget.pctInputColor, 572 // color: d => utilCg.bgColorToTextColor(d.node.tmpClickedSgTarget.pctInputColor) 573 // }) 574 } 575 576 renderAll.hClerpUpdate.fns['subgraph'] = renderSubgraph 577 renderAll.pinnedIds.fns['subgraph'] = renderSubgraph 578 renderAll.clickedId.fns['subgraph'] = styleNodes 579 renderAll.hoveredId.fns['subgraph'] = styleNodes 580 581 // https://github.com/1wheel/d3-force-container/blob/master/src/force-container.js 582 function forceContainer(bbox) { 583 var nodes, strength = 1 584 585 function force(alpha) { 586 var i, 587 n = nodes.length, 588 node, 589 x = 0, 590 y = 0 591 592 for (i = 0; i < n; ++i) { 593 node = nodes[i], x = node.x, y = node.y 594 595 if (x < bbox[0][0]) node.vx += (bbox[0][0] - x)*alpha 596 if (y < bbox[0][1]) node.vy += (bbox[0][1] - y)*alpha 597 if (x > bbox[1][0]) node.vx += (bbox[1][0] - x)*alpha 598 if (y > bbox[1][1]) node.vy += (bbox[1][1] - y)*alpha 599 } 600 } 601 602 force.initialize = function(_){ 603 nodes = _ 604 } 605 606 force.bbox = function(_){ 607 return arguments.length ? (bbox = +_, force) : bbox 608 } 609 force.strength = function(_){ 610 return arguments.length ? (strength = +_, force) : strength 611 } 612 613 return force 614 } 615} 616 617window.init?.()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.