实现收起功能

This commit is contained in:
锦恢 2024-12-29 23:01:23 +08:00
parent 6587cd3150
commit 099309ade9
5 changed files with 205 additions and 160 deletions

View File

@ -53,7 +53,6 @@ export const colorManager = reactive({
for (const item of this.generals) {
const optionName = `--${item.type}-color`;
const colorString = rootStyles.getPropertyValue(optionName);
console.log(optionName, colorString);
item.color = colorString;
}
}

View File

@ -49,10 +49,10 @@ export class CellRender {
const _this = this;
let cellSelections = this.parentSelection.selectAll('svg')
.data(data)
.data(data, d => d.id)
.enter()
.append(data => {
const element = data.element;
const element = data.element;
element.setAttribute('x', data.x);
element.setAttribute('y', data.y);
if (globalSetting.renderAnimation) {

View File

@ -313,6 +313,8 @@ export class NetlistRender {
this.selection.call(zoom);
this.zoom = zoom;
let started = false;
// 缩放事件处理函数
function zoomed(event) {
const { transform, sourceEvent } = event;
@ -334,11 +336,12 @@ export class NetlistRender {
}
} else {
// 系统调用的变换,慢一点变换
if (globalSetting.renderAnimation) {
if (globalSetting.renderAnimation && !started) {
parentSelection
.transition()
.duration(1000)
.attr('transform', transform);
started = true;
} else {
parentSelection.attr('transform', transform);
}
@ -463,6 +466,10 @@ export class NetlistRender {
const elkNode = data._self;
const moduleName = elkNode.renderName;
const module = this.nameToModule.get(moduleName);
// 先记录当前这个选择集的位置
const originX = elkNode.x;
const originY = elkNode.y;
function addNodeIdPrefix(nodes) {
for (const node of nodes) {
@ -512,6 +519,14 @@ export class NetlistRender {
pinkLog(`expandInstance 布局计算耗时 ${timecost} ms`);
console.log(computedLayout);
// 计算 relayout 后的当前这个例化模块的位置偏移了多少,确保点击前后的左上角坐标一致
// 以获得最佳的用户体验
const deltaX = originX - elkNode.x;
const deltaY = originY - elkNode.y;
const transform = d3.zoomIdentity.translate(deltaX, deltaY);
this.zoom.translateBy(this.selection, deltaX, deltaY);
const svg = this.selection;
const g = this.g;
@ -548,7 +563,7 @@ export class NetlistRender {
layout: node
});
}
}
}
}
}
@ -560,15 +575,24 @@ export class NetlistRender {
async collapseInstance(instanceSelection) {
const data = instanceSelection.datum();
const elkNode = data._self;
const moduleName = elkNode.renderName;
const module = this.nameToModule.get(moduleName);
// 找到所在模块
const parentName = elkNode.parentName;
const instanceName = elkNode.name;
const parentModule = this.nameToModule.get(parentName);
const cell = parentModule.view.nameToCell.get(instanceName);
// 先记录当前这个选择集的位置
const originX = elkNode.x;
const originY = elkNode.y;
elkNode.children = [];
elkNode.edges = [];
// 重新计算 elkNode 的宽度和高度
// 重新计算 elkNode(例化模块) 的宽度和高度
const instanceNode = parentModule.makeInstanceNode(cell);
for (const key of Object.keys(instanceNode)) {
elkNode[key] = instanceNode[key];
}
const start = performance.now();
@ -580,6 +604,12 @@ export class NetlistRender {
pinkLog(`collapseInstance 布局计算耗时 ${timecost} ms`);
console.log(computedLayout);
const deltaX = originX - elkNode.x;
const deltaY = originY - elkNode.y;
const transform = d3.zoomIdentity.translate(deltaX, deltaY);
this.zoom.translateBy(this.selection, deltaX, deltaY);
const svg = this.selection;
const g = this.g;
@ -608,7 +638,6 @@ export class NetlistRender {
// 将需要渲染子图的部分扔进渲染器
for (const node of layout.children || []) {
const subChildren = node.children || [];
if (subChildren.length > 0) {
const selection = id2selection.get(node.id);
renderStack.push({

View File

@ -62,6 +62,7 @@ export class InstantiationRender {
portnames,
rx: 3,
ry: 3,
// 数据本身
_self: node
});
}
@ -183,8 +184,6 @@ export class InstantiationRender {
}
});
console.log(id2element);
bgFullScreenSelections
.on('mouseenter', function(_, data) {
const svgElement = id2element.get(data.id);

View File

@ -168,155 +168,10 @@ export class Module {
const skin = skinManager.querySkin(cell.type);
if (skin) {
// 内置器件
// 创建器件节点的 port, port 和 connection 一一对应
const meta = skin.meta;
const height = meta.height;
const width = meta.width;
const ports = [];
// 统计分配到左右两侧的 port
const leftSideConnections = [];
const rightSideConnections = [];
for (const conn of cell.nameToConnection.values()) {
if (conn.direction === 'input') {
leftSideConnections.push(conn);
} else {
rightSideConnections.push(conn);
}
}
// 计算左侧的
for (let i = 0; i < leftSideConnections.length; ++ i) {
const connection = leftSideConnections[i];
const yOffset = meta.getPortYOffset(connection.name);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
y: yOffset
});
}
// 计算右侧的
for (let i = 0; i < rightSideConnections.length; ++ i) {
const connection = rightSideConnections[i];
const yOffset = meta.getPortYOffset(connection.name);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: width,
y: yOffset
});
}
const node = {
id: cell.id,
name: cell.name,
renderName: cell.type,
renderType: 'cell',
width,
height,
ports,
layoutOptions: {
'org.eclipse.elk.portConstraints': 'FIXED_POS',
'partitioning.partition': this.commonPartitionIndex
}
};
const node = this.makeCellNode(cell, skin);
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 + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;
ports.push({
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 + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;
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 = {
id: cell.id,
name: cell.name,
renderName: cell.type,
renderType: 'cell',
width: instanceWidth,
height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING,
ports,
layoutOptions: {
// 强制固定 port 的方向
'org.eclipse.elk.portConstraints': 'FIXED_POS',
// TODO: 同名例化模块对齐
'partitioning.partition': this.commonPartitionIndex
}
};
const node = this.makeInstanceNode(cell);
nodes.push(node);
}
}
@ -442,5 +297,168 @@ export class Module {
return [nodes, edges];
}
/**
* @description 制作器件对应的 node
* @param {Cell} cell
* @param {import("../skin").SkinResource} skin
*/
makeCellNode(cell, skin) {
// 内置器件
// 创建器件节点的 port, port 和 connection 一一对应
const meta = skin.meta;
const height = meta.height;
const width = meta.width;
const ports = [];
// 统计分配到左右两侧的 port
const leftSideConnections = [];
const rightSideConnections = [];
for (const conn of cell.nameToConnection.values()) {
if (conn.direction === 'input') {
leftSideConnections.push(conn);
} else {
rightSideConnections.push(conn);
}
}
// 计算左侧的
for (let i = 0; i < leftSideConnections.length; ++ i) {
const connection = leftSideConnections[i];
const yOffset = meta.getPortYOffset(connection.name);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: 0,
y: yOffset
});
}
// 计算右侧的
for (let i = 0; i < rightSideConnections.length; ++ i) {
const connection = rightSideConnections[i];
const yOffset = meta.getPortYOffset(connection.name);
ports.push({
id: connection.id,
renderName: connection.name,
renderType: 'cellPort',
width: LAYOUT_CONSTANT.CELL_PORT_WIDTH,
height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT,
x: width,
y: yOffset
});
}
const node = {
id: cell.id,
name: cell.name,
renderName: cell.type,
renderType: 'cell',
width,
height,
ports,
layoutOptions: {
'org.eclipse.elk.portConstraints': 'FIXED_POS',
'partitioning.partition': this.commonPartitionIndex
}
};
return node;
}
/**
* @description 制作例化模块对应的 node
* @param {Cell} cell
*/
makeInstanceNode(cell) {
// 统计分配到左右两侧的 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 + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;
ports.push({
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 + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING;
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 = {
id: cell.id,
name: cell.name,
parentName: this.name,
renderName: cell.type,
renderType: 'cell',
width: instanceWidth,
height: instanceHeight + LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + LAYOUT_CONSTANT.INSTANCE_TOP_PADDING,
ports,
layoutOptions: {
// 强制固定 port 的方向
'org.eclipse.elk.portConstraints': 'FIXED_POS',
// TODO: 同名例化模块对齐
'partitioning.partition': this.commonPartitionIndex
}
};
return node;
}
}