graflow, autoplacement 1.0 no layer-ordering
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
border-radius: 6px;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
.bzgf-node .body span{ font-size: 12px; text-align: left; line-height: 21px; }
|
||||
.bzgf-node .port{
|
||||
position: absolute;
|
||||
height: 10px;
|
||||
@@ -48,38 +49,124 @@
|
||||
}
|
||||
.bzgf-node[data-nodetype="inc"] .title{ background: #090; }
|
||||
|
||||
|
||||
.bzgf-node[data-nodetype="wadder"]{
|
||||
background: #DFD;
|
||||
border-color: #090;
|
||||
height:150px
|
||||
}
|
||||
.bzgf-node[data-nodetype="wadder"] .body{ display: grid; grid-gap: 4px; margin-left:0.5em; grid-template-columns: 1fr 1fr; align-items: center; }
|
||||
.bzgf-node[data-nodetype="wadder"] .title{ background: #090; }
|
||||
.bzgf-node[data-nodetype="wadder"] .port[data-id="inp1"] { top:37px; }
|
||||
.bzgf-node[data-nodetype="wadder"] .port[data-id="inp2"] { top:63px; }
|
||||
.bzgf-node[data-nodetype="wadder"] .port[data-id="inp3"] { top:89px; }
|
||||
.bzgf-node[data-nodetype="wadder"] .port[data-id="inp4"] { top:115px; }
|
||||
.bzgf-node[data-nodetype="wadder"] .port[data-id="inp5"] { top:141px; }
|
||||
|
||||
.bzgf-node[data-nodetype="factor"]{
|
||||
background: #DDF;
|
||||
border-color: #009;
|
||||
}
|
||||
.bzgf-node[data-nodetype="factor"] .title{ background: #009; }
|
||||
|
||||
.bzgf-wire{
|
||||
stroke: #0AF;
|
||||
stroke-width: 2;
|
||||
.bzgf-node[data-nodetype="multiplier"]{
|
||||
background: #DDF;
|
||||
border-color: #009;
|
||||
height:110px
|
||||
}
|
||||
.bzgf-node[data-nodetype="multiplier"] .body{
|
||||
font-size:40px;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.bzgf-node[data-nodetype="multiplier"] .title{ background: #009; }
|
||||
.bzgf-node[data-nodetype="multiplier"] .port[data-id="inp1"] { top:37px; }
|
||||
.bzgf-node[data-nodetype="multiplier"] .port[data-id="inp2"] { top:63px; }
|
||||
.bzgf-node[data-nodetype="multiplier"] .port[data-id="inp3"] { top:89px; }
|
||||
|
||||
.bzgf-node[data-nodetype="input"],
|
||||
.bzgf-node[data-nodetype="console"]{
|
||||
background: #CCC;
|
||||
border-color: #555;
|
||||
}
|
||||
.bzgf-node[data-nodetype="input"] .title,
|
||||
.bzgf-node[data-nodetype="console"] .title{ background: #555; }
|
||||
|
||||
.bzgf-wire{ stroke: #0AF; stroke-width: 2; }
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="bzgf-node" data-nodetype="inc">
|
||||
<div class="title">Increment</div>
|
||||
<div class="title">Fixed Increment</div>
|
||||
<div class="port" data-type="in" data-id="inp1" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp2" data-direction="s"></div>
|
||||
<div class="port" data-type="out" data-id="out1" data-direction="e"></div>
|
||||
<div class="body">
|
||||
<input type="text" value="1">
|
||||
<label>Increment:</label><input name="incvalue" type="text" value="1">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<div class="bzgf-node" data-nodetype="factor">
|
||||
<div class="title">Multiply</div>
|
||||
<div class="title">Fixed factor</div>
|
||||
<div class="port" data-type="in" data-id="inp1" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp2" data-direction="s"></div>
|
||||
<div class="port" data-type="out" data-id="out1" data-direction="e"></div>
|
||||
<div class="body">
|
||||
<input type="text" value="0.5">
|
||||
<label>Factor:</label><input name="factvalue" type="text" value="0.5">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<div class="bzgf-node" data-nodetype="wadder">
|
||||
<div class="title">Adder</div>
|
||||
<div class="port" data-type="in" data-id="inp1" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp2" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp3" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp4" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp5" data-direction="w"></div>
|
||||
<div class="port" data-type="out" data-id="out1" data-direction="e"></div>
|
||||
<div class="body">
|
||||
<div>
|
||||
<span>x <input type="text" name="weight1" value="1"></span>
|
||||
<span>x <input type="text" name="weight2" value="1"></span>
|
||||
<span>x <input type="text" name="weight3" value="1"></span>
|
||||
<span>x <input type="text" name="weight4" value="1"></span>
|
||||
<span>x <input type="text" name="weight5" value="1"></span>
|
||||
</div>
|
||||
<div style="font-size:40px;font-weight: bold;transform: translateY(-50%);">∑</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<div class="bzgf-node" data-nodetype="multiplier">
|
||||
<div class="title">Multiply</div>
|
||||
<div class="port" data-type="in" data-id="inp1" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp2" data-direction="w"></div>
|
||||
<div class="port" data-type="in" data-id="inp3" data-direction="w"></div>
|
||||
<div class="port" data-type="out" data-id="out1" data-direction="e"></div>
|
||||
<div class="body">x</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<div class="bzgf-node" data-nodetype="console">
|
||||
<div class="title">Console</div>
|
||||
<div class="port" data-type="in" data-id="inp1" data-direction="w"></div>
|
||||
<div class="body"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<div class="bzgf-node" data-nodetype="input">
|
||||
<div class="title">input</div>
|
||||
<div class="port" data-type="out" data-id="out1" data-direction="e"></div>
|
||||
<div class="body">
|
||||
<label>Value:</label><input name="value" type="text" value="1">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -12,6 +12,6 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<bz-graflow flow="/app/assets/json/bzGraflow/testFlow1.json"></bz-graflow>
|
||||
<bz-graflow flow="/app/assets/json/bzGraflow/testFlow1.json" tension="60"></bz-graflow>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,27 +4,47 @@
|
||||
"nodes":[
|
||||
{ "nodeType": "inc",
|
||||
"id": "aze",
|
||||
"coords": { "x": 20, "y": 10}
|
||||
"coords": { "x": 220, "y": 10}
|
||||
},
|
||||
{ "nodeType": "inc",
|
||||
"id": "aze2",
|
||||
"coords": { "x": 220, "y": 120}
|
||||
},
|
||||
{ "nodeType": "factor",
|
||||
"id": "qsd2",
|
||||
"coords": { "x": 470, "y": 170}
|
||||
},
|
||||
{ "nodeType": "factor",
|
||||
"id": "qsd",
|
||||
"coords": { "x": 270, "y": 50}
|
||||
"coords": { "x": 470, "y": 50}
|
||||
},
|
||||
{ "nodeType": "inc",
|
||||
{ "nodeType": "wadder",
|
||||
"id": "wcx",
|
||||
"coords": { "x": 520, "y": 50}
|
||||
"coords": { "x": 720, "y": 50}
|
||||
},
|
||||
{ "nodeType": "inc",
|
||||
{ "nodeType": "multiplier",
|
||||
"id": "ert",
|
||||
"coords": { "x": 150, "y": 350}
|
||||
"coords": { "x": 550, "y": 350}
|
||||
},
|
||||
{ "nodeType": "input",
|
||||
"id": "0000",
|
||||
"coords": { "x": 20, "y": 350}
|
||||
},
|
||||
{ "nodeType": "console",
|
||||
"id": "9999",
|
||||
"coords": { "x": 800, "y": 350}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{ "from": ["0000", "out1"], "to": ["aze", "inp1"] },
|
||||
{ "from": ["aze2", "out1"], "to": ["qsd2", "inp1"] },
|
||||
{ "from": ["aze", "out1"], "to": ["qsd", "inp1"] },
|
||||
{ "from": ["0000", "out1"], "to": ["aze2", "inp1"] },
|
||||
{ "from": ["qsd2", "out1"], "to": ["wcx", "inp2"] },
|
||||
{ "from": ["wcx", "out1"], "to": ["ert", "inp1"] },
|
||||
{ "from": ["qsd2", "out1"], "to": ["ert", "inp2"] },
|
||||
{ "from": ["qsd", "out1"], "to": ["wcx", "inp1"] },
|
||||
{ "from": ["qsd", "out1"], "to": ["ert", "inp2"] },
|
||||
{ "from": ["ert", "out1"], "to": ["aze", "inp2"] }
|
||||
|
||||
{ "from": ["ert", "out1"], "to": ["9999", "inp1"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,9 @@ body[eicapp] {
|
||||
height: 100vh;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
[eicapp] [eicapptoolbar] {
|
||||
@@ -144,7 +146,8 @@ menu[eicmenu] [menuitem] > a > button, menu[eicmenu] [menuitem] > .nolink button
|
||||
[eicapp] .app-workspace .window > header .controls button.shrink { display: none; }
|
||||
[eicapp] .app-workspace .window > section {
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
overflow: auto; /* important for windows content to be scrollable */
|
||||
transition: all 0.5s;
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
@@ -336,7 +339,7 @@ menu[eicmenu] [menuitem] a label {
|
||||
color: #4E4;
|
||||
}
|
||||
menu[eicmenu] [menuitem] i[class^="icon-"] {
|
||||
color:#fdfb93;;
|
||||
color:#070;;
|
||||
}
|
||||
article[eiccard] > header h1{ text-align: center; }
|
||||
|
||||
|
||||
Vendored
+1
@@ -202,3 +202,4 @@ bz-graflow .bzgf-nodes-container{
|
||||
}
|
||||
bz-graflow .bzgf-nodes-container{ z-index:10; }
|
||||
bz-graflow .bzgf-wires-container{ z-index:9; }
|
||||
bz-graflow .bzgf-nodes-container .bzgf-node{ position:absolute; }
|
||||
Vendored
+156
-36
@@ -2,7 +2,7 @@ class BZgraflow extends Buildoz{
|
||||
|
||||
constructor(){
|
||||
super()
|
||||
this.defaultAttrs = { }
|
||||
this.defaultAttrs = { tension: 100 }
|
||||
this.stagedNodes = { }
|
||||
this.stagedWires = { }
|
||||
}
|
||||
@@ -15,9 +15,7 @@ class BZgraflow extends Buildoz{
|
||||
console.warn('BZgraflow: No flow URL !?')
|
||||
return
|
||||
}
|
||||
|
||||
this.loadFlow(flowUrl) // Let it load async while we coat
|
||||
|
||||
this.nodesContainer = document.createElement('div')
|
||||
this.nodesContainer.classList.add('bzgf-nodes-container')
|
||||
this.wiresContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
@@ -42,6 +40,8 @@ class BZgraflow extends Buildoz{
|
||||
}
|
||||
await this.loadNodes(flowObj.nodesFile)
|
||||
this.flow = flowObj.flow
|
||||
|
||||
if(this.hasAnyLoop()) console.warn('This flow has loops.Optimization not available')
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
@@ -73,31 +73,20 @@ class BZgraflow extends Buildoz{
|
||||
addNode(type, id, x, y){
|
||||
const nodeDef = this.nodesRegistry[type]
|
||||
this.stagedNodes[id] = nodeDef.cloneNode(true)
|
||||
const portEls = this.stagedNodes[id].querySelectorAll('.port')
|
||||
this.stagedNodes[id].style.left = `${x}px`
|
||||
this.stagedNodes[id].style.top = `${y}px`
|
||||
this.stagedNodes[id].dataset.id = id
|
||||
const portEls = this.stagedNodes[id].querySelectorAll('.port')
|
||||
this.stagedNodes[id].ports = Object.fromEntries(Array.from(portEls).map(item => ([item.dataset.id, { ...item.dataset, el:item }])))
|
||||
this.nodesContainer.append(this.stagedNodes[id])
|
||||
return(this.stagedNodes[id])
|
||||
}
|
||||
|
||||
addWire(idNode1, idPort1, idNode2, idPort2){
|
||||
const node1 = this.stagedNodes[idNode1]
|
||||
const port1 = node1.ports[idPort1]
|
||||
const node2 = this.stagedNodes[idNode2]
|
||||
const port2 = node2.ports[idPort2]
|
||||
const id = `${node1.dataset.id}_${node2.dataset.id}`
|
||||
const bb1 = port1.el.getBoundingClientRect()
|
||||
const bb2 = port2.el.getBoundingClientRect()
|
||||
const x1 = Math.floor(bb1.x + (bb1.width/2))
|
||||
const y1 = Math.floor(bb1.y + (bb1.height/2))
|
||||
const x2 = Math.floor(bb2.x + (bb2.width/2))
|
||||
const y2 = Math.floor(bb2.y + (bb2.height/2))
|
||||
|
||||
console.log('====>', x1, y1, x2, y2)
|
||||
const path = this.bezier(idNode1, idPort1, idNode2, idPort2, this.getBZAttribute('tension'))
|
||||
const id = `${idNode1}_${idNode2}`
|
||||
this.stagedWires[id] = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
||||
this.stagedWires[id].setAttribute('d', this.bezier(x1, y1, port1.direction , x2, y2, port2.direction, 60))
|
||||
this.stagedWires[id].setAttribute('d', path)
|
||||
this.stagedWires[id].setAttribute('fill', 'none')
|
||||
this.stagedWires[id].classList.add('bzgf-wire')
|
||||
this.stagedWires[id].dataset.id = id
|
||||
@@ -118,40 +107,171 @@ class BZgraflow extends Buildoz{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bezier(x1, y1, dir1, x2, y2, dir2, tensionMin=60) {
|
||||
bezier(idNode1, idPort1, idNode2, idPort2, tensionMin=60) {
|
||||
const dirVect = {
|
||||
n: { x: 0, y: -1 },
|
||||
s: { x: 0, y: 1 },
|
||||
e: { x: 1, y: 0 },
|
||||
w: { x: -1, y: 0 },
|
||||
}
|
||||
const node1 = this.stagedNodes[idNode1]
|
||||
const port1 = node1.ports[idPort1]
|
||||
const node2 = this.stagedNodes[idNode2]
|
||||
const port2 = node2.ports[idPort2]
|
||||
const bb1 = port1.el.getBoundingClientRect()
|
||||
const bb2 = port2.el.getBoundingClientRect()
|
||||
const x1 = Math.floor(bb1.x + (bb1.width/2))
|
||||
const y1 = Math.floor(bb1.y + (bb1.height/2))
|
||||
const x2 = Math.floor(bb2.x + (bb2.width/2))
|
||||
const y2 = Math.floor(bb2.y + (bb2.height/2))
|
||||
const dist = Math.abs(x2 - x1) + Math.abs(y2 - y1)
|
||||
let tension = dist * 0.4
|
||||
if (tension < tensionMin) tension = tensionMin
|
||||
if(tension < tensionMin) tension = tensionMin
|
||||
|
||||
const c1x = x1 + (dirVect[dir1].x * tension)
|
||||
const c1y = y1 + (dirVect[dir1].y * tension)
|
||||
const c1x = x1 + (dirVect[port1.direction].x * tension)
|
||||
const c1y = y1 + (dirVect[port1.direction].y * tension)
|
||||
|
||||
const c2x = x2 + (dirVect[dir2].x * tension)
|
||||
const c2y = y2 + (dirVect[dir2].y * tension)
|
||||
const c2x = x2 + (dirVect[port2.direction].x * tension)
|
||||
const c2y = y2 + (dirVect[port2.direction].y * tension)
|
||||
return `M ${x1} ${y1} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x2} ${y2}`
|
||||
}
|
||||
|
||||
autoPlace(orientation = 'horizontal', gapx = 80, gapy = 30){
|
||||
if(!['horizontal', 'vertical'].includes(orientation)) return
|
||||
|
||||
/*
|
||||
portPosition(nodeEl, portEl) {
|
||||
const nodeRect = nodeEl.getBoundingClientRect()
|
||||
const portRect = portEl.getBoundingClientRect()
|
||||
const canvasRect = this.canvas.getBoundingClientRect()
|
||||
const parents = {}
|
||||
const adj = {}
|
||||
|
||||
return {
|
||||
x: portRect.left - canvasRect.left + portRect.width / 2,
|
||||
y: portRect.top - canvasRect.top + portRect.height / 2
|
||||
}
|
||||
}
|
||||
this.flow.nodes.forEach(n => {
|
||||
parents[n.id] = []
|
||||
adj[n.id] = []
|
||||
})
|
||||
|
||||
*/
|
||||
this.flow.links.forEach(link => {
|
||||
const from = link.from[0]
|
||||
const to = link.to[0]
|
||||
parents[to].push(from)
|
||||
adj[from].push(to)
|
||||
})
|
||||
|
||||
const layers = this.computeLayers(this.flow.nodes, parents)
|
||||
// const maxLayers = Math.max(...layers.map(item=>item.length))
|
||||
let maxHeight = 0
|
||||
const layerHeights = []
|
||||
for(const layer of layers){
|
||||
let totHeight = 0
|
||||
for(const nid of layer){
|
||||
const bb = this.stagedNodes[nid].getBoundingClientRect()
|
||||
totHeight += bb.height+gapy
|
||||
}
|
||||
if(totHeight>maxHeight) maxHeight = totHeight
|
||||
layerHeights.push(totHeight)
|
||||
}
|
||||
|
||||
let x = gapx
|
||||
for(const [idx, layer] of layers.entries()){
|
||||
for(const nid of layer){
|
||||
// some parents can be in far layers, but at least one is in the prev layer (by definition of layer)
|
||||
const localParent = parents[nid].find((nid => layers[idx-1].includes(nid)))
|
||||
if(localParent){ //undefined for 1st node
|
||||
const ports = this.stagedNodes[localParent].ports
|
||||
console.log(`parent of ${nid} is ${localParent}`, ports)
|
||||
}
|
||||
}
|
||||
|
||||
let wMax = 0
|
||||
let y = ((maxHeight - layerHeights[idx]) / 2) + gapy
|
||||
for(const nid of layer){
|
||||
const bb = this.stagedNodes[nid].getBoundingClientRect()
|
||||
wMax = (bb.width > wMax) ? bb.width : wMax
|
||||
this.moveNode(nid, x, y, 1000)
|
||||
y += gapy + bb.height
|
||||
}
|
||||
x += wMax + gapx
|
||||
}
|
||||
}
|
||||
|
||||
moveNode(nid, destx, desty, duration = 200) {
|
||||
const t0 = performance.now()
|
||||
const bb = this.stagedNodes[nid].getBoundingClientRect()
|
||||
const x0=bb.x
|
||||
const y0 = bb.y
|
||||
function frame(t) {
|
||||
const p = Math.min((t - t0) / duration, 1)
|
||||
const k = p * p * (3 - 2 * p) // smoothstep
|
||||
const x = x0 + (destx - x0) * k
|
||||
const y = y0 + (desty - y0) * k
|
||||
|
||||
this.stagedNodes[nid].style.left = `${x}px`
|
||||
this.stagedNodes[nid].style.top = `${y}px`
|
||||
this.updateWires(nid)
|
||||
|
||||
if(p < 1) requestAnimationFrame(frame.bind(this))
|
||||
}
|
||||
requestAnimationFrame(frame.bind(this))
|
||||
}
|
||||
|
||||
updateWires(nid){
|
||||
const wires = Object.keys(this.stagedWires)
|
||||
.filter(id => (id.startsWith(nid+'_')||id.endsWith('_'+nid)))
|
||||
.map(id => this.stagedWires[id])
|
||||
for(const wire of wires){
|
||||
const [nid1, nid2] = wire.dataset.id.split('_')
|
||||
const lnk = this.getLink(nid1, nid2)
|
||||
const path = this.bezier(nid1, lnk.from[1], nid2, lnk.to[1], this.getBZAttribute('tension'))
|
||||
wire.setAttribute('d', path)
|
||||
}
|
||||
}
|
||||
|
||||
getLink(nid1, nid2){
|
||||
return(this.flow.links.find(item => ((item.from[0]==nid1) && (item.to[0]==nid2))))
|
||||
}
|
||||
|
||||
computeLayers(nodes, parents) {
|
||||
const layer = {}
|
||||
const dfs = (id) => {
|
||||
if (layer[id] !== undefined) return(layer[id])
|
||||
|
||||
if (parents[id].length === 0) {
|
||||
layer[id] = 0
|
||||
} else {
|
||||
layer[id] = 1 + Math.max(...parents[id].map(dfs))
|
||||
}
|
||||
return(layer[id])
|
||||
}
|
||||
nodes.forEach(n => dfs(n.id))
|
||||
const t = []
|
||||
for(const nid in layer) {
|
||||
if(!t[layer[nid]]) t[layer[nid]]=[]
|
||||
t[layer[nid]].push(nid)
|
||||
}
|
||||
return(t)
|
||||
}
|
||||
|
||||
hasAnyLoop(nodes, links) {
|
||||
// self-loops
|
||||
if(this.flow.links.some(l => l.from[0] === l.to[0])) return(true)
|
||||
|
||||
// multi-node cycles
|
||||
const adj = {};
|
||||
this.flow.nodes.forEach(node => adj[node.id] = [])
|
||||
this.flow.links.forEach(link => adj[link.from[0]].push(link.to[0]))
|
||||
|
||||
const visiting = new Set();
|
||||
const visited = new Set()
|
||||
const dfs = (nid) => {
|
||||
if(visiting.has(nid)) return(true)
|
||||
if(visited.has(nid)) return(false)
|
||||
visiting.add(nid)
|
||||
for(const m of adj[nid]) {
|
||||
if(dfs(m)) return(true)
|
||||
}
|
||||
visiting.delete(nid)
|
||||
visited.add(nid)
|
||||
return(false)
|
||||
}
|
||||
return(this.flow.nodes.map(n => n.id).some(dfs))
|
||||
}
|
||||
|
||||
}
|
||||
Buildoz.define('graflow', BZgraflow)
|
||||
|
||||
Reference in New Issue
Block a user