graflow: added icmp example & fixed grand-slam long-links
This commit is contained in:
@@ -161,14 +161,14 @@
|
|||||||
<template id="svg-arrows">
|
<template id="svg-arrows">
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="arrow"
|
<marker id="arrow"
|
||||||
viewBox="0 0 15 15"
|
viewBox="0 0 10 10"
|
||||||
refX="15"
|
refX="10"
|
||||||
refY="7"
|
refY="5"
|
||||||
markerWidth="15"
|
markerWidth="10"
|
||||||
markerHeight="15"
|
markerHeight="10"
|
||||||
orient="auto-start-reverse"
|
orient="auto-start-reverse"
|
||||||
markerUnits="userSpaceOnUse">
|
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>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
}
|
}
|
||||||
bz-graflow{
|
bz-graflow{
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
border: 2px solid black;
|
||||||
}
|
}
|
||||||
bz-graflow.compunet{ grid-column: 1 / -1; width: 80vw; height: 40vh; background: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); }
|
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) }
|
(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',
|
document.querySelector('[data-id="compunet"]').addEventListener('change',
|
||||||
(evt) => { grflw1.setAttribute('tension', evt.target.value); grflw1.refresh() }
|
(evt) => { grflw1.setAttribute('tension', evt.target.value); grflw1.refresh() }
|
||||||
)
|
)
|
||||||
@@ -86,12 +95,16 @@
|
|||||||
document.querySelector('[data-id="organi"]').addEventListener('change',
|
document.querySelector('[data-id="organi"]').addEventListener('change',
|
||||||
(evt) => { grflw3.setAttribute('tension', evt.target.value); grflw3.refresh() }
|
(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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<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 -->
|
<div class="demooptions"> <!-- just for demo purposes -->
|
||||||
<button data-trigger="onAutoplace1H">Auto-place Horizontal</button>
|
<button data-trigger="onAutoplace1H">Auto-place Horizontal</button>
|
||||||
<button data-trigger="onAutoplace1V">Auto-place Vertical</button>
|
<button data-trigger="onAutoplace1V">Auto-place Vertical</button>
|
||||||
@@ -99,7 +112,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</bz-graflow>
|
</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 -->
|
<div class="demooptions"> <!-- just for demo purposes -->
|
||||||
<button data-trigger="onAutoplace2H">Auto-place Horizontal</button>
|
<button data-trigger="onAutoplace2H">Auto-place Horizontal</button>
|
||||||
<button data-trigger="onAutoplace2V">Auto-place Vertical</button>
|
<button data-trigger="onAutoplace2V">Auto-place Vertical</button>
|
||||||
@@ -107,13 +120,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</bz-graflow>
|
</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">
|
<div class="demooptions">
|
||||||
<button data-trigger="onAutoplace3H">Auto-place Horizontal</button>
|
<button data-trigger="onAutoplace3H">Auto-place Horizontal</button>
|
||||||
<button data-trigger="onAutoplace3V">Auto-place Vertical</button>
|
<button data-trigger="onAutoplace3V">Auto-place Vertical</button>
|
||||||
<div class-"cols-2"=""><label>tension</label><input data-id="organi" type="number" size="2" value="60"></div>
|
<div class-"cols-2"=""><label>tension</label><input data-id="organi" type="number" size="2" value="60"></div>
|
||||||
</div>
|
</div>
|
||||||
</bz-graflow>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"coords": { "x": 220, "y": 120}
|
"coords": { "x": 220, "y": 120}
|
||||||
},
|
},
|
||||||
{ "nodeType": "inc",
|
{ "nodeType": "inc",
|
||||||
"subflow": "/app/assets/json/bzGraflow/testSubFlow1.json",
|
"subflow": "/app/assets/json/bzGraflow/testFlowEic.json",
|
||||||
"portLinks": [
|
"portLinks": [
|
||||||
{ "parentPort": ["in1"], "subflowPort": ["inp1"] },
|
{ "parentPort": ["in1"], "subflowPort": ["inp1"] },
|
||||||
{ "parentPort": ["out1"], "subflowPort": ["out1"] }
|
{ "parentPort": ["out1"], "subflowPort": ["out1"] }
|
||||||
|
|||||||
@@ -2,36 +2,36 @@
|
|||||||
"nodesFile": "/app/assets/html/bzGraflow/nodesTest2.html",
|
"nodesFile": "/app/assets/html/bzGraflow/nodesTest2.html",
|
||||||
"flow": {
|
"flow": {
|
||||||
"nodes":[
|
"nodes":[
|
||||||
{ "nodeType": "start",
|
|
||||||
"id": "aze",
|
|
||||||
"coords": { "x": 220, "y": 20},
|
|
||||||
"markup": { "text": "Start" }
|
|
||||||
},
|
|
||||||
{ "nodeType": "process",
|
{ "nodeType": "process",
|
||||||
"id": "aze2",
|
"id": "aze2",
|
||||||
"coords": { "x": 220, "y": 120},
|
"xcoords": { "x": 220, "y": 120},
|
||||||
"markup": { "text": "x = alph - 1" }
|
"markup": { "text": "x = alph - 1" }
|
||||||
},
|
},
|
||||||
{ "nodeType": "condition",
|
{ "nodeType": "condition",
|
||||||
"id": "qsd",
|
"id": "qsd",
|
||||||
"coords": { "x": 250, "y": 270},
|
"xcoords": { "x": 250, "y": 270},
|
||||||
"markup": { "text": "x > 0" }
|
"markup": { "text": "x > 0" }
|
||||||
},
|
},
|
||||||
{ "nodeType": "preparation",
|
{ "nodeType": "preparation",
|
||||||
"id": "qsd2",
|
"id": "qsd2",
|
||||||
"coords": { "x": 250, "y": 470},
|
"xcoords": { "x": 250, "y": 470},
|
||||||
"markup": { "text": "prepare SQL" }
|
"markup": { "text": "prepare SQL" }
|
||||||
},
|
},
|
||||||
{ "nodeType": "database",
|
{ "nodeType": "database",
|
||||||
"id": "wcx",
|
"id": "wcx",
|
||||||
"coords": { "x": 500, "y": 450},
|
"xcoords": { "x": 500, "y": 450},
|
||||||
"markup": { "text": "MySQL<br>Store" }
|
"markup": { "text": "MySQL<br>Store" }
|
||||||
},
|
},
|
||||||
{ "nodeType": "end",
|
{ "nodeType": "end",
|
||||||
"id": "ert",
|
"id": "ert",
|
||||||
"coords": { "x": 250, "y": 650},
|
"xcoords": { "x": 250, "y": 650},
|
||||||
"markup": { "text": "End" }
|
"markup": { "text": "End" }
|
||||||
}
|
},
|
||||||
|
{ "nodeType": "start",
|
||||||
|
"id": "aze",
|
||||||
|
"xcoords": { "x": 220, "y": 20},
|
||||||
|
"markup": { "text": "StartMike" }
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{ "from": ["aze", "out1"], "to": ["aze2", "inout1"], "endArrow":true },
|
{ "from": ["aze", "out1"], "to": ["aze2", "inout1"], "endArrow":true },
|
||||||
|
|||||||
@@ -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)
|
// Now load styles (once)
|
||||||
if(!BZgraflow._loadedNodeStyles.has(url)) {
|
if(!BZgraflow._loadedNodeStyles.has(url) || this.attributes.isolated) {
|
||||||
const styles = doc.querySelectorAll('style')
|
const styles = doc.querySelectorAll('style')
|
||||||
styles.forEach(styleEl => {
|
styles.forEach(styleEl => {
|
||||||
const style = document.createElement('style')
|
const style = document.createElement('style')
|
||||||
@@ -151,39 +151,65 @@ class BZgraflow extends Buildoz{
|
|||||||
}
|
}
|
||||||
|
|
||||||
zoomIn(id){
|
zoomIn(id){
|
||||||
console.log('==============================>ZOOM IN:', id)
|
const nodeEl = this.stagedNodes[id]
|
||||||
const node = this.stagedNodes[id]
|
if(!nodeEl) return
|
||||||
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')
|
|
||||||
|
|
||||||
this.Invade(this, testEl)
|
// Create the child graflow first, place it above (foreground) the current graflow,
|
||||||
node.classList.remove('scaler')
|
// 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()
|
const r = oldEl.getBoundingClientRect()
|
||||||
oldEl.style.visibility = 'hidden'
|
if(hideOld) oldEl.style.visibility = 'hidden'
|
||||||
newEl.style.position = 'fixed'
|
newEl.style.position = 'fixed'
|
||||||
newEl.style.left = r.left + 'px'
|
newEl.style.left = r.left + 'px'
|
||||||
newEl.style.top = r.top + 'px'
|
newEl.style.top = r.top + 'px'
|
||||||
newEl.style.width = r.width + 'px'
|
newEl.style.width = r.width + 'px'
|
||||||
newEl.style.height = r.height + 'px'
|
newEl.style.height = r.height + 'px'
|
||||||
|
newEl.style.display = 'block'
|
||||||
oldEl.parentNode.appendChild(newEl)
|
oldEl.parentNode.appendChild(newEl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,21 +415,40 @@ testEl.classList.add('eic')
|
|||||||
layerWidths.push(totWidth)
|
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
|
// If any long-links, create placeholders for skipped layers
|
||||||
this.flow.longLinks = this.findLongLinks(this.flow.links)
|
this.flow.longLinks = this.findLongLinks(this.flow.links)
|
||||||
for(const link of this.flow.longLinks){
|
for(const llink of this.flow.longLinks){
|
||||||
for(const layerIdx of link.skippedLayers){
|
let fakeParent = llink.link.from[0]
|
||||||
|
for(const layerIdx of llink.skippedLayers){
|
||||||
const nid = `longLinkPlaceHolder_${crypto.randomUUID()}`
|
const nid = `longLinkPlaceHolder_${crypto.randomUUID()}`
|
||||||
layers[layerIdx].push(nid)
|
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
|
// Reorder layers to avoid crossings thanks to indexes
|
||||||
this.reorderLayers(layers, parents, indexes, orientation)
|
this.reorderLayers(layers, parents, indexes, orientation)
|
||||||
|
delete this._virtualLinks
|
||||||
|
|
||||||
// Finally place everything
|
// Finally place everything
|
||||||
if(orientation=='horizontal'){
|
if(orientation=='horizontal'){
|
||||||
|
const fakeNodeHeight = 10
|
||||||
let x = gapx
|
let x = gapx
|
||||||
for(const [idx, layer] of layers.entries()){
|
for(const [idx, layer] of layers.entries()){
|
||||||
let wMax = this.getMaxWidth(layer)
|
let wMax = this.getMaxWidth(layer)
|
||||||
@@ -414,14 +459,15 @@ testEl.classList.add('eic')
|
|||||||
this.moveNode(nid, x, y, orientation, tween)
|
this.moveNode(nid, x, y, orientation, tween)
|
||||||
y += gapy + bb.height
|
y += gapy + bb.height
|
||||||
} else {
|
} 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)
|
this.moveNode(nid, x, y, orientation, tween)
|
||||||
y += gapy + 10 //TODO
|
y += gapy + fakeNodeHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x += wMax + gapx
|
x += wMax + gapx
|
||||||
}
|
}
|
||||||
} else if(orientation=='vertical'){
|
} else if(orientation=='vertical'){
|
||||||
|
const fakeNodeWidth = 10
|
||||||
let y = gapy
|
let y = gapy
|
||||||
for(const [idx, layer] of layers.entries()){
|
for(const [idx, layer] of layers.entries()){
|
||||||
let hMax = this.getMaxHeight(layer)
|
let hMax = this.getMaxHeight(layer)
|
||||||
@@ -432,9 +478,9 @@ testEl.classList.add('eic')
|
|||||||
this.moveNode(nid, x, y, orientation, tween)
|
this.moveNode(nid, x, y, orientation, tween)
|
||||||
x += gapx + bb.width
|
x += gapx + bb.width
|
||||||
} else {
|
} 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)
|
this.moveNode(nid, x, y, orientation, tween)
|
||||||
x += gapx + 10 //TODO
|
x += gapx + fakeNodeWidth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
y += hMax + gapy
|
y += hMax + gapy
|
||||||
@@ -461,12 +507,11 @@ testEl.classList.add('eic')
|
|||||||
computePortOffsets(nid, orientation = 'horizontal'){
|
computePortOffsets(nid, orientation = 'horizontal'){
|
||||||
const node = this.stagedNodes[nid]
|
const node = this.stagedNodes[nid]
|
||||||
if(!node || !node.ports) return({})
|
if(!node || !node.ports) return({})
|
||||||
const axis = (orientation === 'vertical') ? 'x' : 'y'
|
|
||||||
const nodeRect = node.getBoundingClientRect()
|
const nodeRect = node.getBoundingClientRect()
|
||||||
const ports = Object.entries(node.ports)
|
const ports = Object.entries(node.ports)
|
||||||
.map(([pid, p]) => {
|
.map(([pid, p]) => {
|
||||||
const r = p.el.getBoundingClientRect()
|
const r = p.el.getBoundingClientRect()
|
||||||
const pos = (axis === 'x')
|
const pos = (orientation == 'vertical')
|
||||||
? (r.left + (r.width / 2) - nodeRect.left)
|
? (r.left + (r.width / 2) - nodeRect.left)
|
||||||
: (r.top + (r.height / 2) - nodeRect.top)
|
: (r.top + (r.height / 2) - nodeRect.top)
|
||||||
return({ pid, pos })
|
return({ pid, pos })
|
||||||
@@ -498,12 +543,10 @@ testEl.classList.add('eic')
|
|||||||
const toSwap = []
|
const toSwap = []
|
||||||
for(let i=0; i<layer.length; i++){
|
for(let i=0; i<layer.length; i++){
|
||||||
const nid1 = layer[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)
|
// 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)))
|
const pnid1 = parents[nid1].find((nid => layers[lidx-1].includes(nid)))
|
||||||
for(let j=i+1; j<layer.length; j++){
|
for(let j=i+1; j<layer.length; j++){
|
||||||
const nid2 = layer[j]
|
const nid2 = layer[j]
|
||||||
if(nid2.startsWith('longLinkPlaceHolder_')) continue
|
|
||||||
const pnid2 = parents[nid2].find((nid => layers[lidx-1].includes(nid)))
|
const pnid2 = parents[nid2].find((nid => layers[lidx-1].includes(nid)))
|
||||||
const link1 = (pnid1) ? this.getLink(pnid1, nid1) : null
|
const link1 = (pnid1) ? this.getLink(pnid1, nid1) : null
|
||||||
const link2 = (pnid2) ? this.getLink(pnid2, nid2) : 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 p2 = adjIndex(pnid2, link2?.from?.[1])
|
||||||
const c1 = adjIndex(nid1, link1?.to?.[1])
|
const c1 = adjIndex(nid1, link1?.to?.[1])
|
||||||
const c2 = adjIndex(nid2, link2?.to?.[1])
|
const c2 = adjIndex(nid2, link2?.to?.[1])
|
||||||
|
|
||||||
if(((p1 - p2) * (c1 - c2)) < 0) { // crossing (now refined by per-port ordering)
|
if(((p1 - p2) * (c1 - c2)) < 0) { // crossing (now refined by per-port ordering)
|
||||||
toSwap.push([i, j])
|
toSwap.push([i, j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
swap(layer, toSwap)
|
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){
|
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) {
|
buildGraphStructures(nodes, links, includeLinkIndexes = false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user