2025-01-02 05:28:44 +08:00

297 lines
9.3 KiB
JavaScript

/* eslint-disable */
import * as d3 from 'd3';
import { globalSetting } from '../global';
import { NetlistRender } from '.';
import { getMarginParamter, LAYOUT_CONSTANT, LINE_WIDTH } from './layout';
import { svgResource } from '../skin/draw';
import { PulseLine } from '../skin/plusation';
export class WireRender {
/**
*
* @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/
constructor(selection, rootRender) {
this.selection = selection;
this.rootRender = rootRender;
/**
* @type {BasicD3DataItem[]}
*/
this.data = [];
/**
* @description id 到管理数据项的映射
* @type {Map<string, BasicD3ManagmentItem[]>}
*/
this.id2manager = rootRender.id2manager;
this.idCounter = 0;
this.arrowHeight = 12;
this.arrowWidth = 12;
}
/**
* @description 将 elknode 关于 wire 的数据添加为 d3 数据项目
* @param {import('../jsdoc').ElkPoint[]} points 长度至少为 2 的数组,代表经历过的点集
* @param {import('../jsdoc').ElkEdge} edge
* @param {Map<string, import('../jsdoc').ElkPort>} id2port
* @param {number} width
*/
addAsD3DataItems(points, edge, id2port, width) {
const linePaths = [];
const beginPoint = points.at(0);
const endPoint = points.at(-1);
const lastTwoPoint = points.at(-2);
const sourcePort = id2port.get(edge.sourcePort);
const targetPort = id2port.get(edge.targetPort);
const sourceMargin = getMarginParamter(sourcePort);
const targetMargin = getMarginParamter(targetPort);
beginPoint.x -= sourceMargin.rightMargin;
const direction = judgeDirection(endPoint, lastTwoPoint);
// 特殊情况: targetPort 为 undefined
if (targetPort === undefined) {
const port = id2port.get(edge.orginalTarget);
// TODO: 检查正确性
endPoint.x -= LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN;
} else {
// 判断当前的方向
if (direction === 'up' || direction === 'down') {
lastTwoPoint.x += targetMargin.leftMargin + 1;
endPoint.x += targetMargin.leftMargin + 1;
} else {
endPoint.x += targetMargin.leftMargin;
}
}
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(' ');
const arrowLocation = getArrowLocation(endPoint, direction, this.arrowWidth, this.arrowHeight);
this.data.push({
id: this.idCounter,
svg: lineSvg,
endPoint: points.at(-1),
width: width,
arrow: {
icon: direction + '-arrow',
x: arrowLocation.x,
y: arrowLocation.y,
width: this.arrowWidth,
height: this.arrowHeight
},
active: false
});
this.idCounter ++;
}
render() {
const data = this.data;
const id2manager = this.id2manager;
const _this = this;
const arrowHeight = 12;
const arrowWidth = 12;
let lineSelections = this.selection.selectAll('path.lines')
.data(data)
.enter()
.append('path')
.attr('d', d => d.svg)
.attr('class', 'connection-line')
.attr("fill", "none")
.attr('stroke', 'var(--wire-color)')
this.lineSelections = lineSelections;
const arrows = new Map();
let arrowSelections = this.selection.selectAll('svg.line-arrow')
.data(data)
.enter()
.append(data => {
const arrowInfo = data.arrow;
// 获取箭头 svg
const element = svgResource.get(arrowInfo.icon);
element.setAttribute('x', arrowInfo.x);
element.setAttribute('y', arrowInfo.y);
element.setAttribute('width', arrowInfo.width);
element.setAttribute('height', arrowInfo.height);
element.setAttribute('opacity', 'var(--line-arrow-opacity)');
return element;
})
.each(function(data) {
const selection = d3.select(this);
arrows.set(data.id, selection);
});
arrowSelections.raise();
const id2animation = new Map();
const id2PluseLine = new Map();
lineSelections.on('click', function(_, data) {
data.active = !data.active;
if (data.active) {
const pathSelection = d3.select(this);
const pulse = new PulseLine();
id2PluseLine.set(data.id, pulse);
// 开启脉冲动画
pulse.loadToSelection(_this.selection, data);
pulse.startAnimation();
} else {
if (id2PluseLine.has(data.id)) {
const pulse = id2PluseLine.get(data.id);
pulse.destory();
id2PluseLine.delete(data.id);
// 关闭高亮
const selection = d3.select(this);
selection.attr('stroke', 'var(--wire-color)');
const arrowSelection = arrows.get(data.id);
arrowSelection.selectAll('path').attr('fill', 'var(--wire-color)');
}
}
});
lineSelections.on('mouseenter', function(_, data) {
const selection = d3.select(this);
selection.raise();
selection.attr('stroke', 'var(--wire-active-color)');
const arrowSelection = arrows.get(data.id);
arrowSelection.raise();
arrowSelection.selectAll('path').attr('fill', 'var(--wire-active-color)');
});
lineSelections.on('mouseleave', function(_, data) {
if (!id2PluseLine.has(data.id)) {
const selection = d3.select(this);
selection.attr('stroke', 'var(--wire-color)');
const arrowSelection = arrows.get(data.id);
arrowSelection.selectAll('path').attr('fill', 'var(--wire-color)');
}
});
if (globalSetting.renderAnimation) {
lineSelections = lineSelections
.transition()
.duration(1000);
}
lineSelections
.attr('stroke-width', d => {
const incrementWidth = globalSetting.boldMultiWidthWire ? 1 : 0;
return d.width > 1 ? LINE_WIDTH + incrementWidth: LINE_WIDTH;
})
.each(function (data) {
const selection = d3.select(this);
// 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.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;
}
/**
*
* @param {import('../jsdoc').ElkPoint} lastPoint
* @param {import('../jsdoc').ElkPoint} lastTowPoint
* @returns {'left' | 'right' | 'up' | 'down'}
*/
function judgeDirection(lastPoint, lastTowPoint) {
if (lastPoint.x !== lastTowPoint.x) {
return lastPoint.x > lastTowPoint.x ? 'right': 'left';
} else {
return lastPoint.y > lastTowPoint.y ? 'down': 'up';
}
}
/**
*
* @param {import('../jsdoc').ElkPoint} lastPoint
* @param {'left' | 'right' | 'up' | 'down'} direction
*/
function getArrowLocation(lastPoint, direction, arrowWidth, arrowHeight) {
switch (direction) {
case 'left':
return {
x: lastPoint.x + LAYOUT_CONSTANT.CELL_PORT_WIDTH - 2.5,
y: lastPoint.y - arrowHeight / 2
};
case 'right':
return {
x: lastPoint.x - LAYOUT_CONSTANT.CELL_PORT_WIDTH - arrowWidth + 2.5,
y: lastPoint.y - arrowHeight / 2
};
case 'up':
return {
x: lastPoint.x + LAYOUT_CONSTANT.CELL_PORT_WIDTH - arrowWidth / 2 - 1,
y: lastPoint.y - arrowHeight / 2
};
case 'down':
return {
x: lastPoint.x + LAYOUT_CONSTANT.CELL_PORT_WIDTH - arrowWidth / 2 - 1,
y: lastPoint.y - arrowHeight / 2 - 5
};
default:
break;
}
}