From 3e2c7e3ae0ec23fa512108a1c352d675ff2c506f Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Mon, 30 Dec 2024 00:55:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=BF=9E=E6=8E=A5=E7=BA=BF?= =?UTF-8?q?=E7=9A=84=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/netlist.css | 4 +- src/components/render/index.vue | 5 ++ src/components/setting/color.js | 6 ++ src/hook/render/index.js | 16 ++-- src/hook/render/port.js | 3 - src/hook/render/wire.js | 138 ++++++++++++++++++++++++++++---- src/i18n/ar.json | 3 +- src/i18n/de.json | 3 +- src/i18n/en.json | 3 +- src/i18n/fr.json | 3 +- src/i18n/ja.json | 3 +- src/i18n/ko.json | 3 +- src/i18n/ru.json | 3 +- src/i18n/zh-cn.json | 3 +- src/i18n/zh-tw.json | 3 +- 15 files changed, 163 insertions(+), 36 deletions(-) diff --git a/public/netlist.css b/public/netlist.css index 291fec2..88934aa 100644 --- a/public/netlist.css +++ b/public/netlist.css @@ -16,9 +16,11 @@ --instance-color: #CB81DA; --instance-fill-color: rgba(203, 129, 218, 0.1); --wire-color: var(--foreground); + --wire-active-color: #CB81DA; + --wire-ball-color: #CB81DA; --cross-dot-color: var(--foreground); --port-color: rgb(70, 70, 222); - --port-fill-color: rgba(70, 70, 222, 0.1); + --port-fill-color: rgba(70, 70, 222, 0.25); /* css 动画属性 */ --animation-7s: .7s cubic-bezier(0.23,1,0.32,1); diff --git a/src/components/render/index.vue b/src/components/render/index.vue index 18dea6d..84e3816 100644 --- a/src/components/render/index.vue +++ b/src/components/render/index.vue @@ -53,4 +53,9 @@ onMounted(async () => { user-select: none; } +.connection-line { + cursor: pointer; + transition: var(--animation-5s); +} + \ No newline at end of file diff --git a/src/components/setting/color.js b/src/components/setting/color.js index d5ad6c2..6d6687d 100644 --- a/src/components/setting/color.js +++ b/src/components/setting/color.js @@ -31,6 +31,12 @@ export const colorManager = reactive({ type: 'cross-dot', label: t('cross-dot'), color: 'white' + }, + { + value: 4, + type: 'wire-active', + label: t('common.selected-wire'), + color: 'white' } ], diff --git a/src/hook/render/index.js b/src/hook/render/index.js index 06280d4..d4e9597 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -37,7 +37,9 @@ export class NetlistRender { // 指定 layered 算法的方向为从左到右 'elk.direction': 'RIGHT', // 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组 - 'elk.partitioning.activate': true + 'elk.partitioning.activate': true, + // 边之间的距离 + 'elk.spacing.edgeEdge': 25 } /** * @type {ElkGraph} @@ -217,10 +219,10 @@ export class NetlistRender { // 根据最大最小尺寸微调全局方位 this.adjustLocation(g); - setTimeout(() => { - // 生成各个 manager 的拖拽上下文 - this.createManagerDragContext(computedLayout); - }, 1000); + // setTimeout(() => { + // // 生成各个 manager 的拖拽上下文 + // this.createManagerDragContext(computedLayout); + // }, 1000); return svg; } @@ -276,7 +278,7 @@ export class NetlistRender { } /** - * @description 绘制连线 + * @description 绘制连线和交叉点 * @param {d3.Selection} parentSelection * @param {ElkNode} computedLayout */ @@ -581,7 +583,7 @@ export class NetlistRender { const instanceName = elkNode.name; const parentModule = this.nameToModule.get(parentName); const cell = parentModule.view.nameToCell.get(instanceName); - + // 先记录当前这个选择集的位置 const originX = elkNode.x; const originY = elkNode.y; diff --git a/src/hook/render/port.js b/src/hook/render/port.js index 9c46804..5b029da 100644 --- a/src/hook/render/port.js +++ b/src/hook/render/port.js @@ -66,9 +66,6 @@ export class PortRender { .attr('dominant-baseline', 'middle') // 文本垂直居中 .attr('text-anchor', 'middle') // 文本水平居中 .attr('fill', 'var(--foreground)') // 文本颜色 - .attr('font-size', '0') - .transition() - .duration(1000) .attr('font-size', '12px') .attr('class', 'port-caption') .text(data => data.text); // 设置文本内容 diff --git a/src/hook/render/wire.js b/src/hook/render/wire.js index 1727c25..d8f1c7f 100644 --- a/src/hook/render/wire.js +++ b/src/hook/render/wire.js @@ -1,6 +1,9 @@ +/* eslint-disable */ + import * as d3 from 'd3'; import { globalSetting } from '../global'; import { NetlistRender } from '.'; +import { LINE_WIDTH } from './layout'; export class WireRender { /** @@ -30,16 +33,21 @@ export class WireRender { * @param {ElkEdge} edge */ addAsD3DataItems(points, edge) { - for (let i = 0; i < points.length - 1; ++ i) { - this.data.push({ - id: edge.id, - x1: points[i].x, - y1: points[i].y, - x2: points[i + 1].x, - y2: points[i + 1].y, - strokeWidth: 2 - }); + const linePaths = []; + for (let i = 0; i < points.length; ++ i) { + // 根据点的信息创建 path + const command = i === 0 ? 'M': 'L'; + const x = points[i].x; + const y = points[i].y; + linePaths.push(`${command}${x},${y}`); } + + const lineSvg = linePaths.join(' '); + + this.data.push({ + svg: lineSvg, + active: false + }); } render() { @@ -47,16 +55,102 @@ export class WireRender { const id2manager = this.id2manager; const _this = this; - let lineSelections = this.selection.selectAll('line') + + // 一个 g.lines 管理一个线段 + let lineSelections = this.selection.selectAll('path.lines') .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) + .append('path') + .attr('d', d => d.svg) + .attr('class', 'connection-line') + .attr("fill", "none") .attr('stroke', 'var(--wire-color)'); + + const id2animation = new Map(); + + lineSelections.on('click', function(_, data) { + data.active = !data.active; + + if (data.active) { + const pathSelection = d3.select(this); + // 如果当前激活,显示数据流向 + const pathLength = pathSelection.node().getTotalLength(); + + const pivot = _this.selection + .append('circle') + .attr('r', 6) + .attr('fill', 'var(--wire-ball-color)'); + + // 进行一次动画 + function renderOneAnimation() { + console.log(pathLength); + // 1400 的长度差不多配 3500 ? + const duration = pathLength / 14 * 35; + let transition = pivot.transition().duration(duration); + transition.attrTween('transform', () => { + return t => { + const x = cubicBezierAnimation(t, 0, pathLength); + const point = pathSelection.node().getPointAtLength(x); + return `translate(${point.x},${point.y})`; + } + }); + transition = transition.on('end', renderOneAnimation); + // 动画结束后重新启动动画 + id2animation.set(data.id, { + pivot, + transition + }); + } + + renderOneAnimation(); + } else { + if (id2animation.has(data.id)) { + const { pivot, transition } = id2animation.get(data.id); + pivot.remove(); + id2animation.delete(data.id); + } + } + }); + + lineSelections.on('mouseenter', function(_, data) { + const selection = d3.select(this); + // 移动到最上层 + selection.raise(); + selection.attr('stroke', 'var(--wire-active-color)'); + }); + + lineSelections.on('mouseleave', function(_, data) { + const selection = d3.select(this); + selection.attr('stroke', 'var(--wire-color)'); + }); + + // linesContainer.each(function(lines) { + // // 为每一个线段分别创建对应的对象 + // const lineSelection = d3.select(this) + // .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', 'var(--wire-color)'); + + // lineSelection.on('mouseenter', function(_, data) { + // console.log('enter enter'); + // }); + + // lineSelection.on('mouseleave', function(_, data) { + // console.log('enter leave'); + // }); + + // lineSelection.on('click', function(_, data) { + // console.log('click'); + // }); + // }); + if (globalSetting.renderAnimation) { lineSelections = lineSelections .transition() @@ -64,7 +158,7 @@ export class WireRender { } lineSelections - .attr('stroke-width', data => data.strokeWidth) + .attr('stroke-width', LINE_WIDTH) .each(function (data) { const selection = d3.select(this); // const manager = _this.createDataManager(selection, data); @@ -93,4 +187,16 @@ export class WireRender { id2manager.get(data.id).push(managerItem); return managerItem; } +} + +/** + * @description 计算贝塞尔插值,输入 0 - 1 的值 (delta),输出 [oldVal, newVal] 中间的插值 + * @param {*} delta + * @param {*} oldVal + * @param {*} newVal + * @returns + */ +function cubicBezierAnimation(delta, oldVal, newVal) { + delta = 3 * (1 - delta) * (1 - delta) * delta + 3 * (1 - delta) * delta * delta + delta * delta * delta; + return (1 - delta) * oldVal + delta * newVal; } \ No newline at end of file diff --git a/src/i18n/ar.json b/src/i18n/ar.json index 3b77eae..162e226 100644 --- a/src/i18n/ar.json +++ b/src/i18n/ar.json @@ -24,5 +24,6 @@ "common.port": "المنفذ", "common.instance": "تجهيز الوحدة", "setting.general-color-setting": "إعدادات الألوان العامة", - "cross-dot": "تقاطع" + "cross-dot": "تقاطع", + "common.selected-wire": "الخط المحدد" } \ No newline at end of file diff --git a/src/i18n/de.json b/src/i18n/de.json index 2e08e33..af5d09c 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -24,5 +24,6 @@ "common.port": "Port", "common.instance": "Modul instanziieren", "setting.general-color-setting": "Allgemeine Farbeinstellungen", - "cross-dot": "Kreuzung" + "cross-dot": "Kreuzung", + "common.selected-wire": "Ausgewählte Linie" } \ No newline at end of file diff --git a/src/i18n/en.json b/src/i18n/en.json index 06fa9ea..fc7e934 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -24,5 +24,6 @@ "common.port": "Port", "common.instance": "Instantiate module", "setting.general-color-setting": "General Color Settings", - "cross-dot": "Intersection" + "cross-dot": "Intersection", + "common.selected-wire": "Selected line" } \ No newline at end of file diff --git a/src/i18n/fr.json b/src/i18n/fr.json index b3e13ef..eae79b9 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -24,5 +24,6 @@ "common.port": "Port", "common.instance": "Instancier le module", "setting.general-color-setting": "Paramètres de couleur généraux", - "cross-dot": "Intersection" + "cross-dot": "Intersection", + "common.selected-wire": "Ligne sélectionnée" } \ No newline at end of file diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 8dd78ad..ef8f13d 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -24,5 +24,6 @@ "common.port": "ポート", "common.instance": "モジュールのインスタンス化", "setting.general-color-setting": "一般的な色設定", - "cross-dot": "交差点" + "cross-dot": "交差点", + "common.selected-wire": "選択された線" } \ No newline at end of file diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 1fa2c64..36a11ee 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -24,5 +24,6 @@ "common.port": "포트", "common.instance": "모듈 인스턴스화", "setting.general-color-setting": "일반 색상 설정", - "cross-dot": "교차로" + "cross-dot": "교차로", + "common.selected-wire": "선택된 선" } \ No newline at end of file diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 089dcac..2fe98c9 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -24,5 +24,6 @@ "common.port": "Порт", "common.instance": "Создание экземпляра модуля", "setting.general-color-setting": "Общие настройки цвета", - "cross-dot": "Перекресток" + "cross-dot": "Перекресток", + "common.selected-wire": "Выбранная линия" } \ No newline at end of file diff --git a/src/i18n/zh-cn.json b/src/i18n/zh-cn.json index bd704bf..dea4406 100644 --- a/src/i18n/zh-cn.json +++ b/src/i18n/zh-cn.json @@ -24,5 +24,6 @@ "common.port": "端口", "common.instance": "例化模块", "setting.general-color-setting": "通用颜色设置", - "cross-dot": "交叉点" + "cross-dot": "交叉点", + "common.selected-wire": "被选中的线" } \ No newline at end of file diff --git a/src/i18n/zh-tw.json b/src/i18n/zh-tw.json index 9e914f9..96c72c6 100644 --- a/src/i18n/zh-tw.json +++ b/src/i18n/zh-tw.json @@ -24,5 +24,6 @@ "common.port": "端口", "common.instance": "實例化模組", "setting.general-color-setting": "通用顏色設定", - "cross-dot": "交叉點" + "cross-dot": "交叉點", + "common.selected-wire": "被選中的線" } \ No newline at end of file