From 2a1dc5614868035dabd4d977e4a1c22d28481f1c Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Tue, 24 Dec 2024 23:29:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=B8=B2=E6=9F=93=E7=9A=84?= =?UTF-8?q?=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1 + package.json | 1 + src/App.vue | 2 +- src/hook/jsdoc.js | 13 ++ src/hook/render/cell.js | 71 ++++++++++ src/hook/render/connection.js | 70 ++++++++++ src/hook/render/drag.js | 14 -- src/hook/render/index.js | 227 ++++++++++++++----------------- src/hook/render/instantiation.js | 11 ++ src/hook/render/port.js | 81 +++++++++++ src/hook/render/wire.js | 11 ++ 11 files changed, 365 insertions(+), 137 deletions(-) create mode 100644 src/hook/render/cell.js create mode 100644 src/hook/render/connection.js delete mode 100644 src/hook/render/drag.js create mode 100644 src/hook/render/instantiation.js create mode 100644 src/hook/render/port.js create mode 100644 src/hook/render/wire.js diff --git a/package-lock.json b/package-lock.json index 93a504a..7eba35d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "core-js": "^3.8.3", "d3": "^7.9.0", + "d3-drag": "^3.0.0", "element-plus": "^2.9.1", "elkjs": "^0.9.3", "fflate": "^0.8.2", diff --git a/package.json b/package.json index 2d5178d..55a3e59 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "core-js": "^3.8.3", "d3": "^7.9.0", + "d3-drag": "^3.0.0", "element-plus": "^2.9.1", "elkjs": "^0.9.3", "fflate": "^0.8.2", diff --git a/src/App.vue b/src/App.vue index c9663fe..a1c4878 100644 --- a/src/App.vue +++ b/src/App.vue @@ -29,7 +29,7 @@ onMounted(async () => { const skinManager = globalLookup.skinManager; skinManager.load(skinBinary); - + render.load(netJson); const layout = await render.createLayout(); const svg = await render.render(layout, '#netlist'); diff --git a/src/hook/jsdoc.js b/src/hook/jsdoc.js index 8aee47f..748a0c3 100644 --- a/src/hook/jsdoc.js +++ b/src/hook/jsdoc.js @@ -207,3 +207,16 @@ * @property {'NORTH' | 'SOUTH' | 'EAST' | 'WEST'} [elk.port.side] - 端口的位置(可选,如 "NORTH", "SOUTH", "EAST", "WEST")。 */ + + +/** + * @typedef BasicD3DataItem + * @property {number} x + * @property {number} y + * @property {number} width + * @property {number} height + * @property {string} [fill] + * @property {string} [text] + * @property {number} [rx] + * @property {number} [ry] + */ \ No newline at end of file diff --git a/src/hook/render/cell.js b/src/hook/render/cell.js new file mode 100644 index 0000000..892e4fc --- /dev/null +++ b/src/hook/render/cell.js @@ -0,0 +1,71 @@ +import * as d3 from 'd3'; +import { globalSetting } from '../global'; + +export class CellRender { + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} cells + */ + constructor(selection, cells) { + this.parentSelection = selection; + this.data = cells; + } + + render() { + const data = this.data; + + let cellSelections = this.parentSelection.selectAll('g') + .data(data) + .enter() + .append(data => { + const element = data.element; + element.setAttribute('x', data.x); + element.setAttribute('y', data.y); + if (globalSetting.renderAnimation) { + element.setAttribute('stroke-opacity', 0); + } + return element; + }); + + if (globalSetting.renderAnimation) { + cellSelections = cellSelections + .transition() + .duration(1000) + .attr('stroke-opacity', 1); + } + cellSelections.on('end', function (data) { + const cellSelection = d3.select(this); + registerDragEvent(cellSelection, data); + }); + + this.selections = cellSelections; + return cellSelections; + } +} + +/** + * @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); +} \ No newline at end of file diff --git a/src/hook/render/connection.js b/src/hook/render/connection.js new file mode 100644 index 0000000..e8f8549 --- /dev/null +++ b/src/hook/render/connection.js @@ -0,0 +1,70 @@ +import * as d3 from 'd3'; +import { globalSetting } from '../global'; + +export class ConnectionRender { + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} connections + */ + constructor(selection, connections) { + this.parentSelection = selection; + this.data = connections; + } + + render() { + const data = this.data; + + let connectionSelections = this.parentSelection.selectAll('circle') + .data(data) + .enter() + .append('circle') + .attr('cx', data => data.x) + .attr('cy', data => data.y) + .attr('width', data => data.width) + .attr('height', data => data.height) + + if (globalSetting.renderAnimation) { + connectionSelections = connectionSelections + .transition() + .duration(1000); + } + + connectionSelections + .attr('fill', d => d.fill) + .attr('r', d => d.r) + .on('end', function (data) { + const connectionSelection = d3.select(this); + registerDragEvent(connectionSelection, data); + }); + + 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); +} \ No newline at end of file diff --git a/src/hook/render/drag.js b/src/hook/render/drag.js deleted file mode 100644 index d253758..0000000 --- a/src/hook/render/drag.js +++ /dev/null @@ -1,14 +0,0 @@ -import * as d3 from 'd3'; -import ELK from 'elkjs'; - -/** - * @description 注册关于 器件 的拖动事件 - * - * 需要提取最小拓扑子图,然后重新调整各个区域的尺寸 - * @param {d3.Transition} renderSvg - */ -export function registerCellDragEvent(renderSvg) { - // renderSvg.call(d3.drag().on('drag', (event, g) => { - // console.log(event); - // })); -} \ No newline at end of file diff --git a/src/hook/render/index.js b/src/hook/render/index.js index 5d34ddf..fe92e2e 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -5,6 +5,10 @@ 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'; export class NetlistRender { /** @@ -43,7 +47,7 @@ export class NetlistRender { * @type {YosysRawNet} */ this.rawNet = rawNet; - + // 转换为 elkjs 格式的 graph for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) { const top = parseInt(rawModule.attributes.top); @@ -82,6 +86,25 @@ export class NetlistRender { return layoutGraph; } + /** + * @description 窗体大小发生变化时 + */ + registerResizeHandler(element) { + this.resizeMonitor = new ResizeObserver(entries => { + for (const entry of entries) { + const { height, width } = entry.contentRect; + this.renderHeight = height; + this.renderWidth = width; + if (this.selection) { + this.selection + .attr('width', this.renderWidth) + .attr('height', this.renderHeight) + } + } + }); + this.resizeMonitor.observe(element); + } + /** * * @param {ElkNode} computedLayout @@ -92,14 +115,19 @@ export class NetlistRender { const virtualHeight = computedLayout.height; const virtualWidth = computedLayout.width; + const element = document.querySelector(container); + this.renderHeight = element.clientHeight; + this.renderWidth = element.clientWidth; + this.registerResizeHandler(element); + // 根据 height 进行放缩(可以通过设置进行调整) const ratio = this.renderHeight / virtualHeight; // 遍历计算布局进行创建 const svg = d3.select(container) .selectAll('svg') - .attr('width', virtualWidth) - .attr('height', virtualHeight); + .attr('width', this.renderWidth) + .attr('height', this.renderHeight); await this.renderLine(svg, computedLayout, ratio); await this.renderEntity(svg, computedLayout, ratio); @@ -123,17 +151,21 @@ export class NetlistRender { // 生成用于绘制的 d3 数据结构 // 默认需要渲染成矩形的(缺失样式的器件、例化模块等等) - const squares = []; + const ports = []; + const instantiations = []; + const cells = []; const connections = []; - const svgElements = []; + const skinManager = globalLookup.skinManager; + // TODO: 把下面的逻辑融合进入 render 中 + for (const node of computedLayout.children) { const skin = skinManager.querySkin(node.renderName); if (skin) { // 具有 skin 的器件 - svgElements.push({ + cells.push({ element: skin.meta.svgDoc.documentElement, x: node.x, y: node.y, @@ -142,17 +174,30 @@ export class NetlistRender { fill: 'var(--main-dark-color)', }); } else { - // 没有 skin 的器件 - squares.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 - }); + 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 + }); + } 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 + }); + } } // 如果存在 port,绘制 port @@ -169,114 +214,17 @@ export class NetlistRender { } } - if (globalSetting.renderAnimation) { - svg.selectAll('rect') - .data(squares) - .enter() - .append('rect') - .attr('x', data => data.x) - .attr('y', data => data.y) - .attr('width', data => data.width) - .attr('height', data => data.height) - .attr('fill', d => d.fill) - .transition() - .duration(1000) - .attr('stroke', 'var(--main-color)') - .attr('stroke-width', 2) - .attr('rx', d => d.rx) - .attr('ry', d => d.ry); - - const renderSvg = svg.selectAll('g') - .data(svgElements) - .enter() - .append(data => { - const element = data.element; - element.setAttribute('x', data.x); - element.setAttribute('y', data.y); - element.setAttribute('stroke-opacity', 0); - return element; - }) - .transition() - .duration(1000) - .attr('stroke-opacity', 1); - - registerCellDragEvent(renderSvg); + this.portRender = new PortRender(svg, ports); + this.portRender.render(); - svg.selectAll('circle') - .data(connections) - .enter() - .append('circle') - .attr('cx', data => data.x) - .attr('cy', data => data.y) - .attr('width', data => data.width) - .attr('height', data => data.height) - .transition() - .duration(1000) - .attr('fill', d => d.fill) - .attr('r', d => d.r); + // this.instantiationRender = new InstantiationRender(svg, instantiations); + // this.instantiationRender.render(); - svg.selectAll('text') - .data(squares) - .enter() - .append('text') - .attr('x', data => data.x + data.width / 2) // 文本的 x 坐标(居中) - .attr('y', data => data.y + data.height / 2) // 文本的 y 坐标(居中) - .attr('dominant-baseline', 'middle') // 文本垂直居中 - .attr('text-anchor', 'middle') // 文本水平居中 - .attr('fill', 'white') // 文本颜色 - .attr('font-size', '0') - .transition() - .duration(1000) - .attr('font-size', '12px') - .text(data => data.text); // 设置文本内容 - } else { - svg.selectAll('rect') - .data(squares) - .enter() - .append('rect') - .attr('x', data => data.x) - .attr('y', data => data.y) - .attr('width', data => data.width) - .attr('height', data => data.height) - .attr('fill', d => d.fill) - .attr('stroke', 'var(--main-color)') - .attr('stroke-width', 2) - .attr('rx', d => d.rx) - .attr('ry', d => d.ry); - - svg.selectAll('g') - .data(svgElements) - .enter() - .append(data => { - const element = data.element; - element.setAttribute('x', data.x); - element.setAttribute('y', data.y); - return element; - }); + this.cellRender = new CellRender(svg, cells); + this.cellRender.render(); - svg.selectAll('circle') - .data(connections) - .enter() - .append('circle') - .attr('cx', data => data.x) - .attr('cy', data => data.y) - .attr('width', data => data.width) - .attr('height', data => data.height) - .attr('fill', d => d.fill) - .attr('r', d => d.r); - - svg.selectAll('text') - .data(squares) - .enter() - .append('text') - .attr('x', data => data.x + data.width / 2) // 文本的 x 坐标(居中) - .attr('y', data => data.y + data.height / 2) // 文本的 y 坐标(居中) - .attr('dominant-baseline', 'middle') // 文本垂直居中 - .attr('text-anchor', 'middle') // 文本水平居中 - .attr('fill', 'white') // 文本颜色 - .attr('font-size', '12px') - .text(data => data.text); // 设置文本内容 - } + this.connectionRender = new ConnectionRender(svg, connections); + this.connectionRender.render(); } /** @@ -337,4 +285,39 @@ export class NetlistRender { svg.attr('transform', `translate(${globalLookup.svgTranslateX}, ${globalLookup.svgTranslateY}) scale(${globalLookup.svgScale})`); } + + /** + * @description 渲染 module 中的 port + */ + renderPorts() { + + } + + /** + * @description 渲染 module 中的例化模块 + */ + renderInstantiations() { + + } + + /** + * @description 渲染 module 中的基础器件 + */ + renderCells() { + + } + + /** + * @description 渲染每一个 例化模块/基础器件 的接入端口 + */ + renderConnections() { + + } + + /** + * @description 渲染连线 + */ + renderWires() { + + } } \ No newline at end of file diff --git a/src/hook/render/instantiation.js b/src/hook/render/instantiation.js new file mode 100644 index 0000000..cfcdcdd --- /dev/null +++ b/src/hook/render/instantiation.js @@ -0,0 +1,11 @@ +import * as d3 from 'd3'; + +export class InstantiationRender { + /** + * + * @param {d3.Selection} selection + */ + constructor(selection) { + this.selection = selection; + } +} \ No newline at end of file diff --git a/src/hook/render/port.js b/src/hook/render/port.js new file mode 100644 index 0000000..4fc46a8 --- /dev/null +++ b/src/hook/render/port.js @@ -0,0 +1,81 @@ +import * as d3 from 'd3'; +import ELK from 'elkjs'; +import { globalLookup, globalSetting } from '../global'; + +export class PortRender { + /** + * + * @param {d3.Selection} selection + * @param {BasicD3DataItem} ports + */ + constructor(selection, ports) { + this.parentSelection = selection; + this.data = ports; + } + + render() { + const data = this.data; + + let portSelections = this.parentSelection.selectAll('rect') + .data(data) + .enter() + .append('rect') + .attr('x', data => data.x) + .attr('y', data => data.y) + .attr('width', data => data.width) + .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 坐标(居中) + .attr("text-anchor", "middle") // 文本水平居中 + .attr("dominant-baseline", "middle") // 文本垂直居中 + .text(d => d.text) // 设置文本内容 + .attr("fill", "black"); // 文本颜色 + + if (globalSetting.renderAnimation) { + portSelections = portSelections + .transition() + .duration(1000) + } + 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; + } +} + +/** + * @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); +} \ No newline at end of file diff --git a/src/hook/render/wire.js b/src/hook/render/wire.js new file mode 100644 index 0000000..1335d0f --- /dev/null +++ b/src/hook/render/wire.js @@ -0,0 +1,11 @@ +import * as d3 from 'd3'; + +export class WireRender { + /** + * + * @param {d3.Selection} selection + */ + constructor(selection) { + this.selection = selection; + } +} \ No newline at end of file