/* eslint-disable */ import * as d3 from 'd3'; import { globalSetting } from '../global'; import { NetlistRender } from '.'; import { LAYOUT_CONSTANT, LINE_WIDTH } from './layout'; import { svgResource } from '../skin/draw'; 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} */ this.id2manager = rootRender.id2manager; this.arrowHeight = 12; this.arrowWidth = 12; } /** * @description 将 elknode 关于 wire 的数据添加为 d3 数据项目 * @param {ElkPoint[]} points * @param {ElkEdge} edge */ addAsD3DataItems(points, edge) { 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(' '); // 判断当前的朝向 const endPoint = points.at(-1); const direction = endPoint.x > points.at(-2).x ? 'right' : 'left'; const arrowX = direction === 'right' ? endPoint.x - LAYOUT_CONSTANT.CELL_PORT_WIDTH - this.arrowWidth: endPoint.x + LAYOUT_CONSTANT.CELL_PORT_WIDTH const arrowY = endPoint.y - this.arrowHeight / 2; this.data.push({ svg: lineSvg, endPoint: points.at(-1), arrow: { icon: direction + '-arrow', x: arrowX, y: arrowY, width: this.arrowWidth, height: this.arrowHeight }, active: false }); } 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)'); 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; }); arrowSelections.raise(); 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', function() { if (id2animation.has(data.id)) { 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)'); }); if (globalSetting.renderAnimation) { lineSelections = lineSelections .transition() .duration(1000); } lineSelections .attr('stroke-width', 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; }