From 099309ade9a4679ba4e74a2e5328744fc183c0df Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Sun, 29 Dec 2024 23:01:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=94=B6=E8=B5=B7=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/setting/color.js | 1 - src/hook/render/cell.js | 4 +- src/hook/render/index.js | 45 ++++- src/hook/render/instantiation.js | 3 +- src/hook/render/layout.js | 312 ++++++++++++++++--------------- 5 files changed, 205 insertions(+), 160 deletions(-) diff --git a/src/components/setting/color.js b/src/components/setting/color.js index 4dfdb5a..740679d 100644 --- a/src/components/setting/color.js +++ b/src/components/setting/color.js @@ -53,7 +53,6 @@ export const colorManager = reactive({ for (const item of this.generals) { const optionName = `--${item.type}-color`; const colorString = rootStyles.getPropertyValue(optionName); - console.log(optionName, colorString); item.color = colorString; } } diff --git a/src/hook/render/cell.js b/src/hook/render/cell.js index e692531..2197ecf 100644 --- a/src/hook/render/cell.js +++ b/src/hook/render/cell.js @@ -49,10 +49,10 @@ export class CellRender { const _this = this; let cellSelections = this.parentSelection.selectAll('svg') - .data(data) + .data(data, d => d.id) .enter() .append(data => { - const element = data.element; + const element = data.element; element.setAttribute('x', data.x); element.setAttribute('y', data.y); if (globalSetting.renderAnimation) { diff --git a/src/hook/render/index.js b/src/hook/render/index.js index 2c5af62..06280d4 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -313,6 +313,8 @@ export class NetlistRender { this.selection.call(zoom); this.zoom = zoom; + let started = false; + // 缩放事件处理函数 function zoomed(event) { const { transform, sourceEvent } = event; @@ -334,11 +336,12 @@ export class NetlistRender { } } else { // 系统调用的变换,慢一点变换 - if (globalSetting.renderAnimation) { + if (globalSetting.renderAnimation && !started) { parentSelection .transition() .duration(1000) .attr('transform', transform); + started = true; } else { parentSelection.attr('transform', transform); } @@ -463,6 +466,10 @@ export class NetlistRender { const elkNode = data._self; const moduleName = elkNode.renderName; const module = this.nameToModule.get(moduleName); + + // 先记录当前这个选择集的位置 + const originX = elkNode.x; + const originY = elkNode.y; function addNodeIdPrefix(nodes) { for (const node of nodes) { @@ -512,6 +519,14 @@ export class NetlistRender { pinkLog(`expandInstance 布局计算耗时 ${timecost} ms`); console.log(computedLayout); + // 计算 relayout 后的当前这个例化模块的位置偏移了多少,确保点击前后的左上角坐标一致 + // 以获得最佳的用户体验 + const deltaX = originX - elkNode.x; + const deltaY = originY - elkNode.y; + + const transform = d3.zoomIdentity.translate(deltaX, deltaY); + this.zoom.translateBy(this.selection, deltaX, deltaY); + const svg = this.selection; const g = this.g; @@ -548,7 +563,7 @@ export class NetlistRender { layout: node }); } - } + } } } @@ -560,15 +575,24 @@ export class NetlistRender { async collapseInstance(instanceSelection) { const data = instanceSelection.datum(); const elkNode = data._self; - const moduleName = elkNode.renderName; - const module = this.nameToModule.get(moduleName); + // 找到所在模块 + const parentName = elkNode.parentName; + const instanceName = elkNode.name; + const parentModule = this.nameToModule.get(parentName); + const cell = parentModule.view.nameToCell.get(instanceName); - + // 先记录当前这个选择集的位置 + const originX = elkNode.x; + const originY = elkNode.y; + elkNode.children = []; elkNode.edges = []; - // 重新计算 elkNode 的宽度和高度 - + // 重新计算 elkNode(例化模块) 的宽度和高度 + const instanceNode = parentModule.makeInstanceNode(cell); + for (const key of Object.keys(instanceNode)) { + elkNode[key] = instanceNode[key]; + } const start = performance.now(); @@ -580,6 +604,12 @@ export class NetlistRender { pinkLog(`collapseInstance 布局计算耗时 ${timecost} ms`); console.log(computedLayout); + const deltaX = originX - elkNode.x; + const deltaY = originY - elkNode.y; + + const transform = d3.zoomIdentity.translate(deltaX, deltaY); + this.zoom.translateBy(this.selection, deltaX, deltaY); + const svg = this.selection; const g = this.g; @@ -608,7 +638,6 @@ export class NetlistRender { // 将需要渲染子图的部分扔进渲染器 for (const node of layout.children || []) { const subChildren = node.children || []; - if (subChildren.length > 0) { const selection = id2selection.get(node.id); renderStack.push({ diff --git a/src/hook/render/instantiation.js b/src/hook/render/instantiation.js index fa76193..755d5de 100644 --- a/src/hook/render/instantiation.js +++ b/src/hook/render/instantiation.js @@ -62,6 +62,7 @@ export class InstantiationRender { portnames, rx: 3, ry: 3, + // 数据本身 _self: node }); } @@ -183,8 +184,6 @@ export class InstantiationRender { } }); - console.log(id2element); - bgFullScreenSelections .on('mouseenter', function(_, data) { const svgElement = id2element.get(data.id); diff --git a/src/hook/render/layout.js b/src/hook/render/layout.js index 7b5b1c3..e032269 100644 --- a/src/hook/render/layout.js +++ b/src/hook/render/layout.js @@ -168,155 +168,10 @@ export class Module { const skin = skinManager.querySkin(cell.type); if (skin) { - // 内置器件 - // 创建器件节点的 port, port 和 connection 一一对应 - const meta = skin.meta; - - const height = meta.height; - const width = meta.width; - - const ports = []; - - // 统计分配到左右两侧的 port - const leftSideConnections = []; - const rightSideConnections = []; - - for (const conn of cell.nameToConnection.values()) { - if (conn.direction === 'input') { - leftSideConnections.push(conn); - } else { - rightSideConnections.push(conn); - } - } - - // 计算左侧的 - for (let i = 0; i < leftSideConnections.length; ++ i) { - const connection = leftSideConnections[i]; - const yOffset = meta.getPortYOffset(connection.name); - - ports.push({ - id: connection.id, - renderName: connection.name, - renderType: 'cellPort', - width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, - height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, - x: 0, - y: yOffset - }); - } - - // 计算右侧的 - for (let i = 0; i < rightSideConnections.length; ++ i) { - const connection = rightSideConnections[i]; - const yOffset = meta.getPortYOffset(connection.name); - - ports.push({ - id: connection.id, - renderName: connection.name, - renderType: 'cellPort', - width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, - height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, - x: width, - y: yOffset - }); - } - - const node = { - id: cell.id, - name: cell.name, - renderName: cell.type, - renderType: 'cell', - width, - height, - ports, - layoutOptions: { - 'org.eclipse.elk.portConstraints': 'FIXED_POS', - 'partitioning.partition': this.commonPartitionIndex - } - }; + const node = this.makeCellNode(cell, skin); nodes.push(node); } else { - // 例化模块 - - // 统计分配到左右两侧的 port - const leftSideConnections = []; - const rightSideConnections = []; - - for (const conn of cell.nameToConnection.values()) { - if (conn.direction === 'input') { - leftSideConnections.push(conn); - } else { - rightSideConnections.push(conn); - } - } - - // 根据一侧的 port 数量来决定 height - const maxPortNum = Math.max(leftSideConnections.length, rightSideConnections.length); - const instanceHeight = Math.max( - // 这是例化模块高度的最小值 - LAYOUT_CONSTANT.INSTANTIATION_HEIGHT, - // 每一个 connection 占据的空间为:【上方空余区域(用来防止文本的)】 + 【本身的高度】 - (LAYOUT_CONSTANT.PORT_TOP_MARGIN + LAYOUT_CONSTANT.CELL_PORT_HEIGHT) * (maxPortNum + 1) - ); - // 宽度等于预设宽度+两倍的padding - const instanceWidth = LAYOUT_CONSTANT.INSTANTIATION_WIDTH + 2 * LAYOUT_CONSTANT.PORT_INNER_PADDING; - - // 创建器件节点的 port, port 和 connection 一一对应 - const ports = []; - let inputCount = 0; - let outputCount = 0; - for (const connectionName of cell.nameToConnection.keys()) { - const connection = cell.nameToConnection.get(connectionName); - const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT; - if (connection.direction === 'input') { - inputCount ++; - const offsetY = inputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN + - (inputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT + - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING; - - ports.push({ - id: connection.id, - renderName: connectionName, - renderType: 'cellPort', - width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, - height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, - x: 0, - y: offsetY - }); - } else { - outputCount ++; - const offsetY = outputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN + - (outputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT + - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING; - - ports.push({ - id: connection.id, - renderName: connectionName, - renderType: 'cellPort', - width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, - height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, - x: instanceWidth, - y: offsetY - }); - } - } - - const node = { - id: cell.id, - name: cell.name, - renderName: cell.type, - renderType: 'cell', - width: instanceWidth, - height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING, - ports, - layoutOptions: { - // 强制固定 port 的方向 - 'org.eclipse.elk.portConstraints': 'FIXED_POS', - // TODO: 同名例化模块对齐 - 'partitioning.partition': this.commonPartitionIndex - } - }; - + const node = this.makeInstanceNode(cell); nodes.push(node); } } @@ -442,5 +297,168 @@ export class Module { return [nodes, edges]; } + /** + * @description 制作器件对应的 node + * @param {Cell} cell + * @param {import("../skin").SkinResource} skin + */ + makeCellNode(cell, skin) { + // 内置器件 + // 创建器件节点的 port, port 和 connection 一一对应 + const meta = skin.meta; + + const height = meta.height; + const width = meta.width; + const ports = []; + + // 统计分配到左右两侧的 port + const leftSideConnections = []; + const rightSideConnections = []; + + for (const conn of cell.nameToConnection.values()) { + if (conn.direction === 'input') { + leftSideConnections.push(conn); + } else { + rightSideConnections.push(conn); + } + } + + // 计算左侧的 + for (let i = 0; i < leftSideConnections.length; ++ i) { + const connection = leftSideConnections[i]; + const yOffset = meta.getPortYOffset(connection.name); + + ports.push({ + id: connection.id, + renderName: connection.name, + renderType: 'cellPort', + width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, + height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, + x: 0, + y: yOffset + }); + } + + // 计算右侧的 + for (let i = 0; i < rightSideConnections.length; ++ i) { + const connection = rightSideConnections[i]; + const yOffset = meta.getPortYOffset(connection.name); + + ports.push({ + id: connection.id, + renderName: connection.name, + renderType: 'cellPort', + width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, + height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, + x: width, + y: yOffset + }); + } + + const node = { + id: cell.id, + name: cell.name, + renderName: cell.type, + renderType: 'cell', + width, + height, + ports, + layoutOptions: { + 'org.eclipse.elk.portConstraints': 'FIXED_POS', + 'partitioning.partition': this.commonPartitionIndex + } + }; + + return node; + } + + /** + * @description 制作例化模块对应的 node + * @param {Cell} cell + */ + makeInstanceNode(cell) { + // 统计分配到左右两侧的 port + const leftSideConnections = []; + const rightSideConnections = []; + + for (const conn of cell.nameToConnection.values()) { + if (conn.direction === 'input') { + leftSideConnections.push(conn); + } else { + rightSideConnections.push(conn); + } + } + + // 根据一侧的 port 数量来决定 height + const maxPortNum = Math.max(leftSideConnections.length, rightSideConnections.length); + const instanceHeight = Math.max( + // 这是例化模块高度的最小值 + LAYOUT_CONSTANT.INSTANTIATION_HEIGHT, + // 每一个 connection 占据的空间为:【上方空余区域(用来防止文本的)】 + 【本身的高度】 + (LAYOUT_CONSTANT.PORT_TOP_MARGIN + LAYOUT_CONSTANT.CELL_PORT_HEIGHT) * (maxPortNum + 1) + ); + // 宽度等于预设宽度+两倍的padding + const instanceWidth = LAYOUT_CONSTANT.INSTANTIATION_WIDTH + 2 * LAYOUT_CONSTANT.PORT_INNER_PADDING; + + // 创建器件节点的 port, port 和 connection 一一对应 + const ports = []; + let inputCount = 0; + let outputCount = 0; + for (const connectionName of cell.nameToConnection.keys()) { + const connection = cell.nameToConnection.get(connectionName); + const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT; + if (connection.direction === 'input') { + inputCount ++; + const offsetY = inputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN + + (inputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT + + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING; + + ports.push({ + id: connection.id, + renderName: connectionName, + renderType: 'cellPort', + width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, + height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, + x: 0, + y: offsetY + }); + } else { + outputCount ++; + const offsetY = outputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN + + (outputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT + + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING; + + ports.push({ + id: connection.id, + renderName: connectionName, + renderType: 'cellPort', + width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, + height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, + x: instanceWidth, + y: offsetY + }); + } + } + + + const node = { + id: cell.id, + name: cell.name, + parentName: this.name, + renderName: cell.type, + renderType: 'cell', + width: instanceWidth, + height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING, + ports, + layoutOptions: { + // 强制固定 port 的方向 + 'org.eclipse.elk.portConstraints': 'FIXED_POS', + // TODO: 同名例化模块对齐 + 'partitioning.partition': this.commonPartitionIndex + } + }; + + return node; + } }