297 lines
9.3 KiB
JavaScript
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;
|
|
}
|
|
} |