298 lines
8.4 KiB
JavaScript
298 lines
8.4 KiB
JavaScript
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');
|
||
} |