实现 portnames 的渲染
This commit is contained in:
parent
6e4615a236
commit
9886b8c881
@ -28,7 +28,7 @@ export class NetlistRender {
|
||||
children: [],
|
||||
edges: [],
|
||||
layoutOptions: {
|
||||
// org.eclipse. 可以去除
|
||||
// ...
|
||||
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
|
||||
// node 之间的最小间距
|
||||
'elk.spacing.nodeNode': 35,
|
||||
@ -103,6 +103,8 @@ export class NetlistRender {
|
||||
// 构造模块树
|
||||
for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
|
||||
const module = new Module(moduleName, rawModule);
|
||||
this.nameToModule.set(moduleName, module);
|
||||
|
||||
if (moduleName === this.topModuleName) {
|
||||
// 构造符合 elk 格式的节点数据
|
||||
topModule = module;
|
||||
@ -115,8 +117,6 @@ export class NetlistRender {
|
||||
this.elkGraph.children.push(...cellNodes);
|
||||
this.elkGraph.children.push(...constantNodes);
|
||||
this.elkGraph.edges.push(...connectionEdges);
|
||||
|
||||
this.nameToModule.set(moduleName, module);
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,4 +453,18 @@ export class NetlistRender {
|
||||
this.resizeMonitor.observe(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 展开例化模块
|
||||
*/
|
||||
expandInstance() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 关闭例化模块
|
||||
*/
|
||||
closeInstance() {
|
||||
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import * as d3 from 'd3';
|
||||
import { NetlistRender } from '.';
|
||||
import { globalSetting } from '../global';
|
||||
import { LAYOUT_CONSTANT } from './layout';
|
||||
|
||||
export class InstantiationRender {
|
||||
/**
|
||||
@ -29,8 +30,59 @@ export class InstantiationRender {
|
||||
* @param {ElkNode} 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({
|
||||
id: node.id,
|
||||
type: node.renderName, // 例化模块的模块名字
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
name: node.name,
|
||||
@ -38,8 +90,10 @@ export class InstantiationRender {
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)',
|
||||
text: node.renderName,
|
||||
portnames,
|
||||
rx: 3,
|
||||
ry: 3
|
||||
ry: 3,
|
||||
expandText: '📌'
|
||||
});
|
||||
}
|
||||
|
||||
@ -57,36 +111,62 @@ export class InstantiationRender {
|
||||
.attr("transform", d => `translate(${d.x}, ${d.y})`);
|
||||
|
||||
let instances = instantiationSelections.append('rect')
|
||||
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
|
||||
.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);
|
||||
|
||||
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) {
|
||||
instances.transition()
|
||||
.duration(1000)
|
||||
.attr('stroke', 'var(--main-color)')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('rx', d => d.rx)
|
||||
.attr('ry', d => d.ry);
|
||||
} else {
|
||||
instances.attr('stroke', 'var(--main-color)')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('rx', d => d.rx)
|
||||
.attr('ry', d => d.ry);
|
||||
}
|
||||
|
||||
instantiationSelections
|
||||
|
@ -5,8 +5,17 @@
|
||||
import { globalLookup } from "../global";
|
||||
import { Cell, dotConnect, ModuleView } from "./yosys";
|
||||
|
||||
// 线段的宽度
|
||||
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 = {
|
||||
// port
|
||||
PORT_WIDTH: 50,
|
||||
@ -18,6 +27,11 @@ export const LAYOUT_CONSTANT = {
|
||||
|
||||
// port 顶部的空余,一般用来放置标签的
|
||||
PORT_TOP_MARGIN: 30,
|
||||
// port 内部的 padding,用于绘制
|
||||
PORT_INNER_PADDING: 20,
|
||||
|
||||
// 例化模块上部分的空缺区域
|
||||
INSTANCE_TITLE_HEIGHT: 10,
|
||||
|
||||
// 常数
|
||||
CONSTANT_WIDTH: 50,
|
||||
@ -54,6 +68,15 @@ export class Module {
|
||||
* @type {ModuleView}
|
||||
*/
|
||||
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
|
||||
for (const name of this.view.nameToPort.keys()) {
|
||||
const port = this.view.nameToPort.get(name);
|
||||
|
||||
|
||||
if (port.direction === 'input') {
|
||||
// 为 port 设置连接点
|
||||
const portConnection = {
|
||||
id: dotConnect(port.id, '0'),
|
||||
renderType: 'portConnection',
|
||||
width: 1,
|
||||
height: 1,
|
||||
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
|
||||
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
|
||||
x: LAYOUT_CONSTANT.PORT_WIDTH,
|
||||
y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
|
||||
};
|
||||
@ -89,7 +110,7 @@ export class Module {
|
||||
height: LAYOUT_CONSTANT.PORT_HEIGHT,
|
||||
ports: [portConnection],
|
||||
layoutOptions: {
|
||||
'partitioning.partition': 1,
|
||||
'partitioning.partition': MIN_PARTITION_INDEX,
|
||||
'org.eclipse.elk.portConstraints': 'FIXED_POS'
|
||||
}
|
||||
};
|
||||
@ -100,8 +121,8 @@ export class Module {
|
||||
const portConnection = {
|
||||
id: dotConnect(port.id, '0'),
|
||||
renderType: 'portConnection',
|
||||
width: 1,
|
||||
height: 1,
|
||||
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
|
||||
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
|
||||
x: 0,
|
||||
y: LAYOUT_CONSTANT.PORT_HEIGHT / 2
|
||||
};
|
||||
@ -116,7 +137,7 @@ export class Module {
|
||||
height: LAYOUT_CONSTANT.PORT_HEIGHT,
|
||||
ports: [portConnection],
|
||||
layoutOptions: {
|
||||
'partitioning.partition': 999,
|
||||
'partitioning.partition': MAX_PARTITION_INDEX,
|
||||
'org.eclipse.elk.portConstraints': 'FIXED_POS'
|
||||
}
|
||||
};
|
||||
@ -211,28 +232,74 @@ export class Module {
|
||||
ports,
|
||||
layoutOptions: {
|
||||
'org.eclipse.elk.portConstraints': 'FIXED_POS',
|
||||
'partitioning.partition': 10
|
||||
'partitioning.partition': this.commonPartitionIndex
|
||||
}
|
||||
};
|
||||
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;
|
||||
|
||||
ports.push({
|
||||
id: connection.id,
|
||||
renderName: connectionName,
|
||||
renderType: 'cellPort',
|
||||
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
|
||||
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
|
||||
properties: {
|
||||
'port.side': portSide
|
||||
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 = {
|
||||
@ -240,13 +307,14 @@ export class Module {
|
||||
name: cell.name,
|
||||
renderName: cell.type,
|
||||
renderType: 'cell',
|
||||
width: LAYOUT_CONSTANT.INSTANTIATION_WIDTH,
|
||||
height: LAYOUT_CONSTANT.INSTANTIATION_HEIGHT,
|
||||
width: instanceWidth,
|
||||
height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT,
|
||||
ports,
|
||||
layoutOptions: {
|
||||
// 强制固定 port 的方向
|
||||
'org.eclipse.elk.portConstraints': 'FIXED_SIDE',
|
||||
'partitioning.partition': 10
|
||||
'org.eclipse.elk.portConstraints': 'FIXED_POS',
|
||||
// TODO: 同名例化模块对齐
|
||||
'partitioning.partition': this.commonPartitionIndex
|
||||
}
|
||||
};
|
||||
|
||||
@ -288,7 +356,7 @@ export class Module {
|
||||
height: LAYOUT_CONSTANT.CONSTANT_HEIGHT,
|
||||
layoutOptions: {
|
||||
'elk.port.side': 'WEST',
|
||||
'partitioning.partition': 10
|
||||
'partitioning.partition': this.commonPartitionIndex
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user