graflow: added icmp example & fixed grand-slam long-links
This commit is contained in:
@@ -161,14 +161,14 @@
|
||||
<template id="svg-arrows">
|
||||
<defs>
|
||||
<marker id="arrow"
|
||||
viewBox="0 0 15 15"
|
||||
refX="15"
|
||||
refY="7"
|
||||
markerWidth="15"
|
||||
markerHeight="15"
|
||||
viewBox="0 0 10 10"
|
||||
refX="10"
|
||||
refY="5"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto-start-reverse"
|
||||
markerUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L15,7 L0,15 Z" fill="#FF0"/>
|
||||
<path d="M0,0 L10,5 L0,10 Z" fill="#FF0"/>
|
||||
</marker>
|
||||
</defs>
|
||||
</template>
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
}
|
||||
bz-graflow{
|
||||
overflow: scroll;
|
||||
border: 2px solid black;
|
||||
}
|
||||
bz-graflow.compunet{ grid-column: 1 / -1; width: 80vw; height: 40vh; background:black; }
|
||||
bz-graflow.eic{ grid-column: 1 / -1; width: 80vw; height: 30vh; background: var(--eicui-base-color-grey-10); }
|
||||
@@ -77,6 +78,14 @@
|
||||
(evt) => { grflw3.autoPlace('vertical', 80, 50, 1000) }
|
||||
)
|
||||
|
||||
const grflw4 = document.querySelector('bz-graflow.icmp')
|
||||
document.querySelector('[data-trigger="onAutoplace4H"]').addEventListener('click',
|
||||
(evt) => { grflw4.autoPlace('horizontal', 80, 80, 1000) }
|
||||
)
|
||||
document.querySelector('[data-trigger="onAutoplace4V"]').addEventListener('click',
|
||||
(evt) => { grflw4.autoPlace('vertical', 80, 80, 1000) }
|
||||
)
|
||||
|
||||
document.querySelector('[data-id="compunet"]').addEventListener('change',
|
||||
(evt) => { grflw1.setAttribute('tension', evt.target.value); grflw1.refresh() }
|
||||
)
|
||||
@@ -86,12 +95,16 @@
|
||||
document.querySelector('[data-id="organi"]').addEventListener('change',
|
||||
(evt) => { grflw3.setAttribute('tension', evt.target.value); grflw3.refresh() }
|
||||
)
|
||||
|
||||
document.querySelector('[data-id="icmp"]').addEventListener('change',
|
||||
(evt) => { grflw4.setAttribute('tension', evt.target.value); grflw4.refresh() }
|
||||
)
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<bz-graflow class="compunet" flow="/app/assets/json/bzGraflow/testFlow1.json" tension="60">
|
||||
<bz-graflow class="compunet" flow="/app/assets/json/bzGraflow/testFlow1.json" tension="60" isolated>
|
||||
<div class="demooptions"> <!-- just for demo purposes -->
|
||||
<button data-trigger="onAutoplace1H">Auto-place Horizontal</button>
|
||||
<button data-trigger="onAutoplace1V">Auto-place Vertical</button>
|
||||
@@ -99,7 +112,7 @@
|
||||
</div>
|
||||
</bz-graflow>
|
||||
|
||||
<bz-graflow class="eic" flow="/app/assets/json/bzGraflow/testFlowEic.json" tension="60">
|
||||
<bz-graflow class="eic" flow="/app/assets/json/bzGraflow/testFlowEic.json" tension="60" isolated>
|
||||
<div class="demooptions"> <!-- just for demo purposes -->
|
||||
<button data-trigger="onAutoplace2H">Auto-place Horizontal</button>
|
||||
<button data-trigger="onAutoplace2V">Auto-place Vertical</button>
|
||||
@@ -107,7 +120,7 @@
|
||||
</div>
|
||||
</bz-graflow>
|
||||
|
||||
<bz-graflow class="organi" flow="/app/assets/json/bzGraflow/testFlow2.json" tension="60">
|
||||
<bz-graflow class="organi" flow="/app/assets/json/bzGraflow/testFlow2.json" tension="60" isolated>
|
||||
<div class="demooptions">
|
||||
<button data-trigger="onAutoplace3H">Auto-place Horizontal</button>
|
||||
<button data-trigger="onAutoplace3V">Auto-place Vertical</button>
|
||||
@@ -115,5 +128,14 @@
|
||||
</div>
|
||||
</bz-graflow>
|
||||
|
||||
|
||||
<bz-graflow class="icmp" flow="/app/assets/json/bzGraflow/testFlowICMP.json" tension="60" isolated>
|
||||
<div class="demooptions"> <!-- just for demo purposes -->
|
||||
<button data-trigger="onAutoplace4H">Auto-place Horizontal</button>
|
||||
<button data-trigger="onAutoplace4V">Auto-place Vertical</button>
|
||||
<div class-"cols-2"=""><label>tension</label><input data-id="icmp" type="number" size="2" value="60"></div>
|
||||
</div>
|
||||
</bz-graflow>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"coords": { "x": 220, "y": 120}
|
||||
},
|
||||
{ "nodeType": "inc",
|
||||
"subflow": "/app/assets/json/bzGraflow/testSubFlow1.json",
|
||||
"subflow": "/app/assets/json/bzGraflow/testFlowEic.json",
|
||||
"portLinks": [
|
||||
{ "parentPort": ["in1"], "subflowPort": ["inp1"] },
|
||||
{ "parentPort": ["out1"], "subflowPort": ["out1"] }
|
||||
|
||||
@@ -2,35 +2,35 @@
|
||||
"nodesFile": "/app/assets/html/bzGraflow/nodesTest2.html",
|
||||
"flow": {
|
||||
"nodes":[
|
||||
{ "nodeType": "start",
|
||||
"id": "aze",
|
||||
"coords": { "x": 220, "y": 20},
|
||||
"markup": { "text": "Start" }
|
||||
},
|
||||
{ "nodeType": "process",
|
||||
"id": "aze2",
|
||||
"coords": { "x": 220, "y": 120},
|
||||
"xcoords": { "x": 220, "y": 120},
|
||||
"markup": { "text": "x = alph - 1" }
|
||||
},
|
||||
{ "nodeType": "condition",
|
||||
"id": "qsd",
|
||||
"coords": { "x": 250, "y": 270},
|
||||
"xcoords": { "x": 250, "y": 270},
|
||||
"markup": { "text": "x > 0" }
|
||||
},
|
||||
{ "nodeType": "preparation",
|
||||
"id": "qsd2",
|
||||
"coords": { "x": 250, "y": 470},
|
||||
"xcoords": { "x": 250, "y": 470},
|
||||
"markup": { "text": "prepare SQL" }
|
||||
},
|
||||
{ "nodeType": "database",
|
||||
"id": "wcx",
|
||||
"coords": { "x": 500, "y": 450},
|
||||
"xcoords": { "x": 500, "y": 450},
|
||||
"markup": { "text": "MySQL<br>Store" }
|
||||
},
|
||||
{ "nodeType": "end",
|
||||
"id": "ert",
|
||||
"coords": { "x": 250, "y": 650},
|
||||
"xcoords": { "x": 250, "y": 650},
|
||||
"markup": { "text": "End" }
|
||||
},
|
||||
{ "nodeType": "start",
|
||||
"id": "aze",
|
||||
"xcoords": { "x": 220, "y": 20},
|
||||
"markup": { "text": "StartMike" }
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"nodesFile": "/app/assets/html/bzGraflow/nodesEIC.html",
|
||||
"flow": {
|
||||
"nodes":[
|
||||
{ "nodeType": "eicBasic",
|
||||
"id": "eval",
|
||||
"markup": {
|
||||
"title": "Evaluations",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": { "node": "eval", "nodeId":null}
|
||||
},
|
||||
{ "nodeType": "eicBasic",
|
||||
"id": "gap",
|
||||
"ncoords": { "x": 100, "y": 220},
|
||||
"markup": {
|
||||
"title": "GAP",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": { "a": "a2", "b":"b2"}
|
||||
},
|
||||
{ "nodeType": "eicBasic",
|
||||
"id": "cid",
|
||||
"ncoords": { "x": 150, "y": 320},
|
||||
"markup": {
|
||||
"title": "CID",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": { "a": "a3", "b":"b3"}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "allocation",
|
||||
"markup": {
|
||||
"title": "Case Allocation",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "equity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "signature",
|
||||
"markup": {
|
||||
"title": "Grant Signature",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "grant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "progress-meeting",
|
||||
"markup": {
|
||||
"title": "Progress Meetings",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "grant",
|
||||
"instanciable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "techdd",
|
||||
"markup": {
|
||||
"title": "Tech Due Diligences",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "equity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "kyc",
|
||||
"markup": {
|
||||
"title": "KYC",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "equity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "aifm-advisory",
|
||||
"markup": {
|
||||
"title": "AIFM Advisory Commitee",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "equity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "aifm-investment",
|
||||
"markup": {
|
||||
"title": "AIFM Investment Commitee",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "equity",
|
||||
"parent": "aifm-advisory"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "eicBasic",
|
||||
"id": "agreement",
|
||||
"markup": {
|
||||
"title": "Investment Agreement",
|
||||
"subtitle": "...",
|
||||
"severity": "secondary"
|
||||
},
|
||||
"data": {
|
||||
"track": "equity",
|
||||
"parent": "aifm-investment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{ "from": ["eval", "out2"], "to": ["gap", "in1"] },
|
||||
{ "from": ["eval", "out1"], "to": ["cid", "in1"] },
|
||||
{ "from": ["eval", "out3"], "to": ["allocation", "in1"] },
|
||||
{ "from": ["gap", "out1"], "to": ["signature", "in1"] },
|
||||
{ "from": ["signature", "out1"], "to": ["progress-meeting", "in1"] },
|
||||
{ "from": ["cid", "out1"], "to": ["techdd", "in1"] },
|
||||
{ "from": ["allocation", "out1"], "to": ["techdd", "in3"] },
|
||||
{ "from": ["allocation", "out2"], "to": ["kyc", "in2"] },
|
||||
{ "from": ["techdd", "out1"], "to": ["aifm-advisory", "in1"] },
|
||||
{ "from": ["kyc", "out1"], "to": ["aifm-advisory", "in3"] },
|
||||
{ "from": ["aifm-advisory", "out1"], "to": ["aifm-investment", "in1"] },
|
||||
{ "from": ["aifm-investment", "out1"], "to": ["agreement", "in1"] },
|
||||
{ "from": ["gap", "out3"], "to": ["aifm-investment", "in1"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+89
-36
@@ -113,7 +113,7 @@ class BZgraflow extends Buildoz{
|
||||
}
|
||||
|
||||
// Now load styles (once)
|
||||
if(!BZgraflow._loadedNodeStyles.has(url)) {
|
||||
if(!BZgraflow._loadedNodeStyles.has(url) || this.attributes.isolated) {
|
||||
const styles = doc.querySelectorAll('style')
|
||||
styles.forEach(styleEl => {
|
||||
const style = document.createElement('style')
|
||||
@@ -151,39 +151,65 @@ class BZgraflow extends Buildoz{
|
||||
}
|
||||
|
||||
zoomIn(id){
|
||||
console.log('==============================>ZOOM IN:', id)
|
||||
const node = this.stagedNodes[id]
|
||||
const nodeBB = node.getBoundingClientRect()
|
||||
const parentBB = this.nodesContainer.getBoundingClientRect()
|
||||
const sx = parentBB.width / nodeBB.width
|
||||
const sy = parentBB.height / nodeBB.height
|
||||
const tx = parentBB.left - nodeBB.left + this.nodesContainer.scrollLeft // TODO Should have a meth to accumulate scrolls in ancestors
|
||||
const ty = parentBB.top - nodeBB.top + this.nodesContainer.scrollTop // TODO Should have a meth to accumulate scrolls in ancestors
|
||||
node.style.setProperty('--tx', tx + 'px')
|
||||
node.style.setProperty('--ty', ty + 'px')
|
||||
node.style.setProperty('--sx', sx)
|
||||
node.style.setProperty('--sy', sy)
|
||||
node.style.zIndex = '9999'
|
||||
node.classList.add('scaler')
|
||||
Promise.all(node.getAnimations().map(a => a.finished)).then((transitions) => {
|
||||
const testEl = document.createElement('bz-graflow')
|
||||
testEl.setAttribute('flow', '/app/assets/json/bzGraflow/testFlowEic.json')
|
||||
testEl.setAttribute('tension', '60')
|
||||
testEl.classList.add('eic')
|
||||
const nodeEl = this.stagedNodes[id]
|
||||
if(!nodeEl) return
|
||||
|
||||
this.Invade(this, testEl)
|
||||
node.classList.remove('scaler')
|
||||
// Create the child graflow first, place it above (foreground) the current graflow,
|
||||
// scaled down so it fits exactly inside the clicked node, then animate it to full size.
|
||||
const nodeBB = nodeEl.getBoundingClientRect()
|
||||
const parentBB = this.getBoundingClientRect()
|
||||
|
||||
const flowNode = this.flow?.nodes?.find(n => n.id === id)
|
||||
const flowUrl = flowNode?.subflow
|
||||
|
||||
const childEl = document.createElement('bz-graflow')
|
||||
childEl.setAttribute('flow', flowUrl)
|
||||
childEl.setAttribute('tension', this.getBZAttribute('tension') || '60')
|
||||
childEl.style.zIndex = '9999'
|
||||
|
||||
// Put the child in the exact same viewport rect as the parent (fixed overlay)
|
||||
this.Invade(this, childEl, { hideOld:false })
|
||||
|
||||
// Initial transform so the full-size child "fits" inside the node
|
||||
const sx0 = nodeBB.width / parentBB.width
|
||||
const sy0 = nodeBB.height / parentBB.height
|
||||
const tx0 = nodeBB.left - parentBB.left
|
||||
const ty0 = nodeBB.top - parentBB.top
|
||||
|
||||
// Inline "scaler" (shadow styles don't apply to the child element)
|
||||
childEl.style.transformOrigin = 'top left'
|
||||
childEl.style.willChange = 'transform'
|
||||
childEl.style.transition = 'transform 300ms ease-in-out'
|
||||
childEl.style.transform = 'translate(var(--tx, 0px), var(--ty, 0px)) scale(var(--sx, 1), var(--sy, 1))'
|
||||
childEl.style.setProperty('--tx', tx0 + 'px')
|
||||
childEl.style.setProperty('--ty', ty0 + 'px')
|
||||
childEl.style.setProperty('--sx', sx0)
|
||||
childEl.style.setProperty('--sy', sy0)
|
||||
|
||||
// Force style flush, then animate back to identity (full parent size)
|
||||
childEl.getBoundingClientRect()
|
||||
requestAnimationFrame(() => {
|
||||
childEl.style.setProperty('--tx', '0px')
|
||||
childEl.style.setProperty('--ty', '0px')
|
||||
childEl.style.setProperty('--sx', 1)
|
||||
childEl.style.setProperty('--sy', 1)
|
||||
})
|
||||
|
||||
childEl.addEventListener('transitionend', (e) => {
|
||||
if(e.propertyName !== 'transform') return
|
||||
this.style.visibility = 'hidden'
|
||||
}, { once:true })
|
||||
}
|
||||
|
||||
Invade(oldEl, newEl){
|
||||
Invade(oldEl, newEl, { hideOld=true } = {}){
|
||||
const r = oldEl.getBoundingClientRect()
|
||||
oldEl.style.visibility = 'hidden'
|
||||
if(hideOld) oldEl.style.visibility = 'hidden'
|
||||
newEl.style.position = 'fixed'
|
||||
newEl.style.left = r.left + 'px'
|
||||
newEl.style.top = r.top + 'px'
|
||||
newEl.style.width = r.width + 'px'
|
||||
newEl.style.height = r.height + 'px'
|
||||
newEl.style.display = 'block'
|
||||
oldEl.parentNode.appendChild(newEl)
|
||||
}
|
||||
|
||||
@@ -389,21 +415,40 @@ testEl.classList.add('eic')
|
||||
layerWidths.push(totWidth)
|
||||
}
|
||||
|
||||
// Temporary "virtual" links used only during autoPlace() to let reorderLayers()
|
||||
// reason about placeholder nodes as if they were part of the original long-link.
|
||||
// This prevents bogus swaps caused by missing port info on placeholders.
|
||||
this._virtualLinks = new Map()
|
||||
|
||||
// If any long-links, create placeholders for skipped layers
|
||||
this.flow.longLinks = this.findLongLinks(this.flow.links)
|
||||
for(const link of this.flow.longLinks){
|
||||
for(const layerIdx of link.skippedLayers){
|
||||
for(const llink of this.flow.longLinks){
|
||||
let fakeParent = llink.link.from[0]
|
||||
for(const layerIdx of llink.skippedLayers){
|
||||
const nid = `longLinkPlaceHolder_${crypto.randomUUID()}`
|
||||
layers[layerIdx].push(nid)
|
||||
link.interNodes.push(nid)
|
||||
llink.interNodes.push(nid)
|
||||
// Placeholders are added after initial index computation; give them an index
|
||||
// so reorderLayers() can take them into account (otherwise they default to base=0).
|
||||
indexes[nid] = { base: layers[layerIdx].length - 1, ports: {} }
|
||||
// Virtual link: treat placeholder as receiving the same "from port" as the original long-link.
|
||||
// (Child port doesn't matter for placeholders since they have no ports.)
|
||||
this._virtualLinks.set(`${fakeParent}__${nid}`, {
|
||||
from: [fakeParent, llink.link.from[1]],
|
||||
to: [nid, llink.link.to[1]],
|
||||
})
|
||||
parents[nid] = [fakeParent]
|
||||
fakeParent = nid
|
||||
}
|
||||
}
|
||||
|
||||
// Reorder layers to avoid crossings thanks to indexes
|
||||
this.reorderLayers(layers, parents, indexes, orientation)
|
||||
delete this._virtualLinks
|
||||
|
||||
// Finally place everything
|
||||
if(orientation=='horizontal'){
|
||||
const fakeNodeHeight = 10
|
||||
let x = gapx
|
||||
for(const [idx, layer] of layers.entries()){
|
||||
let wMax = this.getMaxWidth(layer)
|
||||
@@ -414,14 +459,15 @@ testEl.classList.add('eic')
|
||||
this.moveNode(nid, x, y, orientation, tween)
|
||||
y += gapy + bb.height
|
||||
} else {
|
||||
this.addFakeNode(nid, x, y, wMax*0.75, 10)
|
||||
this.addFakeNode(nid, x, y, wMax*0.75, fakeNodeHeight)
|
||||
this.moveNode(nid, x, y, orientation, tween)
|
||||
y += gapy + 10 //TODO
|
||||
y += gapy + fakeNodeHeight
|
||||
}
|
||||
}
|
||||
x += wMax + gapx
|
||||
}
|
||||
} else if(orientation=='vertical'){
|
||||
const fakeNodeWidth = 10
|
||||
let y = gapy
|
||||
for(const [idx, layer] of layers.entries()){
|
||||
let hMax = this.getMaxHeight(layer)
|
||||
@@ -432,9 +478,9 @@ testEl.classList.add('eic')
|
||||
this.moveNode(nid, x, y, orientation, tween)
|
||||
x += gapx + bb.width
|
||||
} else {
|
||||
this.addFakeNode(nid, x, y, 10, hMax*0.75)
|
||||
this.addFakeNode(nid, x, y, fakeNodeWidth, hMax*0.75)
|
||||
this.moveNode(nid, x, y, orientation, tween)
|
||||
x += gapx + 10 //TODO
|
||||
x += gapx + fakeNodeWidth
|
||||
}
|
||||
}
|
||||
y += hMax + gapy
|
||||
@@ -461,12 +507,11 @@ testEl.classList.add('eic')
|
||||
computePortOffsets(nid, orientation = 'horizontal'){
|
||||
const node = this.stagedNodes[nid]
|
||||
if(!node || !node.ports) return({})
|
||||
const axis = (orientation === 'vertical') ? 'x' : 'y'
|
||||
const nodeRect = node.getBoundingClientRect()
|
||||
const ports = Object.entries(node.ports)
|
||||
.map(([pid, p]) => {
|
||||
const r = p.el.getBoundingClientRect()
|
||||
const pos = (axis === 'x')
|
||||
const pos = (orientation == 'vertical')
|
||||
? (r.left + (r.width / 2) - nodeRect.left)
|
||||
: (r.top + (r.height / 2) - nodeRect.top)
|
||||
return({ pid, pos })
|
||||
@@ -498,12 +543,10 @@ testEl.classList.add('eic')
|
||||
const toSwap = []
|
||||
for(let i=0; i<layer.length; i++){
|
||||
const nid1 = layer[i]
|
||||
if(nid1.startsWith('longLinkPlaceHolder_')) continue
|
||||
// some parents can be in far layers, but at least one is in the prev layer (by definition of layer)
|
||||
const pnid1 = parents[nid1].find((nid => layers[lidx-1].includes(nid)))
|
||||
for(let j=i+1; j<layer.length; j++){
|
||||
const nid2 = layer[j]
|
||||
if(nid2.startsWith('longLinkPlaceHolder_')) continue
|
||||
const pnid2 = parents[nid2].find((nid => layers[lidx-1].includes(nid)))
|
||||
const link1 = (pnid1) ? this.getLink(pnid1, nid1) : null
|
||||
const link2 = (pnid2) ? this.getLink(pnid2, nid2) : null
|
||||
@@ -511,12 +554,18 @@ testEl.classList.add('eic')
|
||||
const p2 = adjIndex(pnid2, link2?.from?.[1])
|
||||
const c1 = adjIndex(nid1, link1?.to?.[1])
|
||||
const c2 = adjIndex(nid2, link2?.to?.[1])
|
||||
|
||||
if(((p1 - p2) * (c1 - c2)) < 0) { // crossing (now refined by per-port ordering)
|
||||
toSwap.push([i, j])
|
||||
}
|
||||
}
|
||||
}
|
||||
swap(layer, toSwap)
|
||||
// Keep bases in sync with the new order so later layers use the updated positions
|
||||
for(const [idx, nid] of layer.entries()){
|
||||
if(!indexes[nid]) indexes[nid] = { base: idx, ports: {} }
|
||||
else indexes[nid].base = idx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,7 +610,11 @@ testEl.classList.add('eic')
|
||||
|
||||
|
||||
getLink(nid1, nid2){
|
||||
return(this.flow.links.find(item => ((item.from[0]==nid1) && (item.to[0]==nid2))))
|
||||
const real = this.flow.links.find(item => ((item.from[0]==nid1) && (item.to[0]==nid2)))
|
||||
if(real) return(real)
|
||||
const v = this._virtualLinks?.get(`${nid1}__${nid2}`)
|
||||
if(v) return(v)
|
||||
return(null)
|
||||
}
|
||||
|
||||
buildGraphStructures(nodes, links, includeLinkIndexes = false) {
|
||||
|
||||
Reference in New Issue
Block a user