diff --git a/src/Scene.ts b/src/Scene.ts index 87b17c6..e2a9ca9 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -698,7 +698,7 @@ export class Scene { //------------------------------------------------------- // Render ea.style.opacity = 100; - await Promise.all(this.layouts.map(async (layout) => await layout.render())); + await Promise.all(this.layouts.map(async (layout) => await layout.render(isCompactView))); const nodeElements = ea.getElements(); this.links.render(Array.from(this.toolsPanel.linkTagFilter.selectedLinks)); diff --git a/src/graph/Layout.ts b/src/graph/Layout.ts index 72542d4..0de9aae 100644 --- a/src/graph/Layout.ts +++ b/src/graph/Layout.ts @@ -53,8 +53,138 @@ export class Layout { ? Array(columns).fill(null).map((_,j) => sortedNodes[i*columns+j]) //full row : getRowLayout(itemCount % columns).map(idx => idx ? sortedNodes[i*columns+idx-1]:null)); } + private setCompactPosition(center00: { x: number, y: number }) { + const fixOverlap = (center00: { x: number, y: number }) => { - async render() { + //I think if LayoutSpecification could add layoutPositionTag property: 'center' | 'children' | ... + //,this will be more simple and clear. + const layoutPosition = this.spec.origoX === 0 + ? (this.spec.top === null && this.spec.bottom === null) + ? 'center' + : this.spec.origoY < 0 ? 'top' : 'bottom' + : (this.spec.top === null && this.spec.bottom === null) + ? this.spec.origoX < 0 ? 'left' : 'right' + : 'sibling'; + + if (layoutPosition === 'center') { + return this.setPosition(center00); + } + if (layoutPosition === 'sibling') { + const maxNodeWidth = Math.max(...this.renderedNodes.flatMap(row => + row.map(node => node?.labelSize().width) + )); + const gap = Math.abs(this.spec.origoX); + const offset = maxNodeWidth > gap + ? (maxNodeWidth - gap) + 0.5 * maxNodeWidth + : 0; + const newCenter00 = { + x: center00.x + offset, + y: center00.y, + } + return this.setPosition(newCenter00); + } + if (layoutPosition === 'left' || layoutPosition === 'right') { + const maxNodeWidth = Math.max(...this.renderedNodes.flat().map(node => node.labelSize().width)); + const offset = maxNodeWidth > this.spec.columnWidth + ? (maxNodeWidth - this.spec.columnWidth) / 2 + 0.2 * maxNodeWidth + : 0; + const newCenter00 = { + x: layoutPosition === 'left' + ? center00.x - offset + : center00.x + offset, + y: center00.y, + } + return this.setPosition(newCenter00); + + } + else { + const rowWidth = this.spec.columns * this.spec.columnWidth; + const nodeGap = rowWidth * 0.1; + const initialCenter = (spec: LayoutSpecification, width: number, row: number) => { + return width > spec.columnWidth + ? { + x: center00.x + (width - spec.columnWidth) / 2, + y: center00.y + row * spec.rowHeight, + } + : { + x: center00.x - (spec.columnWidth - width) / 2, + y: center00.y + row * spec.rowHeight, + } + } + const nodes = this.renderedNodes.flat().filter(node => !!node); + let stackWidth = 0; + let nodesInfo: { x: number, y: number, width: number }[][] = []; + const alignCenter = (nodeInfo: { x: number, y: number, width: number }[]) => { + const centerX = center00.x - this.spec.columnWidth/2 + rowWidth/2; + let l = 0; + let r = nodeInfo.length-1; + while (l<=r){ + const left = nodeInfo[l]; + const right = nodeInfo[r]; + const offset = centerX-(left.x + right.x)/2; + nodeInfo[l] = {...left,x:left.x + offset}; + nodeInfo[r] = {...right,x:right.x + offset}; + l +=1; + r -=1; + } + return nodeInfo; + } + + nodes.forEach((node, index) => { + const width = node.labelSize().width; + const row = nodesInfo.length - 1; + if (index === 0) { + const init = initialCenter(this.spec, width, 0); + stackWidth = width; + nodesInfo.push([{ ...init, width }]); + } + else if ((stackWidth + nodeGap + width) > rowWidth) { + nodesInfo[row] = alignCenter(nodesInfo[row]); + const newRow = row + 1; + const init = initialCenter(this.spec, width, newRow); + stackWidth = width; + nodesInfo.push([{ width, ...init }]); + } + else { + const prev_center = nodesInfo[row].last(); + nodesInfo[row].push({ + ...prev_center, + width, + x: prev_center.x + prev_center.width / 2 + nodeGap + width / 2, + }) + stackWidth = stackWidth + nodeGap + width; + } + + }); + + const nodePosition = nodesInfo.flatMap((nodes, row) => { + if (row === nodesInfo.length - 1) { + return alignCenter(nodes) + } + return nodes + }); + + return nodes.map((node, index) => { + const info = nodePosition[index]; + node?.setCenter({ ...info }); + return node; + }); + } + } + return fixOverlap(center00); + } + private setPosition(center00: { x: number, y: number }) { + return this.renderedNodes.map((nodes, row) => { + return nodes.map((node, idx) => { + node?.setCenter({ + x: center00.x + idx * this.spec.columnWidth, + y: center00.y + row * this.spec.rowHeight + }); + return node + }) + }).flat(); + } + async render(isCompactView:boolean) { this.layout(); const rows = this.renderedNodes.length; const height = rows * this.spec.rowHeight; @@ -71,16 +201,14 @@ export class Layout { x: this.spec.origoX - (this.spec.columns === 1 ? 0 : (this.spec.columns-1)/2*this.spec.columnWidth), y: top }; - for (const [row, nodes] of this.renderedNodes.entries()) { - for (const [idx, node] of nodes.entries()) { - if(node) { - node.setCenter({ - x: center00.x + idx*this.spec.columnWidth, - y: center00.y + row*this.spec.rowHeight - }); - await node.render(); + + const renderedNodesWithPosition = isCompactView + ? this.setCompactPosition(center00) + : this.setPosition(center00); + for (const [_, node] of renderedNodesWithPosition.entries()) { + if (node) { + await node.render(); } } } - } } \ No newline at end of file diff --git a/src/graph/Node.ts b/src/graph/Node.ts index b9e2909..33f21ae 100644 --- a/src/graph/Node.ts +++ b/src/graph/Node.ts @@ -82,6 +82,21 @@ export class Node { : label; } + labelSize(){ + const ea = this.ea; + ea.style.fontSize = this.style.fontSize; + ea.style.fontFamily = this.style.fontFamily; + ea.style.fillStyle = this.style.fillStyle; + ea.style.roughness = this.style.roughness; + ea.style.strokeSharpness = this.style.strokeShaprness; + ea.style.strokeWidth = this.style.strokeWidth; + ea.style.strokeColor = this.style.textColor; + ea.style.backgroundColor = "transparent"; + const label = this.displayText(); + const labelSize = ea.measureText(`${label}`); + return {width:labelSize.width + 2*this.style.padding,height:labelSize.height} ; + } + setCenter(center:{x:number, y:number}) { this.center = center; }