longlinks fixed

This commit is contained in:
STEINNI
2026-01-24 18:24:17 +00:00
parent 13b863115e
commit ad86da6627
2 changed files with 142 additions and 44 deletions

View File

@@ -67,8 +67,7 @@ bz-select option:hover{
color: #FFF;
}
/************************************************************************************/
bz-toggler{
outline: none;
@@ -138,6 +137,8 @@ bz-toggler div.toggle-switch span.toggle-thumb.turned-on {
left : 1em;
}
/************************************************************************************/
bz-slidepane {
display: block;
position: absolute;
@@ -187,6 +188,9 @@ bz-slidepane[side="right"] div.handle {
transform: translateY(-50%);
cursor: ew-resize;
}
/************************************************************************************/
bz-graflow {
position: relative;
display: block;

View File

@@ -24,6 +24,13 @@ class BZgraflow extends Buildoz{
.bzgf-wires-container,
.bzgf-nodes-container{ position: absolute; inset: 0; width: 100%; height: 100%; }
.bzgf-nodes-container .bzgf-node{ position:absolute; }
.bzgf-nodes-container .bzgf-fake-node{
position: absolute;
width: 5px;
height: 5px;
backgrround: transparent;
border-style: none;
}
`
this.shadow.appendChild(style)
this.nodesContainer = document.createElement('div')
@@ -111,10 +118,22 @@ class BZgraflow extends Buildoz{
return(this.stagedNodes[id])
}
addFakeNode(nid, x, y, w, h){
this.stagedNodes[nid] = document.createElement('div')
this.stagedNodes[nid].classList.add('bzgf-fake-node')
this.stagedNodes[nid].style.left = `${x}px`
this.stagedNodes[nid].style.top = `${y}px`
this.stagedNodes[nid].style.width = `${w}px`
this.stagedNodes[nid].style.height = `${h}px`
this.stagedNodes[nid].dataset.id = nid
this.nodesContainer.append(this.stagedNodes[nid])
return(this.stagedNodes[nid])
}
addWire(link){
const [idNode1, idPort1] = link.from
const [idNode2, idPort2] = link.to
const path = this.bezier(idNode1, idPort1, idNode2, idPort2, this.getBZAttribute('tension'))
const path = this.bezierNodes(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', path)
@@ -153,25 +172,26 @@ class BZgraflow extends Buildoz{
}
}
bezier(idNode1, idPort1, idNode2, idPort2, tensionMin=60) {
bezierNodes(idNode1, idPort1, idNode2, idPort2, tensionMin=60) {
const svgRect = this.wiresContainer.getBoundingClientRect()
const node1 = this.stagedNodes[idNode1]
const port1 = idPort1 ? node1.ports[idPort1] : null
const port1 = node1.ports[idPort1]
const node2 = this.stagedNodes[idNode2]
const port2 = idPort2 ? node2.ports[idPort2] : null
if(!node1 || !node2) {
console.warn('Link on bad node ', idNode1, idNode2)
const port2 = node2.ports[idPort2]
if(!node1 || !node2 || !port1 || !port2) {
console.warn('Link on bad node / port ', idNode1, idPort1, idNode2, idPort2)
return('')
}
const bb1 = port1 ? port1.el.getBoundingClientRect() : node1.getBoundingClientRect()
const bb2 = port2 ? port2.el.getBoundingClientRect() : node2.getBoundingClientRect()
const bb1 = port1.el.getBoundingClientRect()
const bb2 = port2.el.getBoundingClientRect()
const x1 = Math.floor(bb1.x + (bb1.width/2)) - svgRect.left
const y1 = Math.floor(bb1.y + (bb1.height/2)) - svgRect.top
const x2 = Math.floor(bb2.x + (bb2.width/2)) - svgRect.left
const y2 = Math.floor(bb2.y + (bb2.height/2)) - svgRect.top
const loop = (idNode1==idNode2) && (idPort1==idPort2)
const dist = Math.abs(x2 - x1) + Math.abs(y2 - y1)
let tension = dist * 0.4
if(tension < tensionMin) tension = tensionMin
if(tension < tensionMin) tension = parseInt(tensionMin)
const dirVect = {
n: { x: 0, y: -1 },
@@ -183,7 +203,7 @@ class BZgraflow extends Buildoz{
let c1y = y1 + (dirVect[port1.direction].y * tension)
let c2x = x2 + (dirVect[port2.direction].x * tension)
let c2y = y2 + (dirVect[port2.direction].y * tension)
if((idNode1==idNode2) && (idPort1==idPort2)){ // Special case of self-loop: spread a bit intermediary points
if(loop){
if(['n', 's'].includes(port1.direction)) {
c1x += tension
c2x -= tension
@@ -196,7 +216,62 @@ class BZgraflow extends Buildoz{
return(`M ${x1} ${y1} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x2} ${y2}`)
}
bezierInterNodes(idNode1, idPort1, idNode2, idPort2, interNodes, orientation='horizontal', tensionMin=60) {
const svgRect = this.wiresContainer.getBoundingClientRect()
const makeCubicBezier = (x1, y1, x2, y2, orientation) => {
const dist = Math.abs(x2 - x1) + Math.abs(y2 - y1)
let tension = dist * 0.4
if(tension < tensionMin) tension = parseInt(tensionMin)
let c1x, c1y, c2x, c2y
if(orientation=='horizontal'){
c1x = x1 + tension
c1y = y1
c2x = x2 - tension
c2y = y2
} else {
c1x = x1
c1y = y1 + tension
c2x = x2
c2y = y2 - tension
}
return(`C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x2} ${y2}`)
}
const directPath = this.bezierNodes(idNode1, idPort1, idNode2, idPort2, tensionMin)
const startPath = directPath.substring(0,directPath.indexOf('C'))
const endPath = directPath.substring(directPath.lastIndexOf(',')+1).trim()
let path = startPath
let [ , x1, y1] = startPath.split(' ')
x1 = parseInt(x1)
y1 = parseInt(y1)
for(const interNode of interNodes){
const bb = this.stagedNodes[interNode].getBoundingClientRect()
let x2; let y2;
if(orientation=='horizontal'){
x2 = bb.x -svgRect.left
y2 =Math.floor(bb.y + (bb.height/2)) - svgRect.top
path += makeCubicBezier(x1, y1, x2, y2, orientation)
x2 += bb.width
path += ` L ${x2} ${y2} `
} else {
x2 = Math.floor(bb.x + (bb.width/2)) - svgRect.left
y2 = bb.y - svgRect.top
path += makeCubicBezier(x1, y1, x2, y2, orientation)
y2 += bb.height
path += ` L ${x2} ${y2} `
}
x1 = x2
y1 = y2
}
let [x2, y2] = endPath.split(' ')
x2 = parseInt(x2)
y2 = parseInt(y2)
path += ' '+makeCubicBezier(x1, y1, x2, y2, orientation)
return(path)
}
autoPlace(orientation = 'horizontal', gapx = 80, gapy = 80, tween=1000){
// Loops create infinite recursion in dfs for getting parents & adjacency lists: Remove them !
let linksWithoutBackEdges
if(this.hasAnyLoop(this.flow.nodes, this.flow.links)){
console.warn('Loop(s) detected... Cannot auto-place !')
@@ -205,10 +280,12 @@ class BZgraflow extends Buildoz{
} else {
linksWithoutBackEdges = this.flow.links
}
const { parents, adj } = this.buildGraphStructures(this.flow.nodes, linksWithoutBackEdges)
const layers = this.computeLayers(this.flow.nodes, parents)
// Compute indexes for each layer (int part) & add sub-index for ports
// Also compute max width/height for each layer
let maxHeight = 0; let maxWidth = 0
const layerHeights = []; const layerWidths = [];
const indexes = {} // indexes[nid] = { base: <int>, ports: { [portId]: <float in [0,1)> } }
@@ -226,34 +303,34 @@ class BZgraflow extends Buildoz{
layerWidths.push(totWidth)
}
// If any long-links, create placeholders for skipped layers
this.flow.longLinks = this.findLongLinks(this.flow.links)
console.log('==============================>longLinks:', this.flow.longLinks)
for(const link of this.flow.longLinks){
console.log('==============================>longLink:', link)
for(const layerIdx of link.skippedLayers){
const nid = `longLinkPlaceHolder_${crypto.randomUUID()}`
layers[layerIdx].push(nid)
link.interNodes.push(nid)
}
}
// Reorder layers to avoid crossings thanks to indexes
this.reorderLayers(layers, parents, indexes, orientation)
// Finally place everything
if(orientation=='horizontal'){
let x = gapx
for(const [idx, layer] of layers.entries()){
let wMax = 0
let wMax = this.getMaxWidth(layer)
let y = ((maxHeight - layerHeights[idx]) / 2) + gapy
for(const nid of layer){
if(!nid.startsWith('longLinkPlaceHolder_')){
if(!nid.startsWith('longLinkPlaceHolder_')) {
const bb = this.stagedNodes[nid].getBoundingClientRect()
wMax = (bb.width > wMax) ? bb.width : wMax
this.moveNode(nid, x, y, tween)
this.moveNode(nid, x, y, orientation, tween)
y += gapy + bb.height
} else {
console.log('longLinkPlaceHolder', layer)
// Maybe center x here (but we don't have full wmax yet)
this.moveNode(nid, x, y, tween)
y += gapy + 50 // TODO: fix this
this.addFakeNode(nid, x, y, wMax, 10)
this.moveNode(nid, x, y, orientation, tween)
y += gapy + 10 //TODO
}
}
x += wMax + gapx
@@ -261,19 +338,17 @@ class BZgraflow extends Buildoz{
} else if(orientation=='vertical'){
let y = gapy
for(const [idx, layer] of layers.entries()){
let hMax = 0
let hMax = this.getMaxHeight(layer)
let x = ((maxWidth - layerWidths[idx]) / 2) + gapx
for(const nid of layer){
if(!nid.startsWith('longLinkPlaceHolder_')){
const bb = this.stagedNodes[nid].getBoundingClientRect()
hMax = (bb.height > hMax) ? bb.height : hMax
this.moveNode(nid, x, y, tween)
this.moveNode(nid, x, y, orientation, tween)
x += gapx + bb.width
} else {
console.log('longLinkPlaceHolder', layer)
// Maybe center y here (but we don't have full hmax yet)
this.moveNode(nid, x, y, tween)
x += gapx + 50 // TODO: fix this
this.addFakeNode(nid, x, y, 10, hMax)
this.moveNode(nid, x, y, orientation, tween)
x += gapx + 10 //TODO
}
}
y += hMax + gapy
@@ -281,6 +356,22 @@ class BZgraflow extends Buildoz{
}
}
getMaxWidth(layer){
return(layer.filter(nid =>
!nid.startsWith('longLinkPlaceHolder_'))
.map(nid => this.stagedNodes[nid].getBoundingClientRect().width)
.reduce((a, b) => a > b ? a : b, 0)
)
}
getMaxHeight(layer){
return(layer.filter(nid =>
!nid.startsWith('longLinkPlaceHolder_'))
.map(nid => this.stagedNodes[nid].getBoundingClientRect().height)
.reduce((a, b) => a > b ? a : b, 0)
)
}
computePortOffsets(nid, orientation = 'horizontal'){
const node = this.stagedNodes[nid]
if(!node || !node.ports) return({})
@@ -343,7 +434,7 @@ class BZgraflow extends Buildoz{
}
}
moveNode(nid, destx, desty, duration = 200, cb) {
moveNode(nid, destx, desty, orientation, duration = 200, cb) {
const t0 = performance.now()
const bb = this.stagedNodes[nid].getBoundingClientRect()
const parentbb = this.stagedNodes[nid].parentElement.getBoundingClientRect()
@@ -356,14 +447,14 @@ class BZgraflow extends Buildoz{
const y = y0 + (desty - y0) * k
this.stagedNodes[nid].style.left = `${x}px`
this.stagedNodes[nid].style.top = `${y}px`
this.updateWires(nid)
this.updateWires(nid, orientation)
if(p < 1) requestAnimationFrame(frame.bind(this))
}
requestAnimationFrame(frame.bind(this))
}
updateWires(nid){
updateWires(nid, orientation){
const wires = Object.keys(this.stagedWires)
.filter(id => (id.startsWith(nid+'_')||id.endsWith('_'+nid)))
.map(id => this.stagedWires[id])
@@ -372,23 +463,17 @@ class BZgraflow extends Buildoz{
const lnk = this.getLink(nid1, nid2)
const longLink = this.flow.longLinks.find(item => (item.link.from[0] == lnk.from[0] && item.link.from[1] == lnk.from[1] && item.link.to[0] == lnk.to[0] && item.link.to[1] == lnk.to[1]))
if(longLink) {
console.log('==============================>WIRE:', wire)
let lastNid = nid1; let lastPort = lnk.from[1];
for(const interNode of longLink.interNodes){
const path = this.bezier(lastNid, lastPort, interNode, null, this.getBZAttribute('tension'))
wire.setAttribute('d', path)
// TODO: add/draw intermediary wire
lastNid = interNode; lastPort = null;
}
const path = this.bezier(lastNid, lastPort, nid2, lnk.to[1], this.getBZAttribute('tension'))
const path = this.bezierInterNodes(nid1, lnk.from[1], nid2, lnk.to[1], longLink.interNodes, orientation, this.getBZAttribute('tension'))
wire.setAttribute('d', path)
} else {
const path = this.bezier(nid1, lnk.from[1], nid2, lnk.to[1], this.getBZAttribute('tension'))
const path = this.bezierNodes(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))))
}
@@ -489,7 +574,16 @@ class BZgraflow extends Buildoz{
}
findLongLinks(links) {
const { parents } = this.buildGraphStructures(this.flow.nodes, links)
let linksWithoutBackEdges
if(this.hasAnyLoop(this.flow.nodes, this.flow.links)){
console.warn('Loop(s) detected... Cannot auto-place !')
const backEdges = this.findBackEdges(this.flow.nodes, this.flow.links)
linksWithoutBackEdges = this.flow.links.filter((link, idx) => (!backEdges.includes(idx)) && (link.from[0] != link.to[0]))
} else {
linksWithoutBackEdges = this.flow.links
}
/// Yes that means we ignore long & back links !
const { parents } = this.buildGraphStructures(this.flow.nodes, linksWithoutBackEdges)
const layers = this.computeLayers(this.flow.nodes, parents)
const crossLayerLinks = []
for(const link of links){