实现取消 port 节点,和外部直连

This commit is contained in:
锦恢 2024-12-31 20:31:20 +08:00
parent e652631261
commit 64fdc7408b
7 changed files with 140 additions and 25 deletions

View File

@ -171,6 +171,8 @@ import { Module } from "./render/layout";
* @property {number} [y] 端口的 Y 坐标可选由布局算法生成
* @property {number} [width] 端口的宽度可选
* @property {number} [height] 端口的高度可选
* @property {'input' | 'output'} [direction] 端口在这个实体上的方向
* @property {'port' | 'instance' | 'cell'} [source] 这是谁的端口
* @property {ElkLayoutOptions} [layoutOptions] 端口的布局选项可选
*/

View File

@ -53,7 +53,7 @@ export class CellRender {
.enter()
.append(data => {
const element = data.element;
element.setAttribute('x', data.x);
element.setAttribute('x', data.x + LAYOUT_CONSTANT.CELL_LEFT_MARGIN);
element.setAttribute('y', data.y);
if (globalSetting.renderAnimation) {
element.setAttribute('opacity', 0);

View File

@ -1,6 +1,7 @@
import * as d3 from 'd3';
import { globalSetting } from '../global';
import { NetlistRender } from '.';
import { getMarginParamter, LAYOUT_CONSTANT } from './layout';
export class ConnectionRender {
/**
@ -25,16 +26,19 @@ export class ConnectionRender {
/**
* @description elknode 关于 module connection 的数据添加为 d3 数据项目
* @param {ElkPort} cellPort 连接点对象
* @param {ElkNode} node 当前的实体port/例化模块/器件
* @param {import('../jsdoc').ElkPort} cellPort 连接点对象
* @param {import('../jsdoc').ElkNode} node 当前的实体port/例化模块/器件
*/
addAsD3DataItem(cellPort, node) {
addAsD3DataItem(port, node) {
const marginParamter = getMarginParamter(port);
const marginOffset = port.direction === 'input' ? marginParamter.leftMargin: - marginParamter.rightMargin;
this.data.push({
id: cellPort.id,
x: cellPort.x + node.x,
y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽
width: cellPort.width,
height: cellPort.height,
id: port.id,
x: port.x + node.x + marginOffset,
y: port.y + node.y + 0.5, // 0.5 是为了线宽
width: port.width,
height: port.height,
fill: 'var(--main-color)',
text: '',
r: 3.5

View File

@ -288,6 +288,14 @@ export class NetlistRender {
this.wireRender = new WireRender(parentSelection, this);
this.crossDotRender = new CrossDotRender(parentSelection, this);
// 建立关于 port 的索引
const id2port = new Map();
for (const node of computedLayout.children || []) {
for (const port of node.ports || []) {
id2port.set(port.id, port);
}
}
const rangeTree = new RangeTreeMap();
for (const edge of computedLayout.edges) {
@ -298,7 +306,7 @@ export class NetlistRender {
points.push(point);
}
points.push(section.endPoint);
this.wireRender.addAsD3DataItems(points, edge);
this.wireRender.addAsD3DataItems(points, edge, id2port);
// 加入 range tree 中
for (let i = 0; i < points.length - 1; ++ i) {
@ -502,15 +510,17 @@ export class NetlistRender {
const originX = elkNode.x;
const originY = elkNode.y;
// 给所有的子图的节点增加前缀名
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);
port.id = dotConnect(elkNode.name, port.id);
}
}
}
// 给所有的子图的边增加前缀名
function addEdgeIdPrefix(edges) {
for (const edge of edges) {
edge.id = dotConnect(elkNode.name, edge.id);
@ -531,11 +541,41 @@ export class NetlistRender {
addNodeIdPrefix(constantNodes);
addEdgeIdPrefix(connectionEdges);
// 对于子图而言,不需要创建 ports只需要把对应的连线接到所在例化模块的接口即可.
// 只需要对当前 node 中的 port 进行 index然后对于 connectionEdges 中对应的部分的 id 进行替换即可
const portName2port = new Map();
for (const port of elkNode.ports || []) {
portName2port.set(port.renderName, port);
}
const innerPortId2outterPortId = new Map();
for (const innerPort of portNodes) {
const outterPort = portName2port.get(innerPort.name);
if (outterPort) {
innerPortId2outterPortId.set(innerPort.id, outterPort.id);
}
}
for (const edge of connectionEdges) {
if (innerPortId2outterPortId.has(edge.source)) {
const outterId = innerPortId2outterPortId.get(edge.source);
edge.source = elkNode.id;
edge.sourcePort = outterId;
}
if (innerPortId2outterPortId.has(edge.target)) {
const outterId = innerPortId2outterPortId.get(edge.target);
edge.target = elkNode.id;
edge.targetPort = outterId;
}
}
// 加入到布局图中
elkNode.children = [];
elkNode.edges = [];
elkNode.layoutOptions = this.defaultLayoutOptions;
elkNode.children.push(...portNodes);
elkNode.children.push(...cellNodes);
elkNode.children.push(...constantNodes);
elkNode.edges.push(...connectionEdges);
@ -575,8 +615,11 @@ export class NetlistRender {
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);

View File

@ -40,7 +40,7 @@ export class InstantiationRender {
for (const port of node.ports || []) {
const isInput = port.x < LAYOUT_CONSTANT.INSTANTIATION_WIDTH;
const align = isInput ? 'left': 'end';
const portX = isInput ? port.x + textPadding : port.x - textPadding;
const portX = isInput ? port.x + textPadding : port.x - textPadding - LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN - LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN;
portnames.push({
name: port.renderName,
x: portX,
@ -82,14 +82,15 @@ export class InstantiationRender {
// 例化模块的方块
let instances = instantiationSelections.append('rect')
.attr('x', LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN)
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
.attr('width', data => data.width)
.attr('width', data => data.width - LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN - LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN)
.attr('height', data => data.height - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT);
// 说明文字
let texts = instantiationSelections.append('text')
.attr('x', 0)
.attr('x', LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN)
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT - 8)
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'left')
@ -113,7 +114,7 @@ export class InstantiationRender {
.enter()
.append('text')
.attr('class', 'port-name')
.attr('x', d => d.x)
.attr('x', d => d.x + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN)
.attr('y', d => d.y)
.attr('dominant-baseline', 'middle')
.attr('text-anchor', d => d.align)
@ -125,7 +126,7 @@ export class InstantiationRender {
// 增加一个背景方块,防止 svg 点不到
let bgFullScreenSelections = instantiationSelections.append('rect')
.attr('x', 5)
.attr('x', 5 + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN)
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + 5)
.attr('width', 20)
.attr('height', 20)
@ -149,7 +150,7 @@ export class InstantiationRender {
let fullSreenSelections = instantiationSelections.append(data => {
// 获取全屏图标
const element = svgResource.get('full-screen');
element.setAttribute('x', 5);
element.setAttribute('x', 5 + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN);
element.setAttribute('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + 5);
element.setAttribute('width', 20);
element.setAttribute('height', 20);

View File

@ -99,6 +99,9 @@ export class Module {
const portConnection = {
id: dotConnect(port.id, '0'),
renderType: 'portConnection',
source: 'port',
// 对于 input 的 port 而言,它的端口只有一个且为输出
direction: 'output',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: LAYOUT_CONSTANT.PORT_WIDTH,
@ -110,6 +113,9 @@ export class Module {
name: port.name,
renderName: name,
renderType: 'port',
source: 'port',
// 对于 input 的 port 而言,它的端口只有一个且为输入
direction: 'input',
type: port.direction,
width: LAYOUT_CONSTANT.PORT_WIDTH,
height: LAYOUT_CONSTANT.PORT_HEIGHT,
@ -312,7 +318,7 @@ export class Module {
const meta = skin.meta;
const height = meta.height;
const width = meta.width;
const width = meta.width + LAYOUT_CONSTANT.CELL_LEFT_MARGIN + LAYOUT_CONSTANT.CELL_RIGHT_MARGIN;
const ports = [];
@ -337,6 +343,8 @@ export class Module {
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
direction: 'input',
source: 'cell',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
@ -353,6 +361,8 @@ export class Module {
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
direction: 'output',
source: 'cell',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: width,
@ -407,7 +417,7 @@ export class Module {
2 * LAYOUT_CONSTANT.PORT_INNER_PADDING +
LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN +
LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN;
// 创建器件节点的 port, port 和 connection 一一对应
const ports = [];
let inputCount = 0;
@ -425,6 +435,8 @@ export class Module {
id: connection.id,
renderName: connectionName,
renderType: 'cellPort',
direction: 'input',
source: 'instance',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
@ -440,6 +452,8 @@ export class Module {
id: connection.id,
renderName: connectionName,
renderType: 'cellPort',
direction: 'output',
source: 'instance',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: instanceWidth,
@ -469,3 +483,42 @@ export class Module {
return node;
}
}
/**
*
* @param {import('../jsdoc').ElkPort} port
* @returns {ConnectionMarginParameter}
*/
export function getMarginParamter(port) {
if (!port) {
// 如果是例化模块,那么当前的 port 就是空
// 因为例化模块内部的变量一定和外部直连,所以例化模块内部是没有 port 的
return {
leftMargin: 0,
rightMargin: 0
}
}
switch (port.source) {
case 'instance':
return {
leftMargin: LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN,
rightMargin: LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN
}
case 'cell':
return {
leftMargin: LAYOUT_CONSTANT.CELL_LEFT_MARGIN,
rightMargin: LAYOUT_CONSTANT.CELL_RIGHT_MARGIN
}
default:
return {
leftMargin: 0,
rightMargin: 0
}
}
}
/**
* @typedef ConnectionMarginParameter
* @property {number} leftMargin
* @property {number} rightMargin
*/

View File

@ -3,7 +3,7 @@
import * as d3 from 'd3';
import { globalSetting } from '../global';
import { NetlistRender } from '.';
import { LAYOUT_CONSTANT, LINE_WIDTH } from './layout';
import { getMarginParamter, LAYOUT_CONSTANT, LINE_WIDTH } from './layout';
import { svgResource } from '../skin/draw';
import { PulseLine } from '../skin/plusation';
@ -36,11 +36,24 @@ export class WireRender {
/**
* @description elknode 关于 wire 的数据添加为 d3 数据项目
* @param {ElkPoint[]} points
* @param {ElkEdge} edge
* @param {import('../jsdoc').ElkPoint[]} points 长度至少为 2 的数组代表经历过的点集
* @param {import('../jsdoc').ElkEdge} edge
* @param {Map<string, import('../jsdoc').ElkPort>} id2port
*/
addAsD3DataItems(points, edge) {
addAsD3DataItems(points, edge, id2port) {
const linePaths = [];
const beginPoint = points.at(0);
const endPoint = points.at(-1);
const sourcePort = id2port.get(edge.sourcePort);
const targetPort = id2port.get(edge.targetPort);
const sourceMargin = getMarginParamter(sourcePort);
const targetMargin = getMarginParamter(targetPort);
beginPoint.x -= sourceMargin.rightMargin;
endPoint.x += targetMargin.leftMargin;
for (let i = 0; i < points.length; ++ i) {
// 根据点的信息创建 path
const command = i === 0 ? 'M': 'L';
@ -52,7 +65,6 @@ export class WireRender {
const lineSvg = linePaths.join(' ');
// 判断当前的朝向
const endPoint = points.at(-1);
const direction = endPoint.x > points.at(-2).x ? 'right' : 'left';
const arrowX = direction === 'right' ? endPoint.x - LAYOUT_CONSTANT.CELL_PORT_WIDTH - this.arrowWidth + 2.5:
endPoint.x + LAYOUT_CONSTANT.CELL_PORT_WIDTH - 2.5