274 lines
9.5 KiB
JavaScript
274 lines
9.5 KiB
JavaScript
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<string, BasicD3ManagmentItem[]>}
|
||
*/
|
||
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;
|
||
}
|
||
} |