From b718780cf54277b621ce1aab73d07ad9db753772 Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Thu, 26 Dec 2024 14:14:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9E=B6=E6=9E=84=EF=BC=8C?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=BE=9D=E8=B5=96=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hook/jsdoc.js | 10 +-- src/hook/render/cell.js | 110 +++++++++++++++++-------------- src/hook/render/connection.js | 37 ++++++++--- src/hook/render/index.js | 6 +- src/hook/render/instantiation.js | 2 +- src/hook/render/port.js | 93 ++++++++++++++++++-------- src/hook/render/wire.js | 33 ++++++++-- 7 files changed, 190 insertions(+), 101 deletions(-) diff --git a/src/hook/jsdoc.js b/src/hook/jsdoc.js index 93bace2..2ef95cb 100644 --- a/src/hook/jsdoc.js +++ b/src/hook/jsdoc.js @@ -223,14 +223,14 @@ */ /** - * @typedef BasicD3ManagmentItem - * @property {'cell' | 'connection' | 'instantiation' | 'port' | 'wire'} type - * @property {BasicD3DataItem} data - * @property {d3.Selection} selection + * @typedef BasicD3ManagmentItem 用于管理一个 实体/连接 的 d3 上下文数据结构 + * @property {'cell' | 'connection' | 'instantiation' | 'port' | 'wire'} type + * @property {BasicD3DataItem} data 用于绑定 d3 渲染的数据,【初始化/特殊事件】会使得 d3 去主动根据 + * @property {d3.Selection} selection 渲染成图形对应的 d3 选择集 + * @property {DragContext} [dragContext] 与当前这个 实体 相关的拖拽上下文(目前不支持 wire 和 connection 的) */ /** * @typedef DragContext - * @property {BasicD3DataItem} data * @property {ElkGraph} elkGraph */ \ No newline at end of file diff --git a/src/hook/render/cell.js b/src/hook/render/cell.js index 2a636d8..d76f77d 100644 --- a/src/hook/render/cell.js +++ b/src/hook/render/cell.js @@ -21,7 +21,7 @@ export class CellRender { * @description id 到管理数据项的映射 * @type {Map} */ - this.id2selections = rootRender.id2selections; + this.id2manager = rootRender.id2manager; } /** @@ -44,7 +44,8 @@ export class CellRender { render() { const data = this.data; const rootRender = this.rootRender; - const id2selections = this.id2selections; + const id2manager = this.id2manager; + const _this = this; let cellSelections = this.parentSelection.selectAll('svg') .data(data) @@ -67,74 +68,83 @@ export class CellRender { .attr('class', 'grab') .on('end', function (data) { const cellSelection = d3.select(this); - registerDragEvent(cellSelection, data, rootRender); + const manager = _this.createDataManager(cellSelection, data); + registerDragEvent(manager, rootRender); }); } else { cellSelections = cellSelections .attr('class', 'grab') .each(function (data) { const cellSelection = d3.select(this); - registerDragEvent(cellSelection, data, rootRender); + const manager = _this.createDataManager(cellSelection, data); + registerDragEvent(manager, 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; return cellSelections; } + + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} data + * @returns {BasicD3ManagmentItem} + */ + createDataManager(selection, data) { + const id2manager = this.id2manager; + // 创建拖拽上下文 + const dragContext = { + 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.set(data.id, managerItem); + return managerItem; + } } /** * @description 注册关于 器件 的拖动事件 * * 需要提取最小拓扑子图,然后重新调整各个区域的尺寸 - * @param {d3.Selection} selection - * @param {any} data + * @param {BasicD3ManagmentItem} manager * @param {NetlistRender} rootRender */ -export function registerDragEvent(selection, data, rootRender) { +export function registerDragEvent(manager, rootRender) { // 创建拖拽行为 const drag = d3.drag(); - const dragContext = { - data: 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)) + drag.on('start', async event => dragStart(event, manager, rootRender)); + drag.on('drag', async event => dragged(event, manager, rootRender)); + drag.on('end', async event => dragEnd(event, manager, rootRender)) - selection.call(drag); + manager.selection.call(drag); } /** * * @param {d3.D3DragEvent} event - * @param {d3.Selection} selection - * @param {DragContext} dragContext + * @param {BasicD3ManagmentItem} manager * @param {NetlistRender} rootRender */ -function dragStart(event, selection, dragContext, rootRender) { +function dragStart(event, manager, rootRender) { + const selection = manager.selection; selection.attr('class', 'grabbing'); // 更新拓扑图中各个节点的节点坐标 @@ -145,32 +155,34 @@ function dragStart(event, selection, dragContext, rootRender) { /** * * @param {d3.D3DragEvent} event - * @param {d3.Selection} selection - * @param {DragContext} dragContext + * @param {BasicD3ManagmentItem} manager * @param {NetlistRender} rootRender */ -function dragged(event, selection, dragContext, rootRender) { +function dragged(event, manager, rootRender) { // 当拖动结束时,D3 会根据绑定的数据(data.x 和 data.y)重新渲染元素,导致元素回到初始位置。 // 所以需要 手动更新 data.x 和 data.y - dragContext.data.x = event.x; - dragContext.data.y = event.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); // 根据最小拓扑图,提取出关键点,重新计算布局 - console.log(dragContext.data); - + const context = manager.dragContext; + console.log(context.elkGraph); } /** * * @param {d3.D3DragEvent} event - * @param {d3.Selection} selection - * @param {DragContext} dragContext + * @param {BasicD3ManagmentItem} manager * @param {NetlistRender} rootRender */ -function dragEnd(event, selection, dragContext, rootRender) { +function dragEnd(event, manager, rootRender) { + const selection = manager.selection; selection.attr('class', 'grab'); } \ No newline at end of file diff --git a/src/hook/render/connection.js b/src/hook/render/connection.js index 75bce6f..9239197 100644 --- a/src/hook/render/connection.js +++ b/src/hook/render/connection.js @@ -20,7 +20,7 @@ export class ConnectionRender { * @description id 到管理数据项的映射 * @type {Map} */ - this.id2selections = rootRender.id2selections; + this.id2manager = rootRender.id2manager; } /** @@ -43,7 +43,8 @@ export class ConnectionRender { render() { const data = this.data; - const id2selections = this.id2selections; + const id2manager = this.id2manager; + const _this = this; let connectionSelections = this.parentSelection.selectAll('circle') .data(data) @@ -65,17 +66,33 @@ export class ConnectionRender { .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' - }); + const manager = _this.createDataManager(selection, data); }); this.selections = connectionSelections; return connectionSelections; } + + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} data + * @returns {BasicD3ManagmentItem} + */ + createDataManager(selection, data) { + const id2manager = this.id2manager; + // connection 不需要拖拽上下文 + const managerItem = { + data, + selection, + type: 'connection' + }; + + if (!id2manager.has(data.id)) { + id2manager.set(data.id, []); + } + + id2manager.set(data.id, managerItem); + return managerItem; + } } diff --git a/src/hook/render/index.js b/src/hook/render/index.js index 76656d2..2b9f5ae 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -62,9 +62,13 @@ export class NetlistRender { /** * @description id 到管理数据项的映射 + * + * - key: id, 和 layout.js 中的赋值 id 一致,全局唯一 + * - value: 每一个 id 对应的管理者,这里存储着数据视图和渲染视图,查询与操作DOM一体。一个 ID 对应的真实渲染体可能不只一个 + * (比如对于 wire,一个 id 对应的 wire 可以是连续的折现,但是这些折现对应的渲染体是复数个的) * @type {Map} */ - this.id2selections = new Map(); + this.id2manager = new Map(); } /** diff --git a/src/hook/render/instantiation.js b/src/hook/render/instantiation.js index 324075c..00ac271 100644 --- a/src/hook/render/instantiation.js +++ b/src/hook/render/instantiation.js @@ -20,7 +20,7 @@ export class InstantiationRender { * @description id 到管理数据项的映射 * @type {Map} */ - this.id2selections = rootRender.id2selections; + this.id2manager = rootRender.id2manager; } /** diff --git a/src/hook/render/port.js b/src/hook/render/port.js index 084555d..0d0b769 100644 --- a/src/hook/render/port.js +++ b/src/hook/render/port.js @@ -22,7 +22,7 @@ export class PortRender { * @description id 到管理数据项的映射 * @type {Map} */ - this.id2selections = rootRender.id2selections; + this.id2manager = rootRender.id2manager; } /** @@ -45,7 +45,9 @@ export class PortRender { render() { const data = this.data; - const id2selections = this.id2selections; + const id2manager = this.id2manager; + const rootRender = this.rootRender; + const _this = this; let portSelections = this.parentSelection.selectAll('g.port') .data(data) @@ -60,8 +62,8 @@ export class PortRender { .attr('fill', d => d.fill); let texts = portSelections.append('text') - .attr('x', data => data.width / 2) // 文本的 x 坐标(居中) - .attr('y', data => data.height / 2) // 文本的 y 坐标(居中) + .attr('x', data => data.width / 2) // 文本的 x 坐标(居中) + .attr('y', data => data.height / 2) // 文本的 y 坐标(居中) .attr('dominant-baseline', 'middle') // 文本垂直居中 .attr('text-anchor', 'middle') // 文本水平居中 .attr('fill', 'var(--foreground)') // 文本颜色 @@ -70,7 +72,7 @@ export class PortRender { .duration(1000) .attr('font-size', '12px') .attr('class', 'port-caption') - .text(data => data.text); // 设置文本内容 + .text(data => data.text); // 设置文本内容 if (globalSetting.renderAnimation) { @@ -91,18 +93,46 @@ export class PortRender { .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 }); + const manager = _this.createDataManager(portSelection, data); + registerDragEvent(manager, rootRender); }); this.selections = portSelections; return portSelections; } + + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} data + * @returns {BasicD3ManagmentItem} + */ + createDataManager(selection, data) { + const id2manager = this.id2manager; + // 创建拖拽上下文 + const dragContext = { + elkGraph: { + // elk 是无状态的,id 取什么名字都行 + id: 'root', + children: [], + edges: [], + layoutOptions: {} + } + } + const managerItem = { + data, + selection, + type: 'port', + dragContext + }; + + if (!id2manager.has(data.id)) { + id2manager.set(data.id, []); + } + + id2manager.set(data.id, managerItem); + return managerItem; + } } @@ -112,45 +142,50 @@ export class PortRender { * @description 注册关于 器件 的拖动事件 * * 需要提取最小拓扑子图,然后重新调整各个区域的尺寸 - * @param {d3.Selection} selection - * @param {any} data + * @param {BasicD3ManagmentItem} manager + * @param {NetlistRender} rootRender */ -export function registerDragEvent(selection, data) { +export function registerDragEvent(manager, rootRender) { // 创建拖拽行为 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)); + drag.on("start", event => dragStart(event, manager, rootRender)); + drag.on("drag", event => dragged(event, manager, rootRender)); + drag.on("end", event => dragEnd(event, manager, rootRender)); - selection.call(drag); + manager.selection.call(drag); } /** - * * @param {d3.D3DragEvent} event - * @param {d3.Selection} selection + * @param {BasicD3ManagmentItem} manager + * @param {NetlistRender} rootRender */ -function dragStart(event, selection, data) { +function dragStart(event, manager, rootRender) { + const selection = manager.selection; selection.attr('class', 'grabbing'); } /** - * * @param {d3.D3DragEvent} event - * @param {d3.Selection} selection + * @param {BasicD3ManagmentItem} manager + * @param {NetlistRender} rootRender */ -function dragged(event, selection, data) { +function dragged(event, manager, rootRender) { + const selection = manager.selection; + const data = manager.data; + data.x = event.x; data.y = event.y; selection.attr("transform", `translate(${event.x}, ${event.y})`); } /** - * - * @param {d3.D3DragEvent} event - * @param {d3.Selection} selection + * @param {d3.D3DragEvent} event + * @param {BasicD3ManagmentItem} manager + * @param {NetlistRender} rootRender */ -function dragEnd(event, selection, data) { +function dragEnd(event, manager, rootRender) { + const selection = manager.selection; selection.attr('class', 'grab'); } \ No newline at end of file diff --git a/src/hook/render/wire.js b/src/hook/render/wire.js index 09691aa..79993c8 100644 --- a/src/hook/render/wire.js +++ b/src/hook/render/wire.js @@ -21,7 +21,7 @@ export class WireRender { * @description id 到管理数据项的映射 * @type {Map} */ - this.id2selections = rootRender.id2selections; + this.id2manager = rootRender.id2manager; } /** @@ -45,7 +45,8 @@ export class WireRender { render() { const data = this.data; - const id2selections = this.id2selections; + const id2manager = this.id2manager; + const _this = this; let lineSelections = this.selection.selectAll('line') .data(data) @@ -67,10 +68,30 @@ export class WireRender { .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 }); + const manager = _this.createDataManager(selection, data); }); } + + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} data + * @returns {BasicD3ManagmentItem} + */ + createDataManager(selection, data) { + const id2manager = this.id2manager; + // wire 不需要拖拽上下文 + const managerItem = { + data, + selection, + type: 'wire' + }; + + if (!id2manager.has(data.id)) { + id2manager.set(data.id, []); + } + + id2manager.set(data.id, managerItem); + return managerItem; + } } \ No newline at end of file