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} */ 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'); }