diff --git a/src/components/treeview/modules.vue b/src/components/treeview/modules.vue index f93a749..8fbae0b 100644 --- a/src/components/treeview/modules.vue +++ b/src/components/treeview/modules.vue @@ -5,7 +5,7 @@
-  {{ module.name }} +  {{ renderName }} @@ -27,7 +27,8 @@ @@ -39,7 +40,7 @@ /* eslint-disable */ import { globalLookup } from '@/hook/global'; import { ModuleView } from '@/hook/render/yosys'; -import { defineComponent, reactive } from 'vue'; +import { defineComponent, reactive, computed } from 'vue'; defineComponent({ name: 'modules' }); @@ -47,11 +48,18 @@ const props = defineProps({ module: { type: ModuleView, required: true - } + }, + renderName: { + type: String + } }); const module = props.module; +const renderName = computed(() => { + return props.renderName ? props.renderName : module.name +}); + // 当前 scope 的例化模块下的 const ports = []; const cells = []; @@ -63,20 +71,25 @@ for (const portName of module.nameToPort.keys()) { }); } + for (const cellName of module.nameToCell.keys()) { const cell = module.nameToCell.get(cellName); - if (cell.view === module) { + + if (cell.belongModuleView === module) { // 防止递归 continue; } - if (cell.isInstantiation) { + if (cell.isInstantiation) { cells.push({ name: cellName, - view: cell.belongModuleView + view: cell.belongModuleView, + renderName: `${cellName} (${cell.belongModuleView.name})` }); } } + + function clickItem() { } diff --git a/src/hook/jsdoc.js b/src/hook/jsdoc.js index a4e9006..ecb404b 100644 --- a/src/hook/jsdoc.js +++ b/src/hook/jsdoc.js @@ -255,3 +255,8 @@ import { Module } from "./render/layout"; * @typedef DragConnection * @property {d3.Selection} selection */ + +/** + * @typedef ElkMakerConfig + * @param {string} [parentId] + */ \ No newline at end of file diff --git a/src/hook/render/cell.js b/src/hook/render/cell.js index 506f90b..0fc9b85 100644 --- a/src/hook/render/cell.js +++ b/src/hook/render/cell.js @@ -48,11 +48,17 @@ export class CellRender { const id2manager = this.id2manager; const _this = this; + + console.log(data); + + let cellSelections = this.parentSelection.selectAll('svg') .data(data) .enter() .append(data => { const element = data.element; + console.log(data); + 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 d836de2..2c5af62 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -11,6 +11,7 @@ import { ConnectionRender } from './connection'; import { WireRender } from './wire'; import { pinkLog, redLog } from '../utils'; import { treeviewData } from '@/components/treeview/tree'; +import { dotConnect } from './yosys'; export class NetlistRender { /** @@ -20,6 +21,24 @@ export class NetlistRender { * @param {number} renderWidth */ constructor() { + this.defaultLayoutOptions = { + // ... + 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35, + // node 之间的最小间距 + 'elk.spacing.nodeNode': 35, + // layered 算法布局,尽可能让全体布局从左向右 + 'elk.algorithm': 'layered', + // layered 算法的风格 + 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX', + // ... + 'elk.layered.considerModelOrder.longEdgeStrategy': 'DUMMY_NODE_UNDER', + // edge 的生成采用正交路由算法 + 'elk.edgeRouting': 'ORTHOGONAL', + // 指定 layered 算法的方向为从左到右 + 'elk.direction': 'RIGHT', + // 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组 + 'elk.partitioning.activate': true + } /** * @type {ElkGraph} */ @@ -27,24 +46,7 @@ export class NetlistRender { id: 'root', children: [], edges: [], - layoutOptions: { - // ... - 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35, - // node 之间的最小间距 - 'elk.spacing.nodeNode': 35, - // layered 算法布局,尽可能让全体布局从左向右 - 'elk.algorithm': 'layered', - // layered 算法的风格 - 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX', - // ... - 'elk.layered.considerModelOrder.longEdgeStrategy': 'DUMMY_NODE_UNDER', - // edge 的生成采用正交路由算法 - 'elk.edgeRouting': 'ORTHOGONAL', - // 指定 layered 算法的方向为从左到右 - 'elk.direction': 'RIGHT', - // 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组 - 'elk.partitioning.activate': true - } + layoutOptions: this.defaultLayoutOptions }; /** @@ -187,9 +189,6 @@ export class NetlistRender { this.renderWidth = element.clientWidth; this.registerResizeHandler(element); - // 根据 height 进行放缩(可以通过设置进行调整) - const ratio = this.renderHeight / virtualHeight; - // 遍历计算布局进行创建 const svg = d3.select(container) .selectAll('svg') @@ -203,13 +202,14 @@ export class NetlistRender { console.log(computedLayout); // 生成连接 - await this.renderLine(g, computedLayout, ratio); + await this.renderLine(g, computedLayout); // 生成实体 - await this.renderEntity(g, computedLayout, ratio); + await this.renderEntity(g, computedLayout); // svg 挂载为全局注册的 selection this.selection = svg; + this.g = g; // 注册平移和缩放 this.registerRenderTransform(g); @@ -229,9 +229,8 @@ export class NetlistRender { * @description 绘制实体 * @param {d3.Selection} parentSelection * @param {ElkNode} computedLayout - * @param {number} ratio */ - async renderEntity(parentSelection, computedLayout, ratio) { + async renderEntity(parentSelection, computedLayout) { // node 可能是如下的几类 // - module 的 port // - 器件(基础器件 & 例化模块) @@ -251,7 +250,8 @@ export class NetlistRender { const skin = skinManager.querySkin(node.renderName); if (skin) { // 具有 skin 的器件 - this.cellRender.addAsD3DataItem(node, skin.meta.svgDoc.documentElement); + const element = skin.meta.svgDoc.documentElement.cloneNode(true); + this.cellRender.addAsD3DataItem(node, element); } else { if (node.renderType === 'port') { this.portRender.addAsD3DataItem(node); @@ -267,19 +267,20 @@ export class NetlistRender { } } - this.portRender.render(); - this.instantiationRender.render(); - this.cellRender.render(); - this.connectionRender.render(); + const ports = this.portRender.render(); + const instances = this.instantiationRender.render(); + const cells = this.cellRender.render(); + const connections = this.connectionRender.render(); + + return { ports, instances, cells, connections }; } /** * @description 绘制连线 * @param {d3.Selection} parentSelection * @param {ElkNode} computedLayout - * @param {number} ratio */ - async renderLine(parentSelection, computedLayout, ratio) { + async renderLine(parentSelection, computedLayout) { this.wireRender = new WireRender(parentSelection, this); for (const edge of computedLayout.edges) { @@ -455,16 +456,167 @@ export class NetlistRender { /** * @description 展开例化模块 + * @param {d3.Selection} instanceSelection */ - expandInstance() { + async expandInstance(instanceSelection) { + const data = instanceSelection.datum(); + const elkNode = data._self; + const moduleName = elkNode.renderName; + const module = this.nameToModule.get(moduleName); + function addNodeIdPrefix(nodes) { + for (const node of nodes) { + node.id = dotConnect(elkNode.name, node.id); + for (const port of node.ports || []) { + port.id = dotConnect(elkNode.name, port.id); + } + } + } + + function addEdgeIdPrefix(edges) { + for (const edge of edges) { + edge.id = dotConnect(elkNode.name, edge.id); + edge.source = dotConnect(elkNode.name, edge.source); + edge.sourcePort = dotConnect(elkNode.name, edge.sourcePort); + edge.target = dotConnect(elkNode.name, edge.target); + edge.targetPort = dotConnect(elkNode.name, edge.targetPort); + } + } + + const portNodes = module.makeNetsElkNodes(); + const cellNodes = module.makeCellsElkNodes(); + const [constantNodes, connectionEdges] = module.makeConnectionElkNodes(); + + // 增加前缀 + addNodeIdPrefix(portNodes); + addNodeIdPrefix(cellNodes); + addNodeIdPrefix(constantNodes); + addEdgeIdPrefix(connectionEdges); + + elkNode.children = []; + elkNode.edges = []; + elkNode.layoutOptions = this.defaultLayoutOptions; + + elkNode.children.push(...portNodes); + elkNode.children.push(...cellNodes); + elkNode.children.push(...constantNodes); + elkNode.edges.push(...connectionEdges); + + const start = performance.now(); + + // 更新布局 + await this.elk.layout(this.elkGraph); + const computedLayout = this.elkGraph; + const timecost = (performance.now() - start).toFixed(3); + + pinkLog(`expandInstance 布局计算耗时 ${timecost} ms`); + console.log(computedLayout); + + const svg = this.selection; + const g = this.g; + + g.selectAll('*').remove(); + + // 开始递归地进行渲染 + const renderStack = [ + { + parentSelection: g, + layout: computedLayout + } + ]; + + while (renderStack.length > 0) { + const s = renderStack.pop(); + const parentSelection = s.parentSelection; + const layout = s.layout; + await this.renderLine(parentSelection, layout); + const { instances } = await this.renderEntity(parentSelection, layout); + const id2selection = new Map(); + instances.each(function(data) { + const selection = d3.select(this); + id2selection.set(data.id, selection); + }); + + // 将需要渲染子图的部分扔进渲染器 + for (const node of layout.children || []) { + const subChildren = node.children || []; + + if (subChildren.length > 0) { + const selection = id2selection.get(node.id); + renderStack.push({ + parentSelection: selection, + layout: node + }); + } + } + } } /** * @description 关闭例化模块 + * @param {d3.Selection} instanceSelection */ - closeInstance() { + async collapseInstance(instanceSelection) { + const data = instanceSelection.datum(); + const elkNode = data._self; + const moduleName = elkNode.renderName; + const module = this.nameToModule.get(moduleName); + + + elkNode.children = []; + elkNode.edges = []; + // 重新计算 elkNode 的宽度和高度 + + + const start = performance.now(); + + // 更新布局 + await this.elk.layout(this.elkGraph); + const computedLayout = this.elkGraph; + const timecost = (performance.now() - start).toFixed(3); + + pinkLog(`collapseInstance 布局计算耗时 ${timecost} ms`); + console.log(computedLayout); + + const svg = this.selection; + const g = this.g; + + g.selectAll('*').remove(); + + // 开始递归地进行渲染 + const renderStack = [ + { + parentSelection: g, + layout: computedLayout + } + ]; + + while (renderStack.length > 0) { + const s = renderStack.pop(); + const parentSelection = s.parentSelection; + const layout = s.layout; + await this.renderLine(parentSelection, layout); + const { instances } = await this.renderEntity(parentSelection, layout); + const id2selection = new Map(); + instances.each(function(data) { + const selection = d3.select(this); + id2selection.set(data.id, selection); + }); + + // 将需要渲染子图的部分扔进渲染器 + for (const node of layout.children || []) { + const subChildren = node.children || []; + + if (subChildren.length > 0) { + const selection = id2selection.get(node.id); + renderStack.push({ + parentSelection: selection, + layout: node + }); + } + } + } } } \ No newline at end of file diff --git a/src/hook/render/instantiation.js b/src/hook/render/instantiation.js index 4c09371..af6e1c2 100644 --- a/src/hook/render/instantiation.js +++ b/src/hook/render/instantiation.js @@ -93,7 +93,8 @@ export class InstantiationRender { portnames, rx: 3, ry: 3, - expandText: '📌' + expandText: '📌', + _self: node }); } @@ -114,7 +115,10 @@ export class InstantiationRender { .attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT) .attr('width', data => data.width) .attr('height', data => data.height - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT) - .attr('fill', d => d.fill); + .attr('fill', d => { + const children = d._self.children || []; + return children.length > 0 ? 'rgba(203, 129, 218, 0.1)' : d.fill; + }); let texts = instantiationSelections.append('text') @@ -138,7 +142,15 @@ export class InstantiationRender { .attr('fill', 'var(--main-color)'); }) .on('click', function(event) { - + const selection = d3.select(this); + const d = selection.datum(); + const children = d._self.children || []; + + if (children.length > 0) { + rootRender.collapseInstance(selection); + } else { + rootRender.expandInstance(selection); + } }); diff --git a/src/hook/render/layout.js b/src/hook/render/layout.js index 09db0f1..b1a1be9 100644 --- a/src/hook/render/layout.js +++ b/src/hook/render/layout.js @@ -202,8 +202,6 @@ export class Module { x: 0, y: yOffset }); - - } // 计算右侧的 diff --git a/src/hook/render/yosys.js b/src/hook/render/yosys.js index 2300662..4b6d804 100644 --- a/src/hook/render/yosys.js +++ b/src/hook/render/yosys.js @@ -45,7 +45,8 @@ export class ModuleView { } } - for (const [cellName, rawCell] of Object.entries(rawModule.cells)) { + + for (const [cellName, rawCell] of Object.entries(rawModule.cells)) { const cell = new Cell(this, cellName, rawCell); this._nameToCell.set(cellName, cell); // 构建映射 @@ -128,7 +129,7 @@ export class Cell { */ constructor(view, name, rawCell) { this.view = view; - this.name = name; + this.name = name; this.rawCell = rawCell; /** diff --git a/src/hook/skin/index.js b/src/hook/skin/index.js index adf930e..e224d14 100644 --- a/src/hook/skin/index.js +++ b/src/hook/skin/index.js @@ -67,6 +67,9 @@ class SkinMeta { // 颜色替换 // 填充颜色 svgString = svgString.replace(/#279BB0/g, 'var(--main-dark-color)'); + + svgString = svgString.replace(/#41C9A0/g, 'var(--main-dark-color)'); + // 边缘颜色 svgString = svgString.replace(/#000000/g, 'var(--main-color)'); // 字体颜色