UNPKG

@logicflow/core

Version:

LogicFlow, help you quickly create flowcharts

399 lines (398 loc) 19.5 kB
import { ResizeControlIndex } from '../view/Control'; import { cloneDeep, find, forEach } from 'lodash-es'; import { EventType } from '../constant'; import { calculatePointAfterRotateAngle, getNewCenter, radianToAngle, } from '../algorithm/rotate'; export function calculateWidthAndHeight(startRotatedTouchControlPoint, endRotatedTouchControlPoint, oldCenter, angle, freezeWidth, freezeHeight, oldWidth, oldHeight) { if (freezeWidth === void 0) { freezeWidth = false; } if (freezeHeight === void 0) { freezeHeight = false; } // 假设目前触摸的是右下角的control点 // 计算出来左上角的control坐标,resize过程左上角的control坐标保持不变 var freezePoint = { x: oldCenter.x - (startRotatedTouchControlPoint.x - oldCenter.x), y: oldCenter.y - (startRotatedTouchControlPoint.y - oldCenter.y), }; // 【touchEndPoint】右下角 + freezePoint左上角 计算出新的中心点 var newCenter = getNewCenter(freezePoint, endRotatedTouchControlPoint); // 得到【touchEndPoint】右下角-没有transform的坐标 var endZeroTouchControlPoint = calculatePointAfterRotateAngle(endRotatedTouchControlPoint, newCenter, -angle); // ---------- 使用transform之前的坐标计算出新的width和height ---------- // 得到左上角---没有transform的坐标 var zeroFreezePoint = calculatePointAfterRotateAngle(freezePoint, newCenter, -angle); if (freezeWidth) { // 如果固定width,那么不能单纯使用endZeroTouchControlPoint.x=startZeroTouchControlPoint.x // 因为去掉transform的左上角不一定是重合的,我们要保证的是transform后的左上角重合 var newWidth = Math.abs(endZeroTouchControlPoint.x - zeroFreezePoint.x); var widthDx = newWidth - oldWidth; // 点击的是左边锚点,是+widthDx/2,点击是右边锚点,是-widthDx/2 if (newCenter.x > endZeroTouchControlPoint.x) { // 当前触摸的是左边锚点 newCenter.x = newCenter.x + widthDx / 2; } else { // 当前触摸的是右边锚点 newCenter.x = newCenter.x - widthDx / 2; } } if (freezeHeight) { var newHeight = Math.abs(endZeroTouchControlPoint.y - zeroFreezePoint.y); var heightDy = newHeight - oldHeight; if (newCenter.y > endZeroTouchControlPoint.y) { // 当前触摸的是上边锚点 newCenter.y = newCenter.y + heightDy / 2; } else { newCenter.y = newCenter.y - heightDy / 2; } } if (freezeWidth || freezeHeight) { // 如果调整过transform之前的坐标,那么transform后的坐标也会改变,那么算出来的newCenter也得调整 // 由于无论如何rotate,中心点都是不变的,因此我们可以使用transform之前的坐标算出新的中心点 var nowFreezePoint = calculatePointAfterRotateAngle(zeroFreezePoint, newCenter, angle); // 得到当前新rect的左上角与实际上transform后的左上角的偏移量 var dx = nowFreezePoint.x - freezePoint.x; var dy = nowFreezePoint.y - freezePoint.y; // 修正不使用transform的坐标: 左上角、右下角、center newCenter.x = newCenter.x - dx; newCenter.y = newCenter.y - dy; zeroFreezePoint = calculatePointAfterRotateAngle(freezePoint, newCenter, -angle); endZeroTouchControlPoint = { x: newCenter.x - (zeroFreezePoint.x - newCenter.x), y: newCenter.y - (zeroFreezePoint.y - newCenter.y), }; } // transform之前的坐标的左上角+右下角计算出宽度和高度 var width = Math.abs(endZeroTouchControlPoint.x - zeroFreezePoint.x); var height = Math.abs(endZeroTouchControlPoint.y - zeroFreezePoint.y); // ---------- 使用transform之前的坐标计算出新的width和height ---------- if (freezeWidth) { // 理论计算出来的width应该等于oldWidth // 但是有误差,比如oldWidth = 100; newWidth=100.000000000001 // 会在handleResize()限制放大缩小的最大最小范围中被阻止滑动 width = oldWidth; } if (freezeHeight) { height = oldHeight; } return { width: width, height: height, center: newCenter, }; } function recalcRotatedResizeInfo(pct, resizeInfo, rotate, controlX, controlY, oldCenterX, oldCenterY, freezeWidth, freezeHeight) { if (freezeWidth === void 0) { freezeWidth = false; } if (freezeHeight === void 0) { freezeHeight = false; } // 假设我们触摸的点是右下角的control var deltaX = resizeInfo.deltaX, deltaY = resizeInfo.deltaY, oldWidth = resizeInfo.width, oldHeight = resizeInfo.height; var angle = radianToAngle(rotate); // 右下角的control var startZeroTouchControlPoint = { x: controlX, // control锚点的坐标x y: controlY, // control锚点的坐标y }; var oldCenter = { x: oldCenterX, y: oldCenterY }; // 右下角的control坐标(transform后的-touchStartPoint) var startRotatedTouchControlPoint = calculatePointAfterRotateAngle(startZeroTouchControlPoint, oldCenter, angle); // 右下角的control坐标(transform后的-touchEndPoint) var endRotatedTouchControlPoint = { x: startRotatedTouchControlPoint.x + deltaX, y: startRotatedTouchControlPoint.y + deltaY, }; // 计算出新的宽度和高度以及新的中心点 var _a = calculateWidthAndHeight(startRotatedTouchControlPoint, endRotatedTouchControlPoint, oldCenter, angle, freezeWidth, freezeHeight, oldWidth, oldHeight), newWidth = _a.width, newHeight = _a.height, newCenter = _a.center; // calculateWidthAndHeight()得到的是整个宽度,比如圆pct=0.5,此时newWidth等于整个圆直径 resizeInfo.width = newWidth * pct; resizeInfo.height = newHeight * pct; // BaseNodeModel.resize(deltaX/2, deltaY/2),因此这里要*2 resizeInfo.deltaX = (newCenter.x - oldCenter.x) * 2; resizeInfo.deltaY = (newCenter.y - oldCenter.y) * 2; return resizeInfo; } /** * 计算 Control 拖动后,节点的高度信息 * @param index * @param resizeInfo * @param pct * @param freezeWidth * @param freezeHeight */ export var recalcResizeInfo = function (index, resizeInfo, pct, freezeWidth, freezeHeight, rotate, controlX, controlY, oldCenterX, oldCenterY, forceProportional) { if (pct === void 0) { pct = 1; } if (freezeWidth === void 0) { freezeWidth = false; } if (freezeHeight === void 0) { freezeHeight = false; } if (rotate === void 0) { rotate = 0; } if (forceProportional === void 0) { forceProportional = false; } var nextResizeInfo = cloneDeep(resizeInfo); var deltaX = nextResizeInfo.deltaX, deltaY = nextResizeInfo.deltaY; var width = nextResizeInfo.width, height = nextResizeInfo.height, PCTResizeInfo = nextResizeInfo.PCTResizeInfo; if (PCTResizeInfo) { var sensitivity = 4; // 越低越灵敏 var deltaScale = 0; var combineDelta = 0; switch (index) { case ResizeControlIndex.LEFT_TOP: combineDelta = (deltaX * -1 - deltaY) / sensitivity; break; case ResizeControlIndex.RIGHT_TOP: combineDelta = (deltaX - deltaY) / sensitivity; break; case ResizeControlIndex.RIGHT_BOTTOM: combineDelta = (deltaX + deltaY) / sensitivity; break; case ResizeControlIndex.LEFT_BOTTOM: combineDelta = (deltaX * -1 + deltaY) / sensitivity; break; default: break; } if (combineDelta !== 0) { deltaScale = Math.round((combineDelta / PCTResizeInfo.ResizeBasis.basisHeight) * 100000) / 1000; } PCTResizeInfo.ResizePCT.widthPCT = Math.max(Math.min(PCTResizeInfo.ResizePCT.widthPCT + deltaScale, PCTResizeInfo.ScaleLimit.maxScaleLimit), PCTResizeInfo.ScaleLimit.minScaleLimit); PCTResizeInfo.ResizePCT.heightPCT = Math.max(Math.min(PCTResizeInfo.ResizePCT.heightPCT + deltaScale, PCTResizeInfo.ScaleLimit.maxScaleLimit), PCTResizeInfo.ScaleLimit.minScaleLimit); var spcWidth = Math.round((PCTResizeInfo.ResizePCT.widthPCT * PCTResizeInfo.ResizeBasis.basisWidth) / 100); var spcHeight = Math.round((PCTResizeInfo.ResizePCT.heightPCT * PCTResizeInfo.ResizeBasis.basisHeight) / 100); switch (index) { case ResizeControlIndex.LEFT_TOP: deltaX = width - spcWidth; deltaY = height - spcHeight; break; case ResizeControlIndex.RIGHT_TOP: deltaX = spcWidth - width; deltaY = height - spcHeight; break; case ResizeControlIndex.RIGHT_BOTTOM: deltaX = spcWidth - width; deltaY = spcHeight - height; break; case ResizeControlIndex.LEFT_BOTTOM: deltaX = width - spcWidth; deltaY = spcHeight - height; break; default: break; } return nextResizeInfo; } if (rotate % (2 * Math.PI) !== 0 && controlX !== undefined && controlY !== undefined) { // 角度rotate不为0,则触发另外的计算修正resize的deltaX和deltaY // 因为rotate不为0的时候,左上角的坐标一直在变化 // 角度rotate不为0得到的resizeInfo.deltaX仅仅代表中心点的变化,而不是宽度的变化 return recalcRotatedResizeInfo(pct, nextResizeInfo, rotate, controlX, controlY, oldCenterX, oldCenterY, freezeWidth, freezeHeight); } //Shift键等比缩放逻辑 if (forceProportional) { // 计算当前的宽高比 var aspectRatio = width / height; // 根据拖拽方向确定主要的缩放参考 var primaryDelta = 0; var newWidth = width; var newHeight = height; switch (index) { case ResizeControlIndex.LEFT_TOP: // 取绝对值较大的delta作为主要缩放参考 primaryDelta = Math.abs(deltaX) > Math.abs(deltaY) ? -deltaX : -deltaY; if (aspectRatio >= 1) { // 宽度大于等于高度,以宽度为基准 newWidth = width + primaryDelta; newHeight = newWidth / aspectRatio; } else { // 高度大于宽度,以高度为基准 newHeight = height + primaryDelta; newWidth = newHeight * aspectRatio; } nextResizeInfo.width = newWidth; nextResizeInfo.height = newHeight; nextResizeInfo.deltaX = width - newWidth; nextResizeInfo.deltaY = height - newHeight; break; case ResizeControlIndex.RIGHT_TOP: primaryDelta = Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : -deltaY; if (aspectRatio >= 1) { newWidth = width + primaryDelta; newHeight = newWidth / aspectRatio; } else { newHeight = height - primaryDelta; newWidth = newHeight * aspectRatio; } nextResizeInfo.width = newWidth; nextResizeInfo.height = newHeight; nextResizeInfo.deltaX = newWidth - width; nextResizeInfo.deltaY = height - newHeight; break; case ResizeControlIndex.RIGHT_BOTTOM: primaryDelta = Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY; if (aspectRatio >= 1) { newWidth = width + primaryDelta; newHeight = newWidth / aspectRatio; } else { newHeight = height + primaryDelta; newWidth = newHeight * aspectRatio; } nextResizeInfo.width = newWidth; nextResizeInfo.height = newHeight; nextResizeInfo.deltaX = newWidth - width; nextResizeInfo.deltaY = newHeight - height; break; case ResizeControlIndex.LEFT_BOTTOM: primaryDelta = Math.abs(deltaX) > Math.abs(deltaY) ? -deltaX : deltaY; if (aspectRatio >= 1) { newWidth = width - primaryDelta; newHeight = newWidth / aspectRatio; } else { newHeight = height + primaryDelta; newWidth = newHeight * aspectRatio; } nextResizeInfo.width = newWidth; nextResizeInfo.height = newHeight; nextResizeInfo.deltaX = width - newWidth; nextResizeInfo.deltaY = newHeight - height; break; default: break; } return nextResizeInfo; } // 原有的非等比缩放逻辑保持不变 switch (index) { case ResizeControlIndex.LEFT_TOP: nextResizeInfo.width = freezeWidth ? width : width - deltaX * pct; nextResizeInfo.height = freezeHeight ? height : height - deltaY * pct; break; case ResizeControlIndex.RIGHT_TOP: nextResizeInfo.width = freezeWidth ? width : width + deltaX * pct; nextResizeInfo.height = freezeHeight ? height : height - deltaY * pct; break; case ResizeControlIndex.RIGHT_BOTTOM: nextResizeInfo.width = freezeWidth ? width : width + deltaX * pct; nextResizeInfo.height = freezeHeight ? height : height + deltaY * pct; break; case ResizeControlIndex.LEFT_BOTTOM: nextResizeInfo.width = freezeWidth ? width : width - deltaX * pct; nextResizeInfo.height = freezeHeight ? height : height + deltaY * pct; break; default: break; } return nextResizeInfo; }; export var updateEdgePointByAnchors = function (nodeModel, graphModel) { // https://github.com/didi/LogicFlow/issues/807 // https://github.com/didi/LogicFlow/issues/875 // 之前的做法,比如Rect是使用getRectResizeEdgePoint()计算边的point缩放后的位置 // getRectResizeEdgePoint()考虑了瞄点在四条边以及在4个圆角的情况 // 使用的是一种等比例缩放的模式,比如: // const pct = (y - beforeNode.y) / (beforeNode.height / 2 - radius) // afterPoint.y = afterNode.y + (afterNode.height / 2 - radius) * pct // 但是用户自定义的getDefaultAnchor()不一定是按照比例编写的 // 它可能是 x: x + 20:每次缩放都会保持在x右边20的位置,因此用户自定义瞄点时,然后产生无法跟随的问题 // 现在的做法是:直接获取用户自定义瞄点的位置,然后用这个位置作为边的新的起点,而不是自己进行计算 var id = nodeModel.id, anchors = nodeModel.anchors; var edges = graphModel.getNodeEdges(id); // 更新边 forEach(edges, function (edge) { if (edge.sourceNodeId === id) { // 边是以该节点为 sourceNode 时 var anchorItem = find(anchors, function (anchor) { return anchor.id === edge.sourceAnchorId; }); if (anchorItem) { edge.updateStartPoint({ x: anchorItem.x, y: anchorItem.y, }); } } else if (edge.targetNodeId === id) { // 边是以该节点为 targetNode 时 var anchorItem = find(anchors, function (anchor) { return anchor.id === edge.targetAnchorId; }); if (anchorItem) { edge.updateEndPoint({ x: anchorItem.x, y: anchorItem.y, }); } } }); }; export var triggerResizeEvent = function (preNodeData, curNodeData, deltaX, deltaY, index, nodeModel, graphModel) { graphModel.eventCenter.emit(EventType.NODE_RESIZE, { preData: preNodeData, data: curNodeData, deltaX: deltaX, deltaY: deltaY, index: index, model: nodeModel, }); }; /** * 处理节点的 resize 事件,提出来放到 utils 中,方便在外面(extension)中使用 * @param x * @param y * @param deltaX * @param deltaY * @param index * @param nodeModel * @param graphModel * @param cancelCallback * @param forceProportional */ export var handleResize = function (_a) { var x = _a.x, y = _a.y, deltaX = _a.deltaX, deltaY = _a.deltaY, index = _a.index, nodeModel = _a.nodeModel, graphModel = _a.graphModel, cancelCallback = _a.cancelCallback, _b = _a.forceProportional, forceProportional = _b === void 0 ? false : _b; var r = nodeModel.r, // circle rx = nodeModel.rx, // ellipse/diamond ry = nodeModel.ry, width = nodeModel.width, // rect/html height = nodeModel.height, PCTResizeInfo = nodeModel.PCTResizeInfo, minWidth = nodeModel.minWidth, minHeight = nodeModel.minHeight, maxWidth = nodeModel.maxWidth, maxHeight = nodeModel.maxHeight, rotate = nodeModel.rotate, oldCenterX = nodeModel.x, oldCenterY = nodeModel.y; var isFreezeWidth = minWidth === maxWidth; var isFreezeHeight = minHeight === maxHeight; var resizeInfo = { width: r || rx || width, height: r || ry || height, deltaX: deltaX, deltaY: deltaY, PCTResizeInfo: PCTResizeInfo, }; var pct = r || (rx && ry) ? 1 / 2 : 1; var controlX = x; var controlY = y; var nextSize = recalcResizeInfo(index, resizeInfo, pct, isFreezeWidth, isFreezeHeight, rotate, controlX, controlY, oldCenterX, oldCenterY, forceProportional); // 限制放大缩小的最大最小范围 if (nextSize.width < minWidth || nextSize.width > maxWidth || nextSize.height < minHeight || nextSize.height > maxHeight) { // this.dragHandler.cancelDrag() cancelCallback === null || cancelCallback === void 0 ? void 0 : cancelCallback(); return; } if (rotate % (2 * Math.PI) == 0 || PCTResizeInfo || controlX === undefined || controlY === undefined) { // rotate!==0并且不是PCTResizeInfo时,即使是isFreezeWidth||isFreezeHeight // recalcRotatedResizeInfo()计算出来的中心点会发生变化 // 如果限制了宽高不变,对应的 x/y 不产生位移 nextSize.deltaX = isFreezeWidth ? 0 : nextSize.deltaX; nextSize.deltaY = isFreezeHeight ? 0 : nextSize.deltaY; } var preNodeData = nodeModel.getData(); var curNodeData = nodeModel.resize(nextSize); // 检测preNodeData和curNodeData是否没变化 if (preNodeData.x === curNodeData.x && preNodeData.y === curNodeData.y) { // 中心点x和y都没有变化,说明无法resize,阻止下面边的更新以及resize事件的emit return; } // 更新边 updateEdgePointByAnchors(nodeModel, graphModel); // 触发 resize 事件 triggerResizeEvent(preNodeData, curNodeData, deltaX, deltaY, index, nodeModel, graphModel); };