新的 skin

This commit is contained in:
锦恢 2024-12-25 23:18:21 +08:00
parent 5a7dd7f6f0
commit e06f6abc50
9 changed files with 186 additions and 71 deletions

Binary file not shown.

View File

@ -211,12 +211,26 @@
/** /**
* @typedef BasicD3DataItem * @typedef BasicD3DataItem
* @property {number} x * @property {string} id 全局唯一标识
* @property {number} y * @property {number} x svg 布局下的 x
* @property {number} width * @property {number} y svg 布局下的 y
* @property {number} height * @property {number} width 宽度
* @property {string} [fill] * @property {number} height 高度
* @property {string} [text] * @property {string} [fill] 填充颜色
* @property {number} [rx] * @property {string} [text] 填充文字
* @property {number} [ry] * @property {number} [rx] 圆角 rx
* @property {number} [ry] 圆角 ry
*/
/**
* @typedef BasicD3ManagmentItem
* @property {'cell' | 'connection' | 'instantiation' | 'port' | 'wire'} type
* @property {BasicD3DataItem} data
* @property {d3.Selection} selection
*/
/**
* @typedef DragContext
* @property {BasicD3DataItem} data
* @property {ElkGraph} elkGraph
*/ */

View File

@ -16,6 +16,12 @@ export class CellRender {
* @type {BasicD3DataItem[]} * @type {BasicD3DataItem[]}
*/ */
this.data = []; this.data = [];
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2selections = rootRender.id2selections;
} }
/** /**
@ -25,6 +31,7 @@ export class CellRender {
*/ */
addAsD3DataItem(node, element) { addAsD3DataItem(node, element) {
this.data.push({ this.data.push({
id: node.id,
element, element,
x: node.x, x: node.x,
y: node.y, y: node.y,
@ -37,6 +44,7 @@ export class CellRender {
render() { render() {
const data = this.data; const data = this.data;
const rootRender = this.rootRender; const rootRender = this.rootRender;
const id2selections = this.id2selections;
let cellSelections = this.parentSelection.selectAll('svg') let cellSelections = this.parentSelection.selectAll('svg')
.data(data) .data(data)
@ -59,7 +67,6 @@ export class CellRender {
.attr('class', 'grab') .attr('class', 'grab')
.on('end', function (data) { .on('end', function (data) {
const cellSelection = d3.select(this); const cellSelection = d3.select(this);
registerDragEvent(cellSelection, data, rootRender); registerDragEvent(cellSelection, data, rootRender);
}); });
} else { } else {
@ -67,11 +74,22 @@ export class CellRender {
.attr('class', 'grab') .attr('class', 'grab')
.each(function (data) { .each(function (data) {
const cellSelection = d3.select(this); const cellSelection = d3.select(this);
registerDragEvent(cellSelection, data, rootRender); registerDragEvent(cellSelection, data, rootRender);
}); });
} }
cellSelections.each(function (data) {
const selection = d3.select(this);
if (!id2selections.has(data.id)) {
id2selections.set(data.id, []);
}
id2selections.get(data.id).push({
data,
selection,
type: 'cell'
});
});
this.selections = cellSelections; this.selections = cellSelections;
return cellSelections; return cellSelections;
} }
@ -89,9 +107,22 @@ export function registerDragEvent(selection, data, rootRender) {
// 创建拖拽行为 // 创建拖拽行为
const drag = d3.drag(); const drag = d3.drag();
drag.on('start', async event => dragStart(event, selection, data)); const dragContext = {
drag.on('drag', async event => dragged(event, selection, data, rootRender)); data: data,
drag.on('end', async event => dragEnd(event, selection, data)) elkGraph: {
id: 'root',
children: [],
edges: [],
layoutOptions: {
// 伟大,无需多言
'elk.algorithm': ''
}
}
}
drag.on('start', async event => dragStart(event, selection, dragContext, rootRender));
drag.on('drag', async event => dragged(event, selection, dragContext, rootRender));
drag.on('end', async event => dragEnd(event, selection, dragContext, rootRender))
selection.call(drag); selection.call(drag);
} }
@ -100,28 +131,36 @@ export function registerDragEvent(selection, data, rootRender) {
* *
* @param {d3.D3DragEvent} event * @param {d3.D3DragEvent} event
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {DragContext} dragContext
* @param {NetlistRender} rootRender
*/ */
function dragStart(event, selection, data) { function dragStart(event, selection, dragContext, rootRender) {
selection.attr('class', 'grabbing'); selection.attr('class', 'grabbing');
// 更新拓扑图中各个节点的节点坐标
} }
/** /**
* *
* @param {d3.D3DragEvent} event * @param {d3.D3DragEvent} event
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {DragContext} dragContext
* @param {NetlistRender} rootRender * @param {NetlistRender} rootRender
*/ */
function dragged(event, selection, data, rootRender) { function dragged(event, selection, dragContext, rootRender) {
// 当拖动结束时D3 会根据绑定的数据data.x 和 data.y重新渲染元素导致元素回到初始位置。 // 当拖动结束时D3 会根据绑定的数据data.x 和 data.y重新渲染元素导致元素回到初始位置。
// 所以需要 手动更新 data.x 和 data.y // 所以需要 手动更新 data.x 和 data.y
data.x = event.x; dragContext.data.x = event.x;
data.y = event.y; dragContext.data.y = event.y;
selection selection
.attr('x', event.x) .attr('x', event.x)
.attr('y', event.y); .attr('y', event.y);
// 根据最小拓扑图,提取出关键点,重新计算布局 // 根据最小拓扑图,提取出关键点,重新计算布局
console.log(dragContext.data);
} }
@ -129,7 +168,9 @@ function dragged(event, selection, data, rootRender) {
* *
* @param {d3.D3DragEvent} event * @param {d3.D3DragEvent} event
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {DragContext} dragContext
* @param {NetlistRender} rootRender
*/ */
function dragEnd(event, selection, data) { function dragEnd(event, selection, dragContext, rootRender) {
selection.attr('class', 'grab'); selection.attr('class', 'grab');
} }

View File

@ -1,17 +1,26 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { globalSetting } from '../global'; import { globalSetting } from '../global';
import { NetlistRender } from '.';
export class ConnectionRender { export class ConnectionRender {
/** /**
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/ */
constructor(selection) { constructor(selection, rootRender) {
this.parentSelection = selection; this.parentSelection = selection;
this.rootRender = rootRender;
/** /**
* @type {BasicD3DataItem[]} * @type {BasicD3DataItem[]}
*/ */
this.data = []; this.data = [];
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2selections = rootRender.id2selections;
} }
/** /**
@ -21,6 +30,7 @@ export class ConnectionRender {
*/ */
addAsD3DataItem(cellPort, node) { addAsD3DataItem(cellPort, node) {
this.data.push({ this.data.push({
id: cellPort.id,
x: cellPort.x + node.x, x: cellPort.x + node.x,
y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽 y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽
width: cellPort.width, width: cellPort.width,
@ -33,6 +43,7 @@ export class ConnectionRender {
render() { render() {
const data = this.data; const data = this.data;
const id2selections = this.id2selections;
let connectionSelections = this.parentSelection.selectAll('circle') let connectionSelections = this.parentSelection.selectAll('circle')
.data(data) .data(data)
@ -51,7 +62,18 @@ export class ConnectionRender {
connectionSelections connectionSelections
.attr('fill', d => d.fill) .attr('fill', d => d.fill)
.attr('r', d => d.r); .attr('r', d => d.r)
.each(function (data) {
const selection = d3.select(this);
if (!id2selections.has(data.id)) {
id2selections.set(data.id, []);
}
id2selections.get(data.id).push({
data,
selection,
type: 'connection'
});
});
this.selections = connectionSelections; this.selections = connectionSelections;
return connectionSelections; return connectionSelections;

View File

@ -24,7 +24,15 @@ export class NetlistRender {
this.elkGraph = { this.elkGraph = {
id: 'root', id: 'root',
children: [], children: [],
edges: [] edges: [],
layoutOptions: {
// org.eclipse. 可以去除
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
'elk.spacing.nodeNode': 35,
'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
'elk.algorithm': 'layered',
'elk.partitioning.activate': true
}
}; };
/** /**
@ -52,6 +60,11 @@ export class NetlistRender {
*/ */
this.startOffsetY = 0; this.startOffsetY = 0;
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2selections = new Map();
} }
/** /**
@ -76,13 +89,6 @@ export class NetlistRender {
const cellNodes = module.makeCellsElkNodes(); const cellNodes = module.makeCellsElkNodes();
const [constantNodes, connectionEdges] = module.makeConnectionElkNodes(); const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();
// debug
for (const node of portNodes) {
if (node.type === 'output') {
node.x = 400;
}
}
// 挂载到渲染图中 // 挂载到渲染图中
this.elkGraph.children.push(...portNodes); this.elkGraph.children.push(...portNodes);
this.elkGraph.children.push(...cellNodes); this.elkGraph.children.push(...cellNodes);
@ -101,16 +107,7 @@ export class NetlistRender {
async createLayout() { async createLayout() {
const graph = this.elkGraph; const graph = this.elkGraph;
const start = performance.now(); const start = performance.now();
const layoutGraph = await this.elk.layout(graph, { const layoutGraph = await this.elk.layout(graph);
layoutOptions: {
// org.eclipse. 可以去除
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
'elk.spacing.nodeNode': 35,
'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
'elk.algorithm': 'layered',
'elk.partitioning.activate': true
}
});
const timecost = (performance.now() - start).toFixed(3); const timecost = (performance.now() - start).toFixed(3);
console.log( console.log(
`%c 布局计算耗时 ${timecost} ms`, `%c 布局计算耗时 ${timecost} ms`,
@ -197,10 +194,10 @@ export class NetlistRender {
const skinManager = globalLookup.skinManager; const skinManager = globalLookup.skinManager;
// 创建各个主要实体的 render // 创建各个主要实体的 render
this.cellRender = new CellRender(parentSelection); this.cellRender = new CellRender(parentSelection, this);
this.portRender = new PortRender(parentSelection); this.portRender = new PortRender(parentSelection, this);
this.instantiationRender = new InstantiationRender(parentSelection); this.instantiationRender = new InstantiationRender(parentSelection, this);
this.connectionRender = new ConnectionRender(parentSelection); this.connectionRender = new ConnectionRender(parentSelection, this);
for (const node of computedLayout.children) { for (const node of computedLayout.children) {
// 只计算形体的,因为 连接点 非常小,几乎不影响布局 // 只计算形体的,因为 连接点 非常小,几乎不影响布局
@ -239,8 +236,7 @@ export class NetlistRender {
* @param {number} ratio * @param {number} ratio
*/ */
async renderLine(parentSelection, computedLayout, ratio) { async renderLine(parentSelection, computedLayout, ratio) {
this.wireRender = new WireRender(parentSelection, this);
this.wireRender = new WireRender(parentSelection);
for (const edge of computedLayout.edges) { for (const edge of computedLayout.edges) {
for (const section of edge.sections || []) { for (const section of edge.sections || []) {
@ -250,7 +246,7 @@ export class NetlistRender {
points.push(point); points.push(point);
} }
points.push(section.endPoint); points.push(section.endPoint);
this.wireRender.addAsD3DataItems(points); this.wireRender.addAsD3DataItems(points, edge);
} }
} }

View File

@ -1,17 +1,26 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { NetlistRender } from '.';
export class InstantiationRender { export class InstantiationRender {
/** /**
* *
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/ */
constructor(selection) { constructor(selection, rootRender) {
this.selection = selection; this.selection = selection;
this.rootRender = rootRender;
/** /**
* @type {BasicD3DataItem[]} * @type {BasicD3DataItem[]}
*/ */
this.data = [] this.data = []
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2selections = rootRender.id2selections;
} }
/** /**

View File

@ -1,18 +1,28 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import ELK from 'elkjs'; import ELK from 'elkjs';
import { globalLookup, globalSetting } from '../global'; import { globalLookup, globalSetting } from '../global';
import { NetlistRender } from '.';
export class PortRender { export class PortRender {
/** /**
* *
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/ */
constructor(selection) { constructor(selection, rootRender) {
this.parentSelection = selection; this.parentSelection = selection;
this.rootRender = rootRender;
/** /**
* @type {BasicD3DataItem[]} * @type {BasicD3DataItem[]}
*/ */
this.data = []; this.data = [];
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2selections = rootRender.id2selections;
} }
/** /**
@ -21,6 +31,7 @@ export class PortRender {
*/ */
addAsD3DataItem(node) { addAsD3DataItem(node) {
this.data.push({ this.data.push({
id: node.id,
x: node.x, x: node.x,
y: node.y, y: node.y,
width: node.width, width: node.width,
@ -34,6 +45,7 @@ export class PortRender {
render() { render() {
const data = this.data; const data = this.data;
const id2selections = this.id2selections;
let portSelections = this.parentSelection.selectAll('g.port') let portSelections = this.parentSelection.selectAll('g.port')
.data(data) .data(data)
@ -50,9 +62,9 @@ export class PortRender {
let texts = portSelections.append('text') let texts = portSelections.append('text')
.attr('x', data => data.width / 2) // 文本的 x 坐标(居中) .attr('x', data => data.width / 2) // 文本的 x 坐标(居中)
.attr('y', data => data.height / 2) // 文本的 y 坐标(居中) .attr('y', data => data.height / 2) // 文本的 y 坐标(居中)
.attr('dominant-baseline', 'middle') // 文本垂直居中 .attr('dominant-baseline', 'middle') // 文本垂直居中
.attr('text-anchor', 'middle') // 文本水平居中 .attr('text-anchor', 'middle') // 文本水平居中
.attr('fill', 'white') // 文本颜色 .attr('fill', 'var(--foreground)') // 文本颜色
.attr('font-size', '0') .attr('font-size', '0')
.transition() .transition()
.duration(1000) .duration(1000)
@ -68,27 +80,25 @@ export class PortRender {
.attr('stroke-width', 2) .attr('stroke-width', 2)
.attr('rx', d => d.rx) .attr('rx', d => d.rx)
.attr('ry', d => d.ry); .attr('ry', d => d.ry);
portSelections
.attr('class', 'grab')
.each(function (data) {
const portSelection = d3.select(this);
registerDragEvent(portSelection, data);
});
} else { } else {
ports.attr('stroke', 'var(--main-color)') ports.attr('stroke', 'var(--main-color)')
.attr('stroke-width', 2) .attr('stroke-width', 2)
.attr('rx', d => d.rx) .attr('rx', d => d.rx)
.attr('ry', d => d.ry); .attr('ry', d => d.ry);
portSelections
.attr('class', 'grab')
.each(function (data) {
const portSelection = d3.select(this);
registerDragEvent(portSelection, data);
});
} }
portSelections
.attr('class', 'grab')
.each(function (data) {
const portSelection = d3.select(this);
// 注册拖拽
registerDragEvent(portSelection, data);
// 进行管理
if (!id2selections.has(data.id)) {
id2selections.set(data.id, []);
}
id2selections.get(data.id).push({ data, selection: portSelection });
});
this.selections = portSelections; this.selections = portSelections;
return portSelections; return portSelections;

View File

@ -1,27 +1,38 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { globalSetting } from '../global'; import { globalSetting } from '../global';
import { NetlistRender } from '.';
export class WireRender { export class WireRender {
/** /**
* *
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/ */
constructor(selection) { constructor(selection, rootRender) {
this.selection = selection; this.selection = selection;
this.rootRender = rootRender;
/** /**
* @type {BasicD3DataItem[]} * @type {BasicD3DataItem[]}
*/ */
this.data = []; this.data = [];
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2selections = rootRender.id2selections;
} }
/** /**
* @description elknode 关于 wire 的数据添加为 d3 数据项目 * @description elknode 关于 wire 的数据添加为 d3 数据项目
* @param {ElkPoint[]} points * @param {ElkPoint[]} points
* @param {ElkEdge} edge
*/ */
addAsD3DataItems(points) { addAsD3DataItems(points, edge) {
for (let i = 0; i < points.length - 1; ++ i) { for (let i = 0; i < points.length - 1; ++ i) {
this.data.push({ this.data.push({
id: edge.id,
x1: points[i].x, x1: points[i].x,
y1: points[i].y, y1: points[i].y,
x2: points[i + 1].x, x2: points[i + 1].x,
@ -34,6 +45,7 @@ export class WireRender {
render() { render() {
const data = this.data; const data = this.data;
const id2selections = this.id2selections;
let lineSelections = this.selection.selectAll('line') let lineSelections = this.selection.selectAll('line')
.data(data) .data(data)
@ -51,7 +63,14 @@ export class WireRender {
.duration(1000); .duration(1000);
} }
lineSelections.attr('stroke-width', data => data.strokeWidth); lineSelections
// line 就不注册拖拽事件了 .attr('stroke-width', data => data.strokeWidth)
.each(function (data) {
const selection = d3.select(this);
if (!id2selections.has(data.id)) {
id2selections.set(data.id, []);
}
id2selections.get(data.id).push({ data, selection });
});
} }
} }

View File

@ -65,8 +65,12 @@ class SkinMeta {
const parser = new DOMParser(); const parser = new DOMParser();
// 颜色替换 // 颜色替换
// 填充颜色
svgString = svgString.replace(/#279BB0/g, 'var(--main-dark-color)'); svgString = svgString.replace(/#279BB0/g, 'var(--main-dark-color)');
// 边缘颜色
svgString = svgString.replace(/#000000/g, 'var(--main-color)'); svgString = svgString.replace(/#000000/g, 'var(--main-color)');
// 字体颜色
svgString = svgString.replace(/#010101/g, 'var(--foreground)');
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml'); const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
const element = svgDoc.documentElement; const element = svgDoc.documentElement;