2024-12-29 06:13:22 +08:00

298 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as d3 from 'd3';
import { globalLookup, globalSetting } from '../global';
import { NetlistRender } from '.';
import { LAYOUT_CONSTANT, makeEdgeId } from './layout';
export class CellRender {
/**
*
* @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/
constructor(selection, rootRender) {
this.parentSelection = selection;
this.rootRender = rootRender;
/**
* @type {BasicD3DataItem[]}
*/
this.data = [];
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2manager = rootRender.id2manager;
}
/**
* @description 将 elknode 关于 器件 的数据添加为 d3 数据项目
* @param {ElkNode} node
* @param {HTMLElement} element
*/
addAsD3DataItem(node, element) {
this.data.push({
id: node.id,
element,
x: node.x,
y: node.y,
width: node.width,
height: node.height,
fill: 'var(--main-dark-color)'
});
}
render() {
const data = this.data;
const rootRender = this.rootRender;
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) {
element.setAttribute('stroke-opacity', 0);
}
return element;
})
.each(function (data) {
const cellSelection = d3.select(this);
const manager = _this.createDataManager(cellSelection, data);
// TODO: 实现拖拽
// registerDragEvent(manager, rootRender);
});
if (globalSetting.renderAnimation) {
cellSelections = cellSelections
.transition()
.duration(1000)
.attr('stroke-opacity', 1)
.attr('class', 'grab');
} else {
cellSelections = cellSelections
.attr('class', 'grab');
}
this.selections = cellSelections;
return cellSelections;
}
/**
*
* @param {d3.Selection} selection
* @param {BasicD3DataItem} data
* @returns {BasicD3ManagmentItem}
*/
createDataManager(selection, data) {
const id2manager = this.id2manager;
// 创建拖拽上下文
const dragContext = {
neighbors: [],
elkGraph: {
// elk 是无状态的id 取什么名字都行
id: 'root',
children: [],
edges: [],
layoutOptions: {}
}
}
const managerItem = {
data,
selection,
type: 'cell',
dragContext
};
if (!id2manager.has(data.id)) {
id2manager.set(data.id, []);
}
id2manager.get(data.id).push(managerItem);
return managerItem;
}
}
/**
* @description 注册关于 器件 的拖动事件
*
* 需要提取最小拓扑子图,然后重新调整各个区域的尺寸
* @param {BasicD3ManagmentItem} manager
* @param {NetlistRender} rootRender
*/
export function registerDragEvent(manager, rootRender) {
// 创建拖拽行为
const drag = d3.drag();
drag.on('start', async event => await dragStart(event, manager, rootRender));
drag.on('drag', async event => await dragged(event, manager, rootRender));
drag.on('end', async event => await dragEnd(event, manager, rootRender))
manager.selection.call(drag);
}
/**
*
* @param {d3.D3DragEvent} event
* @param {BasicD3ManagmentItem} manager
* @param {NetlistRender} rootRender
*/
async function dragStart(event, manager, rootRender) {
const selection = manager.selection;
selection.attr('class', 'grabbing');
// 更新当前的 elkGraph
const context = manager.dragContext;
const nodes = [];
const edges = [];
const Avoid = globalLookup.Avoid;
const layoutOptions = {
// node 固定不动
'elk.stress.fixed': true,
// node 的 port 固定不动
'elk.portConstraints': 'FIXED_POS',
// 从左到右
'elk.direction': 'RIGHT'
};
// 添加自己的节点
const cell = manager.selection.datum();
// 获取 connection
const id2manager = rootRender.id2manager;
const cellNode = {
id: cell.id,
x: cell.x,
y: cell.y,
width: cell.width,
height: cell.height,
// 后续遍历时获取该 port
ports: [],
layoutOptions
};
// 添加邻居的节点和边
for (const neighbor of context.neighbors) {
const ndata = neighbor.selection.datum();
const node = {
id: ndata.id,
width: ndata.width,
height: ndata.height,
x: ndata.x,
y: ndata.y,
ports: [],
layoutOptions
};
// 加入 port如果有的话
for (const connection of neighbor.connections) {
const conndata = connection.selection.datum();
node.ports.push({
id: conndata.id,
width: conndata.width,
height: conndata.height,
x: conndata.x,
y: conndata.y
});
}
nodes.push(node);
edges.push({
id: neighbor.edge.id,
source: neighbor.edge.source,
sourcePort: neighbor.edge.sourcePort,
target: neighbor.edge.target,
targetPort: neighbor.edge.targetPort
});
if (neighbor.location === 'source') {
// 此时 cell 的 connection 就是 targetPort
const cellConnectionId = neighbor.edge.targetPort;
const connectionManager = id2manager.get(cellConnectionId)[0];
const conndata = connectionManager.data;
cellNode.ports.push({
id: conndata.id,
width: conndata.width,
height: conndata.height,
x: conndata.x,
y: conndata.y
});
} else {
// 此时 cell 的 connection 就是 sourcePort
const cellConnectionId = neighbor.edge.sourcePort;
const connectionManager = id2manager.get(cellConnectionId)[0];
const conndata = connectionManager.data;
cellNode.ports.push({
id: conndata.id,
width: conndata.width,
height: conndata.height,
x: conndata.x,
y: conndata.y
});
}
}
nodes.push(cellNode);
context.cellNode = cellNode;
context.elkGraph.children = nodes;
context.elkGraph.edges = edges;
context.elkGraph.layoutOptions = {
'elk.algorithm': 'layered'
};
}
/**
*
* @param {d3.D3DragEvent} event
* @param {BasicD3ManagmentItem} manager
* @param {NetlistRender} rootRender
*/
async function dragged(event, manager, rootRender) {
// 当拖动结束时D3 会根据绑定的数据data.x 和 data.y重新渲染元素导致元素回到初始位置。
// 所以需要 手动更新 data.x 和 data.y
const selection = manager.selection;
const data = manager.data;
data.x = event.x;
data.y = event.y;
selection
.attr('x', event.x)
.attr('y', event.y);
// 根据最小拓扑图,提取出关键点,重新计算布局
const context = manager.dragContext;
const elkGraph = context.elkGraph;
const currentNode = elkGraph.children.at(-1);
currentNode.x = event.x;
currentNode.y = event.y;
// const computedLayout = await rootRender.elk.layout(elkGraph);
}
/**
*
* @param {d3.D3DragEvent} event
* @param {BasicD3ManagmentItem} manager
* @param {NetlistRender} rootRender
*/
async function dragEnd(event, manager, rootRender) {
const selection = manager.selection;
selection.attr('class', 'grab');
}