实现连接线的动画

This commit is contained in:
锦恢 2024-12-30 00:55:25 +08:00
parent f36fd94cbb
commit 3e2c7e3ae0
15 changed files with 163 additions and 36 deletions

View File

@ -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);

View File

@ -53,4 +53,9 @@ onMounted(async () => {
user-select: none;
}
.connection-line {
cursor: pointer;
transition: var(--animation-5s);
}
</style>

View File

@ -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'
}
],

View File

@ -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;

View File

@ -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); // 设置文本内容

View File

@ -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;
}

View File

@ -24,5 +24,6 @@
"common.port": "المنفذ",
"common.instance": "تجهيز الوحدة",
"setting.general-color-setting": "إعدادات الألوان العامة",
"cross-dot": "تقاطع"
"cross-dot": "تقاطع",
"common.selected-wire": "الخط المحدد"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -24,5 +24,6 @@
"common.port": "ポート",
"common.instance": "モジュールのインスタンス化",
"setting.general-color-setting": "一般的な色設定",
"cross-dot": "交差点"
"cross-dot": "交差点",
"common.selected-wire": "選択された線"
}

View File

@ -24,5 +24,6 @@
"common.port": "포트",
"common.instance": "모듈 인스턴스화",
"setting.general-color-setting": "일반 색상 설정",
"cross-dot": "교차로"
"cross-dot": "교차로",
"common.selected-wire": "선택된 선"
}

View File

@ -24,5 +24,6 @@
"common.port": "Порт",
"common.instance": "Создание экземпляра модуля",
"setting.general-color-setting": "Общие настройки цвета",
"cross-dot": "Перекресток"
"cross-dot": "Перекресток",
"common.selected-wire": "Выбранная линия"
}

View File

@ -24,5 +24,6 @@
"common.port": "端口",
"common.instance": "例化模块",
"setting.general-color-setting": "通用颜色设置",
"cross-dot": "交叉点"
"cross-dot": "交叉点",
"common.selected-wire": "被选中的线"
}

View File

@ -24,5 +24,6 @@
"common.port": "端口",
"common.instance": "實例化模組",
"setting.general-color-setting": "通用顏色設定",
"cross-dot": "交叉點"
"cross-dot": "交叉點",
"common.selected-wire": "被選中的線"
}