完成 d3 数据项目 + 渲染的架构调整
This commit is contained in:
parent
2a1dc56148
commit
425c513635
@ -34,4 +34,13 @@ onMounted(async () => {
|
||||
align-items: center;
|
||||
transition: var(--animation-5s);
|
||||
}
|
||||
|
||||
.grab {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.grabbing {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
</style>
|
@ -5,11 +5,30 @@ export class CellRender {
|
||||
/**
|
||||
*
|
||||
* @param {d3.Selection} selection
|
||||
* @param {BasicD3DataItem} cells
|
||||
*/
|
||||
constructor(selection, cells) {
|
||||
this.parentSelection = selection;
|
||||
this.data = cells;
|
||||
|
||||
/**
|
||||
* @type {BasicD3DataItem[]}
|
||||
*/
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将 elknode 关于 器件 的数据添加为 d3 数据项目
|
||||
* @param {ElkNode} node
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
addAsD3DataItem(node, element) {
|
||||
this.data.push({
|
||||
element,
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)'
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -32,12 +51,23 @@ export class CellRender {
|
||||
cellSelections = cellSelections
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.attr('stroke-opacity', 1);
|
||||
.attr('stroke-opacity', 1)
|
||||
.attr('class', 'grab')
|
||||
.on('end', function (data) {
|
||||
console.log('enter end');
|
||||
|
||||
const cellSelection = d3.select(this);
|
||||
registerDragEvent(cellSelection, data);
|
||||
});
|
||||
} else {
|
||||
cellSelections = cellSelections
|
||||
.attr('class', 'grab')
|
||||
.each(function (data) {
|
||||
console.log('enter end'); // 在这里执行你需要的逻辑
|
||||
const cellSelection = d3.select(this);
|
||||
registerDragEvent(cellSelection, data);
|
||||
});
|
||||
}
|
||||
cellSelections.on('end', function (data) {
|
||||
const cellSelection = d3.select(this);
|
||||
registerDragEvent(cellSelection, data);
|
||||
});
|
||||
|
||||
this.selections = cellSelections;
|
||||
return cellSelections;
|
||||
@ -54,10 +84,22 @@ export class CellRender {
|
||||
export function registerDragEvent(selection, data) {
|
||||
// 创建拖拽行为
|
||||
const drag = d3.drag();
|
||||
drag.on("drag", event => dragged(event, selection, data));
|
||||
|
||||
drag.on('start', event => dragStart(event, selection, data));
|
||||
drag.on('drag', event => dragged(event, selection, data));
|
||||
drag.on('end', event => dragEnd(event, selection, data))
|
||||
|
||||
selection.call(drag);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {d3.D3DragEvent} event
|
||||
* @param {d3.Selection} selection
|
||||
*/
|
||||
function dragStart(event, selection, data) {
|
||||
selection.attr('class', 'grabbing');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -67,5 +109,17 @@ export function registerDragEvent(selection, data) {
|
||||
function dragged(event, selection, data) {
|
||||
data.x = event.x;
|
||||
data.y = event.y;
|
||||
selection.attr('x', event.x).attr('y', event.y);
|
||||
|
||||
selection
|
||||
.attr('x', event.x)
|
||||
.attr('y', event.y);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {d3.D3DragEvent} event
|
||||
* @param {d3.Selection} selection
|
||||
*/
|
||||
function dragEnd(event, selection, data) {
|
||||
selection.attr('class', 'grab');
|
||||
}
|
@ -3,13 +3,32 @@ import { globalSetting } from '../global';
|
||||
|
||||
export class ConnectionRender {
|
||||
/**
|
||||
*
|
||||
* @param {d3.Selection} selection
|
||||
* @param {BasicD3DataItem} connections
|
||||
* @param {d3.Selection} selection
|
||||
*/
|
||||
constructor(selection, connections) {
|
||||
constructor(selection) {
|
||||
this.parentSelection = selection;
|
||||
this.data = connections;
|
||||
|
||||
/**
|
||||
* @type {BasicD3DataItem[]}
|
||||
*/
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将 elknode 关于 module connection 的数据添加为 d3 数据项目
|
||||
* @param {ElkPort} cellPort 连接点对象
|
||||
* @param {ElkNode} node 当前的实体(port/例化模块/器件)
|
||||
*/
|
||||
addAsD3DataItem(cellPort, node) {
|
||||
this.data.push({
|
||||
x: cellPort.x + node.x,
|
||||
y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽
|
||||
width: cellPort.width,
|
||||
height: cellPort.height,
|
||||
fill: 'var(--main-color)',
|
||||
text: '',
|
||||
r: 3.5
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -32,39 +51,9 @@ export class ConnectionRender {
|
||||
|
||||
connectionSelections
|
||||
.attr('fill', d => d.fill)
|
||||
.attr('r', d => d.r)
|
||||
.on('end', function (data) {
|
||||
const connectionSelection = d3.select(this);
|
||||
registerDragEvent(connectionSelection, data);
|
||||
});
|
||||
.attr('r', d => d.r);
|
||||
|
||||
this.selections = connectionSelections;
|
||||
return connectionSelections;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 注册关于 器件 的拖动事件
|
||||
*
|
||||
* 需要提取最小拓扑子图,然后重新调整各个区域的尺寸
|
||||
* @param {d3.Selection} selection
|
||||
* @param {any} data
|
||||
*/
|
||||
export function registerDragEvent(selection, data) {
|
||||
// 创建拖拽行为
|
||||
const drag = d3.drag();
|
||||
drag.on("drag", event => dragged(event, selection, data));
|
||||
selection.call(drag);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {d3.D3DragEvent} event
|
||||
* @param {d3.Selection} selection
|
||||
*/
|
||||
function dragged(event, selection, data) {
|
||||
data.x = event.x;
|
||||
data.y = event.y;
|
||||
selection.attr('x', event.x).attr('y', event.y);
|
||||
}
|
@ -4,11 +4,11 @@ import ELK from 'elkjs';
|
||||
|
||||
import { Module } from './layout';
|
||||
import { globalLookup, globalSetting } from '../global';
|
||||
import { registerCellDragEvent } from './drag';
|
||||
import { PortRender } from './port';
|
||||
import { InstantiationRender } from './instantiation';
|
||||
import { CellRender } from './cell';
|
||||
import { ConnectionRender } from './connection';
|
||||
import { WireRender } from './wire';
|
||||
|
||||
export class NetlistRender {
|
||||
/**
|
||||
@ -129,112 +129,77 @@ export class NetlistRender {
|
||||
.attr('width', this.renderWidth)
|
||||
.attr('height', this.renderHeight);
|
||||
|
||||
await this.renderLine(svg, computedLayout, ratio);
|
||||
await this.renderEntity(svg, computedLayout, ratio);
|
||||
|
||||
// 将分组作为一个后续操作的 parent selection
|
||||
const g = svg.append('g');
|
||||
|
||||
await this.renderLine(g, computedLayout, ratio);
|
||||
await this.renderEntity(g, computedLayout, ratio);
|
||||
|
||||
// svg 挂载为全局注册的 selection
|
||||
this.selection = svg;
|
||||
|
||||
// 注册平移和缩放
|
||||
// this.registerRenderTransform(g);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 绘制实体
|
||||
* @param {d3.Selection} svg
|
||||
* @param {d3.Selection} parentSelection
|
||||
* @param {ElkNode} computedLayout
|
||||
* @param {number} ratio
|
||||
*/
|
||||
async renderEntity(svg, computedLayout, ratio) {
|
||||
async renderEntity(parentSelection, computedLayout, ratio) {
|
||||
// node 可能是如下的几类
|
||||
// - module 的 port
|
||||
// - 器件(基础器件 & 例化模块)
|
||||
// - 器件的 port
|
||||
|
||||
// 生成用于绘制的 d3 数据结构
|
||||
// 默认需要渲染成矩形的(缺失样式的器件、例化模块等等)
|
||||
const ports = [];
|
||||
const instantiations = [];
|
||||
|
||||
const cells = [];
|
||||
const connections = [];
|
||||
|
||||
const skinManager = globalLookup.skinManager;
|
||||
|
||||
// TODO: 把下面的逻辑融合进入 render 中
|
||||
// 创建各个主要实体的 render
|
||||
this.cellRender = new CellRender(parentSelection);
|
||||
this.portRender = new PortRender(parentSelection);
|
||||
this.instantiationRender = new InstantiationRender(parentSelection);
|
||||
this.connectionRender = new ConnectionRender(parentSelection);
|
||||
|
||||
for (const node of computedLayout.children) {
|
||||
const skin = skinManager.querySkin(node.renderName);
|
||||
if (skin) {
|
||||
// 具有 skin 的器件
|
||||
cells.push({
|
||||
element: skin.meta.svgDoc.documentElement,
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)',
|
||||
});
|
||||
this.cellRender.addAsD3DataItem(node, skin.meta.svgDoc.documentElement);
|
||||
} else {
|
||||
if (node.renderType === 'port') {
|
||||
ports.push({
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)',
|
||||
text: node.renderName,
|
||||
rx: 3,
|
||||
ry: 3
|
||||
});
|
||||
this.portRender.addAsD3DataItem(node);
|
||||
} else {
|
||||
// 没有 skin 的器件或者端口
|
||||
instantiations.push({
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)',
|
||||
text: node.renderName,
|
||||
rx: 3,
|
||||
ry: 3
|
||||
});
|
||||
this.instantiationRender.addAsD3DataItem(node);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果存在 port,绘制 port
|
||||
for (const cellPort of node.ports || []) {
|
||||
connections.push({
|
||||
x: cellPort.x + node.x,
|
||||
y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽
|
||||
width: cellPort.width,
|
||||
height: cellPort.height,
|
||||
fill: 'var(--main-color)',
|
||||
text: '',
|
||||
r: 3.5
|
||||
});
|
||||
this.connectionRender.addAsD3DataItem(cellPort, node);
|
||||
}
|
||||
}
|
||||
|
||||
this.portRender = new PortRender(svg, ports);
|
||||
this.portRender.render();
|
||||
|
||||
// this.instantiationRender = new InstantiationRender(svg, instantiations);
|
||||
// TODO: 实现它
|
||||
// this.instantiationRender.render();
|
||||
|
||||
this.cellRender = new CellRender(svg, cells);
|
||||
this.cellRender.render();
|
||||
|
||||
this.connectionRender = new ConnectionRender(svg, connections);
|
||||
this.connectionRender.render();
|
||||
this.connectionRender.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 绘制连线
|
||||
* @param {d3.Selection} svg
|
||||
* @param {d3.Selection} parentSelection
|
||||
* @param {ElkNode} computedLayout
|
||||
* @param {number} ratio
|
||||
*/
|
||||
async renderLine(svg, computedLayout, ratio) {
|
||||
const lines = [];
|
||||
async renderLine(parentSelection, computedLayout, ratio) {
|
||||
|
||||
this.wireRender = new WireRender(parentSelection);
|
||||
|
||||
for (const edge of computedLayout.edges) {
|
||||
for (const section of edge.sections || []) {
|
||||
const points = [];
|
||||
@ -243,47 +208,60 @@ export class NetlistRender {
|
||||
points.push(point);
|
||||
}
|
||||
points.push(section.endPoint);
|
||||
|
||||
for (let i = 0; i < points.length - 1; ++ i) {
|
||||
lines.push({
|
||||
x1: points[i].x,
|
||||
y1: points[i].y,
|
||||
x2: points[i + 1].x,
|
||||
y2: points[i + 1].y,
|
||||
strokeWidth: 2,
|
||||
color: 'var(--foreground)'
|
||||
});
|
||||
}
|
||||
this.wireRender.addAsD3DataItems(points);
|
||||
}
|
||||
}
|
||||
|
||||
let lineSelection = svg.selectAll('line')
|
||||
.data(lines)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('x1', data => data.x1)
|
||||
.attr('y1', data => data.y1)
|
||||
.attr('x2', data => data.x2)
|
||||
.attr('y2', data => data.y2)
|
||||
.attr('stroke', data => data.color);
|
||||
|
||||
if (globalSetting.renderAnimation) {
|
||||
lineSelection = lineSelection.transition().duration(1000);
|
||||
}
|
||||
this.wireRender.render();
|
||||
}
|
||||
|
||||
lineSelection.attr('stroke-width', data => data.strokeWidth);
|
||||
/**
|
||||
* @description 注册平移和缩放
|
||||
* @param {d3.Selection} parentSelection
|
||||
*/
|
||||
registerRenderTransform(parentSelection) {
|
||||
// 创建缩放行为
|
||||
const zoom = d3.zoom()
|
||||
// 设置缩放范围(最小缩放比例 0.5,最大缩放比例 5)
|
||||
.scaleExtent([0.5, 5])
|
||||
.on("zoom", zoomed);
|
||||
|
||||
// 将缩放行为应用到 SVG
|
||||
this.selection.call(zoom);
|
||||
|
||||
// 缩放事件处理函数
|
||||
function zoomed(event) {
|
||||
const { transform, sourceEvent } = event;
|
||||
|
||||
if (sourceEvent && sourceEvent.type === "wheel") {
|
||||
if (globalSetting.renderAnimation) {
|
||||
parentSelection
|
||||
.transition()
|
||||
.duration(350)
|
||||
.attr('transform', transform);
|
||||
} else {
|
||||
parentSelection.attr('transform', transform);
|
||||
}
|
||||
} else if (sourceEvent && sourceEvent.type === "mousemove") {
|
||||
if (globalSetting.renderAnimation) {
|
||||
parentSelection.attr('transform', transform);
|
||||
} else {
|
||||
parentSelection.attr('transform', transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从 globalLookup 中更新 svg 的方位
|
||||
*/
|
||||
updateLocationFromGlobal() {
|
||||
const svg = globalLookup.netlistRender.selection;
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
// const svg = globalLookup.netlistRender.selection;
|
||||
// if (!svg) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
svg.attr('transform', `translate(${globalLookup.svgTranslateX}, ${globalLookup.svgTranslateY}) scale(${globalLookup.svgScale})`);
|
||||
// svg.attr('transform', `translate(${globalLookup.svgTranslateX}, ${globalLookup.svgTranslateY}) scale(${globalLookup.svgScale})`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,5 +7,27 @@ export class InstantiationRender {
|
||||
*/
|
||||
constructor(selection) {
|
||||
this.selection = selection;
|
||||
|
||||
/**
|
||||
* @type {BasicD3DataItem[]}
|
||||
*/
|
||||
this.data = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将 elknode 关于 例化模块 的数据添加为 d3 数据项目
|
||||
* @param {ElkNode} node
|
||||
*/
|
||||
addAsD3DataItem(node) {
|
||||
this.data.push({
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)',
|
||||
text: node.renderName,
|
||||
rx: 3,
|
||||
ry: 3
|
||||
});
|
||||
}
|
||||
}
|
@ -6,11 +6,30 @@ export class PortRender {
|
||||
/**
|
||||
*
|
||||
* @param {d3.Selection} selection
|
||||
* @param {BasicD3DataItem} ports
|
||||
*/
|
||||
constructor(selection, ports) {
|
||||
constructor(selection) {
|
||||
this.parentSelection = selection;
|
||||
this.data = ports;
|
||||
/**
|
||||
* @type {BasicD3DataItem[]}
|
||||
*/
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将 elknode 关于 port 的数据添加为 d3 数据项目
|
||||
* @param {ElkNode} node
|
||||
*/
|
||||
addAsD3DataItem(node) {
|
||||
this.data.push({
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
fill: 'var(--main-dark-color)',
|
||||
text: node.renderName,
|
||||
rx: 3,
|
||||
ry: 3
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -26,6 +45,7 @@ export class PortRender {
|
||||
.attr('height', data => data.height)
|
||||
.attr('fill', d => d.fill);
|
||||
|
||||
|
||||
portSelections.append("text")
|
||||
.attr("x", d => d.width / 2) // 文本的 x 坐标(居中)
|
||||
.attr("y", d => d.height / 2) // 文本的 y 坐标(居中)
|
||||
@ -38,16 +58,28 @@ export class PortRender {
|
||||
portSelections = portSelections
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.attr('stroke', 'var(--main-color)')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('rx', d => d.rx)
|
||||
.attr('ry', d => d.ry)
|
||||
.attr('class', 'grab')
|
||||
.on('end', function (data) {
|
||||
const portSelection = d3.select(this);
|
||||
registerDragEvent(portSelection, data);
|
||||
});
|
||||
} else {
|
||||
portSelections
|
||||
.attr('stroke', 'var(--main-color)')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('rx', d => d.rx)
|
||||
.attr('ry', d => d.ry)
|
||||
.attr('class', 'grab')
|
||||
.each(function (data) {
|
||||
const portSelection = d3.select(this);
|
||||
registerDragEvent(portSelection, data);
|
||||
});
|
||||
}
|
||||
portSelections
|
||||
.attr('stroke', 'var(--main-color)')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('rx', d => d.rx)
|
||||
.attr('ry', d => d.ry)
|
||||
.on('end', function (data) {
|
||||
const portSelection = d3.select(this);
|
||||
registerDragEvent(portSelection, data);
|
||||
});
|
||||
|
||||
|
||||
this.selections = portSelections;
|
||||
return portSelections;
|
||||
@ -64,10 +96,22 @@ export class PortRender {
|
||||
export function registerDragEvent(selection, data) {
|
||||
// 创建拖拽行为
|
||||
const drag = d3.drag();
|
||||
|
||||
drag.on("start", event => dragStart(event, selection, data));
|
||||
drag.on("drag", event => dragged(event, selection, data));
|
||||
drag.on("end", event => dragEnd(event, selection, data));
|
||||
|
||||
selection.call(drag);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {d3.D3DragEvent} event
|
||||
* @param {d3.Selection} selection
|
||||
*/
|
||||
function dragStart(event, selection, data) {
|
||||
selection.attr('class', 'grabbing');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -78,4 +122,13 @@ function dragged(event, selection, data) {
|
||||
data.x = event.x;
|
||||
data.y = event.y;
|
||||
selection.attr('x', event.x).attr('y', event.y);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {d3.D3DragEvent} event
|
||||
* @param {d3.Selection} selection
|
||||
*/
|
||||
function dragEnd(event, selection, data) {
|
||||
selection.attr('class', 'grab');
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import * as d3 from 'd3';
|
||||
import { globalSetting } from '../global';
|
||||
|
||||
export class WireRender {
|
||||
/**
|
||||
@ -7,5 +8,50 @@ export class WireRender {
|
||||
*/
|
||||
constructor(selection) {
|
||||
this.selection = selection;
|
||||
|
||||
/**
|
||||
* @type {BasicD3DataItem[]}
|
||||
*/
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将 elknode 关于 wire 的数据添加为 d3 数据项目
|
||||
* @param {ElkPoint[]} points
|
||||
*/
|
||||
addAsD3DataItems(points) {
|
||||
for (let i = 0; i < points.length - 1; ++ i) {
|
||||
this.data.push({
|
||||
x1: points[i].x,
|
||||
y1: points[i].y,
|
||||
x2: points[i + 1].x,
|
||||
y2: points[i + 1].y,
|
||||
strokeWidth: 2,
|
||||
color: 'var(--foreground)'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.data;
|
||||
|
||||
let lineSelections = this.selection.selectAll('line')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('x1', data => data.x1)
|
||||
.attr('y1', data => data.y1)
|
||||
.attr('x2', data => data.x2)
|
||||
.attr('y2', data => data.y2)
|
||||
.attr('stroke', data => data.color);
|
||||
|
||||
if (globalSetting.renderAnimation) {
|
||||
lineSelections = lineSelections
|
||||
.transition()
|
||||
.duration(1000);
|
||||
}
|
||||
|
||||
lineSelections.attr('stroke-width', data => data.strokeWidth);
|
||||
// line 就不注册拖拽事件了
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user