Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(draw): support draw line for element with angle #WIK-14850 #785

Merged
merged 5 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-carrots-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@plait/draw': minor
---

support draw line for element with angle
18 changes: 12 additions & 6 deletions packages/common/src/utils/vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ export function getPointByVectorComponent(point: Point, vector: Vector, componen
return [point[0] + (vector[0] / distance) * component, point[1] + (vector[1] / distance) * component];
}

export function getPointByVectorDirectionComponent(
point: Point,
unitVector: Vector,
directionComponent: number,
isHorizontal: boolean
) {
export function getPointByVectorDirectionComponent(point: Point, unitVector: Vector, directionComponent: number, isHorizontal: boolean) {
if (isHorizontal) {
return [point[0] + directionComponent, point[1] + (directionComponent / unitVector[0]) * unitVector[1]] as Point;
} else {
Expand All @@ -40,3 +35,14 @@ export function rotateVectorAnti90(vector: Vector): Vector {
const rotatedY = -x;
return [rotatedX, rotatedY];
}

export function rotateVector(vector: Vector, angle: number): Vector {
if (!angle) {
return vector;
}
const x = vector[0];
const y = vector[1];
const rotatedX = x * Math.cos(angle) - y * Math.sin(angle);
const rotatedY = x * Math.sin(angle) + y * Math.cos(angle);
return [rotatedX, rotatedY];
}
4 changes: 4 additions & 0 deletions packages/core/src/utils/angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ export const rotatedDataPoints = (points: Point[], rotateCenterPoint: Point, ang
const { offsetX, offsetY } = getOffsetAfterRotate(RectangleClient.getRectangleByPoints(points), rotateCenterPoint, angle);
return points.map(p => [p[0] + offsetX, p[1] + offsetY]) as Point[];
};

export const hasValidAngle = (node: PlaitElement) => {
return node.angle && node.angle !== 0;
};
43 changes: 36 additions & 7 deletions packages/draw/src/interfaces/line.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Direction, PlaitBoard, PlaitElement, Point, PointOfRectangle, Vector, getElementById } from '@plait/core';
import {
Direction,
PlaitBoard,
PlaitElement,
Point,
PointOfRectangle,
RectangleClient,
Vector,
getElementById,
rotatePoints
} from '@plait/core';
import { Element } from 'slate';
import { PlaitGeometry } from './geometry';
import { StrokeStyle } from './element';
Expand Down Expand Up @@ -107,12 +117,31 @@ export const PlaitLine = {
return line.target.boundId === element.id;
},
getPoints(board: PlaitBoard, line: PlaitLine) {
let sourcePoint = line.source.boundId
? getConnectionPoint(getElementById<PlaitGeometry>(board, line.source.boundId)!, line.source.connection!)
: line.points[0];
let targetPoint = line.target.boundId
? getConnectionPoint(getElementById<PlaitGeometry>(board, line.target.boundId)!, line.target.connection!)
: line.points[line.points.length - 1];
let sourcePoint;
if (line.source.boundId) {
const sourceElement = getElementById<PlaitGeometry>(board, line.source.boundId)!;
const sourceConnectionPoint = getConnectionPoint(sourceElement, line.source.connection!);
sourcePoint = rotatePoints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

验证 hasValidAngle

[sourceConnectionPoint],
RectangleClient.getCenterPoint(board.getRectangle(sourceElement)!),
sourceElement.angle
)[0];
} else {
sourcePoint = line.points[0];
}

let targetPoint;
if (line.target.boundId) {
const targetElement = getElementById<PlaitGeometry>(board, line.target.boundId)!;
const targetConnectionPoint = getConnectionPoint(targetElement, line.target.connection!);
targetPoint = rotatePoints(
[targetConnectionPoint],
RectangleClient.getCenterPoint(board.getRectangle(targetElement)!),
targetElement.angle
)[0];
} else {
targetPoint = line.points[line.points.length - 1];
}
const restPoints = line.points.length > 2 ? line.points.slice(1, line.points.length - 1) : [];
return [sourcePoint, ...restPoints, targetPoint];
}
Expand Down
22 changes: 20 additions & 2 deletions packages/draw/src/plugins/with-line-auto-complete-reaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { CursorClass, PlaitBoard, PlaitElement, RgbaToHEX, drawCircle, isSelectionMoving, toHostPoint, toViewBoxPoint } from '@plait/core';
import {
CursorClass,
PlaitBoard,
PlaitElement,
RectangleClient,
RgbaToHEX,
drawCircle,
isSelectionMoving,
rotatePoints,
setAngleForG,
toHostPoint,
toViewBoxPoint
} from '@plait/core';
import { PlaitDrawElement } from '../interfaces';
import { getAutoCompletePoints, getHitIndexOfAutoCompletePoint, getSelectedDrawElements } from '../utils';
import { GeometryComponent } from '../geometry.component';
Expand All @@ -15,8 +27,13 @@ export const withLineAutoCompleteReaction = (board: PlaitBoard) => {
const targetElement = selectedElements.length === 1 && selectedElements[0];
const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
if (!PlaitBoard.isReadonly(board) && !isSelectionMoving(board) && targetElement && PlaitDrawElement.isShape(targetElement)) {
const [rotatedMovingPoint] = rotatePoints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

验证 hasValidAngle

[movingPoint],
RectangleClient.getCenterPoint(board.getRectangle(targetElement)!),
-targetElement.angle
);
const points = getAutoCompletePoints(targetElement);
const hitIndex = getHitIndexOfAutoCompletePoint(movingPoint, points);
const hitIndex = getHitIndexOfAutoCompletePoint(rotatedMovingPoint, points);
const hitPoint = points[hitIndex];
const component = PlaitElement.getComponent(targetElement) as GeometryComponent;
component.lineAutoCompleteGenerator!.recoverAutoCompleteG();
Expand All @@ -29,6 +46,7 @@ export const withLineAutoCompleteReaction = (board: PlaitBoard) => {
});
PlaitBoard.getElementActiveHost(board).append(reactionG);
PlaitBoard.getBoardContainer(board).classList.add(CursorClass.crosshair);
setAngleForG(reactionG, RectangleClient.getCenterPoint(board.getRectangle(targetElement)!), targetElement.angle);
}
}
pointerMove(event);
Expand Down
20 changes: 18 additions & 2 deletions packages/draw/src/plugins/with-line-auto-complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
Transforms,
addSelectedElement,
clearSelectedElement,
createDebugGenerator,
createG,
distanceBetweenPointAndPoint,
rotatePoints,
temporaryDisableSelection,
toHostPoint,
toViewBoxPoint
Expand Down Expand Up @@ -42,7 +44,12 @@ export const withLineAutoComplete = (board: PlaitBoard) => {
const clickPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
if (!PlaitBoard.isReadonly(board) && targetElement && PlaitDrawElement.isShape(targetElement)) {
const points = getAutoCompletePoints(targetElement);
const index = getHitIndexOfAutoCompletePoint(clickPoint, points);
const [rotatedClickPoint] = rotatePoints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasValidAngle

[clickPoint],
RectangleClient.getCenterPoint(board.getRectangle(targetElement)!),
-targetElement.angle
);
const index = getHitIndexOfAutoCompletePoint(rotatedClickPoint, points);
const hitPoint = points[index];
if (hitPoint) {
temporaryDisableSelection(board as PlaitOptionsBoard);
Expand All @@ -59,7 +66,12 @@ export const withLineAutoComplete = (board: PlaitBoard) => {
lineShapeG = createG();
let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
if (startPoint && sourceElement) {
const distance = distanceBetweenPointAndPoint(...movingPoint, ...startPoint);
const [rotatedStartPoint] = rotatePoints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasValidAngle

[startPoint],
RectangleClient.getCenterPoint(board.getRectangle(sourceElement)!),
sourceElement.angle
);
const distance = distanceBetweenPointAndPoint(...movingPoint, ...rotatedStartPoint);
if (distance > PRESS_AND_MOVE_BUFFER) {
const rectangle = RectangleClient.getRectangleByPoints(sourceElement.points);
const shape = getShape(sourceElement);
Expand All @@ -69,6 +81,10 @@ export const withLineAutoComplete = (board: PlaitBoard) => {
const crossingPoint = engine.getNearestCrossingPoint(rectangle, startPoint);
sourcePoint = crossingPoint;
}

if (sourceElement.angle) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasValidAngle

sourcePoint = rotatePoints([sourcePoint], RectangleClient.getCenterPoint(rectangle), sourceElement.angle)[0];
}
temporaryElement = handleLineCreating(board, LineShape.elbow, sourcePoint, movingPoint, sourceElement, lineShapeG);
}
}
Expand Down
10 changes: 8 additions & 2 deletions packages/draw/src/plugins/with-line-bound-reaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
RectangleClient,
SELECTION_BORDER_COLOR,
drawCircle,
rotatePoints,
setAngleForG,
toHostPoint,
toViewBoxPoint
} from '@plait/core';
Expand Down Expand Up @@ -37,9 +39,10 @@ export const withLineBoundReaction = (board: PlaitBoard) => {
if (isLinePointer || isLineResizing) {
const hitElement = getHitOutlineGeometry(board, movingPoint, -4);
if (hitElement) {
boundShapeG = drawBoundMask(board, hitElement);
let nearestPoint = getNearestPoint(hitElement, movingPoint);
const rectangle = RectangleClient.getRectangleByPoints(hitElement.points);
boundShapeG = drawBoundMask(board, hitElement);
const [rotatedMovingPoint] = rotatePoints([movingPoint], RectangleClient.getCenterPoint(rectangle), -hitElement.angle);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasValidAngle

let nearestPoint = getNearestPoint(hitElement, rotatedMovingPoint);
const activeRectangle = RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH);
const hitConnector = getHitConnectorPoint(nearestPoint, hitElement, activeRectangle);
nearestPoint = hitConnector ? hitConnector : nearestPoint;
Expand All @@ -51,6 +54,9 @@ export const withLineBoundReaction = (board: PlaitBoard) => {
});
boundShapeG.appendChild(circleG);
PlaitBoard.getElementActiveHost(board).append(boundShapeG);
if (hitElement.angle) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasValidAngle

setAngleForG(boundShapeG, RectangleClient.getCenterPoint(rectangle), hitElement.angle);
}
}
}
pointerMove(event);
Expand Down
10 changes: 8 additions & 2 deletions packages/draw/src/plugins/with-line-resize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Path, PlaitBoard, PlaitNode, Point } from '@plait/core';
import { Path, PlaitBoard, PlaitNode, Point, RectangleClient, rotatePoints } from '@plait/core';
import { ResizeRef, ResizeState, WithResizeOptions, isSourceAndTargetIntersect, simplifyOrthogonalPoints, withResize } from '@plait/common';
import { getSelectedLineElements } from '../utils/selected';
import { getHitLineResizeHandleRef, LineResizeHandle } from '../utils/position/line';
Expand Down Expand Up @@ -80,7 +80,13 @@ export const withLineResize = (board: PlaitBoard) => {
const object = resizeRef.handle === LineResizeHandle.source ? source : target;
points[handleIndex] = resizeState.endPoint;
if (hitElement) {
object.connection = getConnectionByNearestPoint(board, resizeState.endPoint, hitElement);
const [rotatedEndPoint] = rotatePoints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasValidAngle

[resizeState.endPoint],
RectangleClient.getCenterPoint(board.getRectangle(hitElement)!),
-hitElement.angle
);

object.connection = getConnectionByNearestPoint(board, rotatedEndPoint, hitElement);
object.boundId = hitElement.id;
} else {
object.connection = undefined;
Expand Down
34 changes: 26 additions & 8 deletions packages/draw/src/utils/line/elbow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Point, PlaitBoard, getElementById, RectangleClient, Vector } from '@plait/core';
import { Point, PlaitBoard, getElementById, RectangleClient, Vector, rotatePoints } from '@plait/core';
import {
getPoints,
getPointByVectorComponent,
Expand All @@ -12,6 +12,8 @@ import { createGeometryElement } from '../geometry';
import { getStrokeWidthByElement } from '../style/stroke';
import { getElbowLineRouteOptions, getLineHandleRefPair } from './line-common';
import { getMidKeyPoints, getMirrorDataPoints, hasIllegalElbowPoint } from './line-resize';
import { getShape } from '../shape';
import { getEngine } from '../../engines';

export const getElbowPoints = (board: PlaitBoard, element: PlaitLine) => {
const handleRefPair = getLineHandleRefPair(board, element);
Expand Down Expand Up @@ -40,7 +42,6 @@ export const getElbowPoints = (board: PlaitBoard, element: PlaitLine) => {
if (hasIllegalElbowPoint(midDataPoints)) {
return simplifyOrthogonalPoints(keyPoints);
}

const nextDataPoints = [simplifiedNextKeyPoints[0], ...midDataPoints, simplifiedNextKeyPoints[simplifiedNextKeyPoints.length - 1]];
const mirrorDataPoints = getMirrorDataPoints(board, nextDataPoints, simplifiedNextKeyPoints, params);
// console.log(mirrorDataPoints, 'mirrorDataPoints');
Expand Down Expand Up @@ -95,14 +96,31 @@ export const getSourceAndTargetRectangle = (board: PlaitBoard, element: PlaitLin
const target = handleRefPair.target;
targetElement = createFakeElement(target.point, target.vector);
}
const sourceRectangle = RectangleClient.inflate(
RectangleClient.getRectangleByPoints(sourceElement.points),
getStrokeWidthByElement(sourceElement) * 2

let sourceRectangle = RectangleClient.getRectangleByPoints(sourceElement.points);
MissLixf marked this conversation as resolved.
Show resolved Hide resolved
const sourceShape = getShape(sourceElement);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sourceShape 和 sourceEngine 有用到吗?

const sourceEngine = getEngine(sourceShape);
const sourceElementCornerPoints = sourceEngine.getCornerPoints(sourceRectangle);
const rotatedSourceElementCornerPoints = rotatePoints(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

判断 hasValidAngle

sourceElementCornerPoints,
RectangleClient.getCenterPoint(sourceRectangle),
sourceElement.angle
);
const targetRectangle = RectangleClient.inflate(
RectangleClient.getRectangleByPoints(targetElement.points),
getStrokeWidthByElement(targetElement) * 2
sourceRectangle = RectangleClient.getRectangleByPoints(rotatedSourceElementCornerPoints);
sourceRectangle = RectangleClient.inflate(sourceRectangle, getStrokeWidthByElement(sourceElement) * 2);

let targetRectangle = RectangleClient.getRectangleByPoints(targetElement.points);
const shape = getShape(targetElement);
const engine = getEngine(shape);
const targetElementCornerPoints = engine.getCornerPoints(targetRectangle);
const rotatedTargetElementCornerPoints = rotatePoints(
targetElementCornerPoints,
RectangleClient.getCenterPoint(targetRectangle),
targetElement.angle
);
targetRectangle = RectangleClient.getRectangleByPoints(rotatedTargetElementCornerPoints);
targetRectangle = RectangleClient.inflate(targetRectangle, getStrokeWidthByElement(targetElement) * 2);

return {
sourceRectangle,
targetRectangle
Expand Down
44 changes: 39 additions & 5 deletions packages/draw/src/utils/line/line-basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ import {
createMask,
createRect,
distanceBetweenPointAndPoint,
catmullRomFitting
catmullRomFitting,
rotatePoints,
hasValidAngle
} from '@plait/core';
import { pointsOnBezierCurves } from 'points-on-curve';
import { getPointOnPolyline, getPointByVectorComponent, removeDuplicatePoints, getExtendPoint, isSourceAndTargetIntersect } from '@plait/common';
import {
getPointOnPolyline,
getPointByVectorComponent,
removeDuplicatePoints,
getExtendPoint,
isSourceAndTargetIntersect
} from '@plait/common';
import { LineHandle, LineMarkerType, LineShape, LineText, PlaitDrawElement, PlaitLine, PlaitShape } from '../../interfaces';
import { getNearestPoint } from '../geometry';
import { getLineDashByElement, getStrokeColorByElement, getStrokeWidthByElement } from '../style/stroke';
Expand Down Expand Up @@ -244,8 +252,34 @@ export const handleLineCreating = (
lineShapeG: SVGGElement
) => {
const hitElement = getHitOutlineGeometry(board, movingPoint, REACTION_MARGIN);
const targetConnection = hitElement ? getConnectionByNearestPoint(board, movingPoint, hitElement) : undefined;
const connection = sourceElement ? getConnectionByNearestPoint(board, sourcePoint, sourceElement) : undefined;
let targetConnection = undefined,
sourceConnection = undefined;
if (hitElement) {
MissLixf marked this conversation as resolved.
Show resolved Hide resolved
if (hasValidAngle(hitElement)) {
const rotatedMovingPoint = rotatePoints(
[movingPoint],
RectangleClient.getCenterPoint(board.getRectangle(hitElement)!),
-hitElement.angle
)[0];
targetConnection = getConnectionByNearestPoint(board, rotatedMovingPoint, hitElement);
} else {
targetConnection = getConnectionByNearestPoint(board, movingPoint, hitElement);
}
}

if (sourceElement) {
if (hasValidAngle(sourceElement)) {
const revertSourcePoint = rotatePoints(
[sourcePoint],
RectangleClient.getCenterPoint(board.getRectangle(sourceElement)!),
-sourceElement.angle
)[0];
sourceConnection = getConnectionByNearestPoint(board, revertSourcePoint, sourceElement);
} else {
sourceConnection = getConnectionByNearestPoint(board, sourcePoint, sourceElement);
}
}

const targetBoundId = hitElement ? hitElement.id : undefined;
const lineGenerator = new LineShapeGenerator(board);
const memorizedLatest = getLineMemorizedLatest();
Expand All @@ -257,7 +291,7 @@ export const handleLineCreating = (
const temporaryLineElement = createLineElement(
lineShape,
[sourcePoint, movingPoint],
{ marker: sourceMarker || LineMarkerType.none, connection: connection, boundId: sourceElement?.id },
{ marker: sourceMarker || LineMarkerType.none, connection: sourceConnection, boundId: sourceElement?.id },
{ marker: targetMarker || LineMarkerType.arrow, connection: targetConnection, boundId: targetBoundId },
[],
{
Expand Down
Loading