实现 portnames 的渲染

This commit is contained in:
锦恢 2024-12-29 01:32:59 +08:00
parent 6e4615a236
commit 9886b8c881
3 changed files with 211 additions and 49 deletions

View File

@ -28,7 +28,7 @@ export class NetlistRender {
children: [], children: [],
edges: [], edges: [],
layoutOptions: { layoutOptions: {
// org.eclipse. 可以去除 // ...
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35, 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
// node 之间的最小间距 // node 之间的最小间距
'elk.spacing.nodeNode': 35, 'elk.spacing.nodeNode': 35,
@ -103,6 +103,8 @@ export class NetlistRender {
// 构造模块树 // 构造模块树
for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) { for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
const module = new Module(moduleName, rawModule); const module = new Module(moduleName, rawModule);
this.nameToModule.set(moduleName, module);
if (moduleName === this.topModuleName) { if (moduleName === this.topModuleName) {
// 构造符合 elk 格式的节点数据 // 构造符合 elk 格式的节点数据
topModule = module; topModule = module;
@ -115,8 +117,6 @@ export class NetlistRender {
this.elkGraph.children.push(...cellNodes); this.elkGraph.children.push(...cellNodes);
this.elkGraph.children.push(...constantNodes); this.elkGraph.children.push(...constantNodes);
this.elkGraph.edges.push(...connectionEdges); this.elkGraph.edges.push(...connectionEdges);
this.nameToModule.set(moduleName, module);
} }
} }
@ -453,4 +453,18 @@ export class NetlistRender {
this.resizeMonitor.observe(element); this.resizeMonitor.observe(element);
} }
/**
* @description 展开例化模块
*/
expandInstance() {
}
/**
* @description 关闭例化模块
*/
closeInstance() {
}
} }

View File

@ -1,6 +1,7 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { NetlistRender } from '.'; import { NetlistRender } from '.';
import { globalSetting } from '../global'; import { globalSetting } from '../global';
import { LAYOUT_CONSTANT } from './layout';
export class InstantiationRender { export class InstantiationRender {
/** /**
@ -29,8 +30,59 @@ export class InstantiationRender {
* @param {ElkNode} node * @param {ElkNode} node
*/ */
addAsD3DataItem(node) { addAsD3DataItem(node) {
const nodeModule = this.rootRender.nameToModule.get(node.renderName);
const view = nodeModule.view;
const portnames = [];
let inputCount = 0;
let outputCount = 0;
const instanceWidth = LAYOUT_CONSTANT.INSTANTIATION_WIDTH + 2 * LAYOUT_CONSTANT.PORT_INNER_PADDING;
// 统计出两侧 port 字符串最长的
let inputPortMaxLength = 0;
let outputPortMaxLength = 0;
for (const portName of view.nameToPort.keys()) {
const port = view.nameToPort.get(portName);
if (port.direction === 'input') {
inputPortMaxLength = Math.max(portName.length, inputPortMaxLength);
} else {
outputPortMaxLength = Math.max(portName.length, outputPortMaxLength);
}
}
for (const portName of view.nameToPort.keys()) {
const port = view.nameToPort.get(portName);
if (port.direction === 'input') {
inputCount ++;
const offsetY = inputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN +
(inputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT +
LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT;
portnames.push({
name: portName,
x: 5,
y: offsetY,
align: 'left'
});
} else {
outputCount ++;
const offsetY = outputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN +
(outputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT +
LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT;
portnames.push({
name: portName,
x: instanceWidth - 5,
y: offsetY,
align: 'end'
});
}
}
this.data.push({ this.data.push({
id: node.id, id: node.id,
type: node.renderName, // 例化模块的模块名字
x: node.x, x: node.x,
y: node.y, y: node.y,
name: node.name, name: node.name,
@ -38,8 +90,10 @@ export class InstantiationRender {
height: node.height, height: node.height,
fill: 'var(--main-dark-color)', fill: 'var(--main-dark-color)',
text: node.renderName, text: node.renderName,
portnames,
rx: 3, rx: 3,
ry: 3 ry: 3,
expandText: '📌'
}); });
} }
@ -57,36 +111,62 @@ export class InstantiationRender {
.attr("transform", d => `translate(${d.x}, ${d.y})`); .attr("transform", d => `translate(${d.x}, ${d.y})`);
let instances = instantiationSelections.append('rect') let instances = instantiationSelections.append('rect')
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
.attr('width', data => data.width) .attr('width', data => data.width)
.attr('height', data => data.height) .attr('height', data => data.height - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
.attr('fill', d => d.fill); .attr('fill', d => d.fill);
let texts = instantiationSelections.append('text')
.attr('x', data => data.width / 2) // 文本的 x 坐标(居中)
.attr('y', data => data.height / 2) // 文本的 y 坐标(居中)
.attr('dominant-baseline', 'middle') // 文本垂直居中
.attr('text-anchor', 'middle') // 文本水平居中
.attr('fill', 'var(--foreground)') // 文本颜色
.attr('font-size', '0')
.transition()
.duration(1000)
.attr('font-size', '15px')
.attr('class', 'port-caption')
.text(data => data.name); // 设置文本内容
let texts = instantiationSelections.append('text')
.attr('x', 0)
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT - 8)
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'left')
.attr('fill', 'var(--main-dark-color)')
.attr('font-size', '0')
.attr('font-size', '12px')
.attr('class', 'port-caption')
.text(data => data.name + ' ' + data.expandText)
.each(function(data) {
const text = d3.select(this);
const bbox = text.node().getBBox();
instantiationSelections.insert('rect', 'text')
.attr('x', -1)
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT - bbox.height - 5)
.attr('width', bbox.width + 10)
.attr('height', bbox.height + 5)
.attr('fill', 'var(--main-color)');
})
.on('click', function(event) {
});
// 获取 port 相关的信息
instantiationSelections.each(function(node) {
d3.select(this) // 选择当前节点
.selectAll('.port-name') // 选择所有端口名称元素
.data(node.portnames) // 绑定端口名称数据
.enter()
.append('text') // 创建文本元素
.attr('class', 'port-name')
.attr('x', d => d.x) // 使用端口的相对 x 坐标
.attr('y', d => d.y) // 使用端口的相对 y 坐标
.attr('dominant-baseline', 'middle') // 文本垂直居中
.attr('text-anchor', d => d.align)
.attr('fill', 'var(--foreground)') // 文本颜色
.attr('font-size', '10px') // 设置字体大小
.text(d => d.name); // 设置文本内容
});
if (globalSetting.renderAnimation) { if (globalSetting.renderAnimation) {
instances.transition() instances.transition()
.duration(1000) .duration(1000)
.attr('stroke', 'var(--main-color)') .attr('stroke', 'var(--main-color)')
.attr('stroke-width', 2) .attr('stroke-width', 2)
.attr('rx', d => d.rx)
.attr('ry', d => d.ry);
} else { } else {
instances.attr('stroke', 'var(--main-color)') instances.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 2) .attr('stroke-width', 2)
.attr('rx', d => d.rx)
.attr('ry', d => d.ry);
} }
instantiationSelections instantiationSelections

View File

@ -5,8 +5,17 @@
import { globalLookup } from "../global"; import { globalLookup } from "../global";
import { Cell, dotConnect, ModuleView } from "./yosys"; import { Cell, dotConnect, ModuleView } from "./yosys";
// 线段的宽度
export const LINE_WIDTH = 2; export const LINE_WIDTH = 2;
// 分组最小 ID
export const MIN_PARTITION_INDEX = 1;
// 分组最大 ID
export const MAX_PARTITION_INDEX = 10000000;
// 每一个例化最多能占据的列数
export const MAX_INSTANCE_COL_NUM = 1000;
// 每一列最多的同类器件数量
export const MAX_SAME_TYPE_INSTANCE_NUM = 5;
// 其他常量
export const LAYOUT_CONSTANT = { export const LAYOUT_CONSTANT = {
// port // port
PORT_WIDTH: 50, PORT_WIDTH: 50,
@ -18,6 +27,11 @@ export const LAYOUT_CONSTANT = {
// port 顶部的空余,一般用来放置标签的 // port 顶部的空余,一般用来放置标签的
PORT_TOP_MARGIN: 30, PORT_TOP_MARGIN: 30,
// port 内部的 padding用于绘制
PORT_INNER_PADDING: 20,
// 例化模块上部分的空缺区域
INSTANCE_TITLE_HEIGHT: 10,
// 常数 // 常数
CONSTANT_WIDTH: 50, CONSTANT_WIDTH: 50,
@ -54,6 +68,15 @@ export class Module {
* @type {ModuleView} * @type {ModuleView}
*/ */
this.view = new ModuleView(name, module); this.view = new ModuleView(name, module);
this.commonPartitionIndex = 10;
this.partitionIndex = 11;
/**
* @description 统计一个例化模块 name 出现的次数用来分配布局分组索引
* @type {Map<string, number>}
*/
this.instanceNameCounter = new Map();
} }
/** /**
@ -66,15 +89,13 @@ export class Module {
// 绘制 ports // 绘制 ports
for (const name of this.view.nameToPort.keys()) { for (const name of this.view.nameToPort.keys()) {
const port = this.view.nameToPort.get(name); const port = this.view.nameToPort.get(name);
if (port.direction === 'input') { if (port.direction === 'input') {
// 为 port 设置连接点 // 为 port 设置连接点
const portConnection = { const portConnection = {
id: dotConnect(port.id, '0'), id: dotConnect(port.id, '0'),
renderType: 'portConnection', renderType: 'portConnection',
width: 1, width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: 1, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: LAYOUT_CONSTANT.PORT_WIDTH, x: LAYOUT_CONSTANT.PORT_WIDTH,
y: LAYOUT_CONSTANT.PORT_HEIGHT / 2 y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
}; };
@ -89,7 +110,7 @@ export class Module {
height: LAYOUT_CONSTANT.PORT_HEIGHT, height: LAYOUT_CONSTANT.PORT_HEIGHT,
ports: [portConnection], ports: [portConnection],
layoutOptions: { layoutOptions: {
'partitioning.partition': 1, 'partitioning.partition': MIN_PARTITION_INDEX,
'org.eclipse.elk.portConstraints': 'FIXED_POS' 'org.eclipse.elk.portConstraints': 'FIXED_POS'
} }
}; };
@ -100,8 +121,8 @@ export class Module {
const portConnection = { const portConnection = {
id: dotConnect(port.id, '0'), id: dotConnect(port.id, '0'),
renderType: 'portConnection', renderType: 'portConnection',
width: 1, width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: 1, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0, x: 0,
y: LAYOUT_CONSTANT.PORT_HEIGHT / 2 y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
}; };
@ -116,7 +137,7 @@ export class Module {
height: LAYOUT_CONSTANT.PORT_HEIGHT, height: LAYOUT_CONSTANT.PORT_HEIGHT,
ports: [portConnection], ports: [portConnection],
layoutOptions: { layoutOptions: {
'partitioning.partition': 999, 'partitioning.partition': MAX_PARTITION_INDEX,
'org.eclipse.elk.portConstraints': 'FIXED_POS' 'org.eclipse.elk.portConstraints': 'FIXED_POS'
} }
}; };
@ -211,28 +232,74 @@ export class Module {
ports, ports,
layoutOptions: { layoutOptions: {
'org.eclipse.elk.portConstraints': 'FIXED_POS', 'org.eclipse.elk.portConstraints': 'FIXED_POS',
'partitioning.partition': 10 'partitioning.partition': this.commonPartitionIndex
} }
}; };
nodes.push(node); nodes.push(node);
} else { } 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 一一对应 // 创建器件节点的 port, port 和 connection 一一对应
const ports = []; const ports = [];
let inputCount = 0;
let outputCount = 0;
for (const connectionName of cell.nameToConnection.keys()) { for (const connectionName of cell.nameToConnection.keys()) {
const connection = cell.nameToConnection.get(connectionName); const connection = cell.nameToConnection.get(connectionName);
const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT; const portSide = connection.direction === 'input' ? ELK_DIRECTION.LEFT: ELK_DIRECTION.RIGHT;
ports.push({ if (connection.direction === 'input') {
id: connection.id, inputCount ++;
renderName: connectionName, const offsetY = inputCount * LAYOUT_CONSTANT.PORT_TOP_MARGIN +
renderType: 'cellPort', (inputCount - 1) * LAYOUT_CONSTANT.CELL_PORT_HEIGHT +
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT;
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
properties: { ports.push({
'port.side': portSide 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;
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 = { const node = {
@ -240,13 +307,14 @@ export class Module {
name: cell.name, name: cell.name,
renderName: cell.type, renderName: cell.type,
renderType: 'cell', renderType: 'cell',
width: LAYOUT_CONSTANT.INSTANTIATION_WIDTH, width: instanceWidth,
height: LAYOUT_CONSTANT.INSTANTIATION_HEIGHT, height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT,
ports, ports,
layoutOptions: { layoutOptions: {
// 强制固定 port 的方向 // 强制固定 port 的方向
'org.eclipse.elk.portConstraints': 'FIXED_SIDE', 'org.eclipse.elk.portConstraints': 'FIXED_POS',
'partitioning.partition': 10 // TODO: 同名例化模块对齐
'partitioning.partition': this.commonPartitionIndex
} }
}; };
@ -288,7 +356,7 @@ export class Module {
height: LAYOUT_CONSTANT.CONSTANT_HEIGHT, height: LAYOUT_CONSTANT.CONSTANT_HEIGHT,
layoutOptions: { layoutOptions: {
'elk.port.side': 'WEST', 'elk.port.side': 'WEST',
'partitioning.partition': 10 'partitioning.partition': this.commonPartitionIndex
} }
}; };