基于 vue3.x 的流程图绘制( 三 )

简单一点来说,第一个控制点是根据起始点来算的,第二个控制点是跟根据结束点来算的 。算的方式是根据当前点相对于节点的方向,继续往前算一段距离,而这段距离是根据起止两个点的最大相对距离的一半(可能有点绕...) 。
绘制方法:
// 绘制贝塞尔曲线const drawBezier = ({ ctx, points }: WF.DrawLineType, highlight?: boolean) => {ctx.beginPath()ctx.moveTo(points[0][0], points[0][1])ctx.bezierCurveTo(points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1])shadowLine(ctx, highlight)ctx.stroke()ctx.restore()ctx.globalCompositeOperation = 'source-over'//目标图像上显示源图像}复制代码节点与连接线的选择节点是用 div 来渲染的,所以节点的选择可以忽略,然后就是连接点的选择,首先第一点是鼠标在移动的时候都要判断鼠标的当前位置下面是否有连接线,所以这里就有 3 种判断方法,呃... 严格来说是两种,因为折线是多条直线,所以是按直线的判断方法来 。
// 判断当前鼠标位置是否有线export const isAboveLine = (offsetX: number, offsetY: number, points: WF.LineInfo[]) => {for (let i = points.length - 1; i >= 0; --i) {const innerPonints = points[i].pointslet pre: [number, number], cur: [number, number]// 非曲线判断方法if (points[i].type !== 'bezier') {for (let j = 1; j < innerPonints.length; j++) {pre = innerPonints[j - 1]cur = innerPonints[j]if (getDistance([offsetX, offsetY], pre, cur) < 20) {return points[i]}}} else {// 先用 x 求出对应的 t,用 t 求相应位置的 y,再比较得出的 y 与 offsetY 之间的差值const tsx = getBezierT(innerPonints[0][0], innerPonints[1][0], innerPonints[2][0], innerPonints[3][0], offsetX)for (let x = 0; x < 3; x++) {if (tsx[x] <= 1 && tsx[x] >= 0) {const ny = getThreeBezierPoint(tsx[x], innerPonints[0], innerPonints[1], innerPonints[2], innerPonints[3])if (Math.abs(ny[1] - offsetY) < 8) {return points[i]}}}// 如果上述没有结果,则用 y 求出对应的 t,再用 t 求出对应的 x,与 offsetX 进行匹配const tsy = getBezierT(innerPonints[0][1], innerPonints[1][1], innerPonints[2][1], innerPonints[3][1], offsetY)for (let y = 0; y < 3; y++) {if (tsy[y] <= 1 && tsy[y] >= 0) {const nx = getThreeBezierPoint(tsy[y], innerPonints[0], innerPonints[1], innerPonints[2], innerPonints[3])if (Math.abs(nx[0] - offsetX) < 8) {return points[i]}}}}}return false}复制代码直线的判断方法是点到线段的距离:
/** * 求点到线段的距离 * @param {number} pt 直线外的点 * @param {number} p 直线内的点1 * @param {number} q 直线内的点2 * @returns {number} 距离 */function getDistance(pt: [number, number], p: [number, number], q: [number, number]) {const pqx = q[0] - p[0]const pqy = q[1] - p[1]let dx = pt[0] - p[0]let dy = pt[1] - p[1]const d = pqx * pqx + pqy * pqy// qp线段长度的平方let t = pqx * dx + pqy * dy// p pt向量 点积 pq 向量(p相当于A点,q相当于B点,pt相当于P点)if (d > 0) {// 除数不能为0; 如果为零 t应该也为零 。下面计算结果仍然成立 。t /= d// 此时t 相当于 上述推导中的 r 。}if (t < 0) {// 当t(r)< 0时,最短距离即为 pt点 和 p点(A点和P点)之间的距离 。t = 0} else if (t > 1) { // 当t(r)> 1时,最短距离即为 pt点 和 q点(B点和P点)之间的距离 。t = 1}// t = 0,计算 pt点 和 p点的距离; t = 1, 计算 pt点 和 q点 的距离; 否则计算 pt点 和 投影点 的距离 。dx = p[0] + t * pqx - pt[0]dy = p[1] + t * pqy - pt[1]return dx * dx + dy * dy}复制代码关于曲线的判断方法比较复杂,这里就不多介绍,想了解的可以去看这篇: 如何判断一个坐标点是否在三阶贝塞尔曲线附近
连接线还有一个功能就是双击连接线后可以编辑这条连接线的备注信息 。这个备注信息的位置是在当前连接线的中心点位置 。所以我们需要求出中心点,这个相对简单 。
// 获取一条直线的中点坐标const getStraightLineCenterPoint = ([[x1, y1], [x2, y2]]: [number, number][]): [number, number] => {return [(x1 + x2) / 2, (y1 + y2) / 2]}// 获取一条折线的中点坐标const getBrokenCenterPoint = (points: [number, number][]): [number, number] => {const lineDistancehalf = getLineDistance(points) >> 1let distanceSum = 0, pre = 0, tp: [number, number][] = [], distance = 0for (let i = 1; i < points.length; i++) {pre = getTwoPointDistance(points[i - 1], points[i])if (distanceSum + pre > lineDistancehalf) {tp = [points[i - 1], points[i]]distance = lineDistancehalf - distanceSumbreak}distanceSum += pre}if (!tp.length) {return [0, 0]}let x = tp[0][0], y = tp[0][1]if (tp[0][0] === tp[1][0]) {if (tp[0][1] > tp[1][1]) {y -= distance} else {y += distance}} else {if (tp[0][0] > tp[1][0]) {x -= distance} else {x += distance}}return [x, y]}复制代码


推荐阅读