import * as d3 from 'd3'; import { NetlistRender } from '.'; import { globalSetting } from '../global'; import { LAYOUT_CONSTANT } from './layout'; import { svgResource } from '../skin/draw'; export class InstantiationRender { /** * * @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 {import('../jsdoc').ElkNode} node */ addAsD3DataItem(node) { const nodeModule = this.rootRender.nameToModule.get(node.renderName); const view = nodeModule.view; const textPadding = 5; const portnames = []; // 当前的例化是否为一个展开的例化 this.isExpanded = (node.children || []).length > 0; const topPadding = this.isExpanded ? 25 : 0; 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 - LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN - LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN; const portY = this.isExpanded ? port.y - 10: port.y; portnames.push({ name: port.renderName, x: portX, y: portY, align }); } this.data.push({ id: node.id, type: node.renderName, // 例化模块的模块名字 x: node.x, y: node.y, name: node.name, width: node.width, height: node.height + topPadding, topPadding, text: node.renderName, portnames, rx: 3, ry: 3, // 数据本身 _self: node }); } render() { const data = this.data; const rootRender = this.rootRender; const id2manager = this.id2manager; const _this = this; // 外部控制主体的 g let instantiationSelections = this.parentSelection.selectAll('g.instance') .data(data) .enter() .append('g') .attr('class', 'instance') .attr("transform", d => `translate(${d.x}, ${d.y})`); // 例化模块的方块 let instances = instantiationSelections.append('rect') .attr('x', LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', d => LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT - d.topPadding) .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', LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', d => LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT - 8 - d.topPadding) .attr('dominant-baseline', 'middle') .attr('text-anchor', 'left') .attr('fill', 'var(--foreground)') .attr('font-size', '14px') .attr('class', 'port-caption') .text(data => data.name) .each(function(data) { const text = d3.select(this); const bbox = text.node().getBBox(); data.textX = (data.width - bbox.width) / 2; }) .attr('x', d => d.textX) // 渲染 portnames instantiationSelections.each(function(node) { d3.select(this) .selectAll('.port-name') .data(node.portnames) .enter() .append('text') .attr('class', 'port-name') .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) .attr('fill', 'var(--foreground)') .attr('font-size', '11px') .text(d => d.name); }); // 增加一个背景方块,防止 svg 点不到 let bgFullScreenSelections = instantiationSelections.append('rect') .attr('x', 5 + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', d => LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + 5 - d.topPadding) .attr('width', 20) .attr('height', 20) .attr('opacity', 0) .attr('class', 'pointer') .on('click', function(data) { const selection = d3.select(this); const d = selection.datum(); const children = d._self.children || []; if (children.length > 0) { rootRender.collapseInstance(selection); } else { rootRender.expandInstance(selection); } }); const id2element = new Map(); // 渲染左上角的放大缩小图标,并注册对应的展开/收起事件 let fullSreenSelections = instantiationSelections.append(data => { // 获取全屏图标 const element = svgResource.get('full-screen'); element.setAttribute('x', 5 + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN); element.setAttribute('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + 5 - data.topPadding); element.setAttribute('width', 20); element.setAttribute('height', 20); element.setAttribute('class', 'pointer'); return element; }) .each(function(data) { const svgElement = d3.select(this); id2element.set(data.id, svgElement); }) .on('mouseenter', function() { const svgElement = d3.select(this); svgElement.selectAll("*") .attr("fill", "var(--instance-color)"); }) .on('mouseleave', function() { const svgElement = d3.select(this); svgElement.selectAll("*") .attr("fill", "var(--foreground)"); }) .on('click', function(data) { const selection = d3.select(this); const d = selection.datum(); const children = d._self.children || []; if (children.length > 0) { rootRender.collapseInstance(selection); } else { rootRender.expandInstance(selection); } }); bgFullScreenSelections .on('mouseenter', function(_, data) { const svgElement = id2element.get(data.id); svgElement.selectAll("*") .attr("fill", "var(--instance-color)"); }) .on('mouseleave', function(_, data) { const svgElement = id2element.get(data.id); svgElement.selectAll("*") .attr("fill", "var(--foreground)"); }); if (globalSetting.renderAnimation) { instances .transition() .duration(1000) .attr('fill', d => { const children = d._self.children || []; return children.length > 0 ? 'rgba(203, 129, 218, 0.1)' : 'var(--instance-fill-color)'; }) .attr('stroke', 'var(--instance-color)') .attr('stroke-width', 2); } else { instances .attr('fill', d => { const children = d._self.children || []; return children.length > 0 ? 'rgba(203, 129, 218, 0.1)' : 'var(--instance-fill-color)'; }) .attr('stroke', 'var(--instance-color)') .attr('stroke-width', 2) } instantiationSelections .attr('class', 'grab') .each(function (data) { const portSelection = d3.select(this); // const manager = _this.createDataManager(portSelection, data); // TODO: 实现拖拽 // registerDragEvent(manager, rootRender); }); this.selections = instantiationSelections; return instantiationSelections; } /** * * @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; } }