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)');
// 字体颜色