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 all 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];
}
36 changes: 32 additions & 4 deletions packages/core/src/utils/angle.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { PlaitElement, Point, RectangleClient } from '../interfaces';
import { approximately, rotate } from './math';

export const rotatePoints = (points: Point[], centerPoint: Point, angle: number) => {
export const rotatePoints = <T>(points: T, centerPoint: Point, angle: number): T => {
if (!angle) {
angle = 0;
}
return points.map(point => {
return rotate(point[0], point[1], centerPoint[0], centerPoint[1], angle) as Point;
});
if (Array.isArray(points) && typeof points[0] === 'number') {
return rotate(points[0], points[1], centerPoint[0], centerPoint[1], angle) as T;
} else {
return (points as Point[]).map(point => {
return rotate(point[0], point[1], centerPoint[0], centerPoint[1], angle);
}) as T;
}
};

export const getSelectionAngle = (elements: PlaitElement[]) => {
Expand Down Expand Up @@ -51,3 +55,27 @@ 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;
};

export const rotatePointsByElement = <T>(points: T, element: PlaitElement): T | null => {
if (hasValidAngle(element)) {
let rectangle = RectangleClient.getRectangleByPoints(element.points!);
const centerPoint = RectangleClient.getCenterPoint(rectangle);
return rotatePoints(points, centerPoint, element.angle);
} else {
return null;
}
};

export const rotateAntiPointsByElement = <T>(points: T, element: PlaitElement): T | null => {
if (hasValidAngle(element)) {
let rectangle = RectangleClient.getRectangleByPoints(element.points!);
const centerPoint = RectangleClient.getCenterPoint(rectangle);
return rotatePoints(points, centerPoint, -element.angle);
} else {
return null;
}
};
25 changes: 18 additions & 7 deletions packages/draw/src/interfaces/line.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Direction, PlaitBoard, PlaitElement, Point, PointOfRectangle, Vector, getElementById } from '@plait/core';
import { Direction, PlaitBoard, PlaitElement, Point, PointOfRectangle, Vector, getElementById, rotatePointsByElement } from '@plait/core';
import { Element } from 'slate';
import { PlaitGeometry } from './geometry';
import { StrokeStyle } from './element';
Expand Down Expand Up @@ -107,12 +107,23 @@ 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 = rotatePointsByElement(sourceConnectionPoint, sourceElement) || sourceConnectionPoint;
} 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 = rotatePointsByElement(targetConnectionPoint, targetElement) || targetConnectionPoint;
} 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
20 changes: 18 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,17 @@
import { CursorClass, PlaitBoard, PlaitElement, RgbaToHEX, drawCircle, isSelectionMoving, toHostPoint, toViewBoxPoint } from '@plait/core';
import {
CursorClass,
PlaitBoard,
PlaitElement,
RectangleClient,
RgbaToHEX,
drawCircle,
hasValidAngle,
isSelectionMoving,
rotateAntiPointsByElement,
setAngleForG,
toHostPoint,
toViewBoxPoint
} from '@plait/core';
import { PlaitDrawElement } from '../interfaces';
import { getAutoCompletePoints, getHitIndexOfAutoCompletePoint, getSelectedDrawElements } from '../utils';
import { GeometryComponent } from '../geometry.component';
Expand All @@ -16,7 +29,7 @@ export const withLineAutoCompleteReaction = (board: PlaitBoard) => {
const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
if (!PlaitBoard.isReadonly(board) && !isSelectionMoving(board) && targetElement && PlaitDrawElement.isShape(targetElement)) {
const points = getAutoCompletePoints(targetElement);
const hitIndex = getHitIndexOfAutoCompletePoint(movingPoint, points);
const hitIndex = getHitIndexOfAutoCompletePoint(rotateAntiPointsByElement(movingPoint, targetElement) || movingPoint, points);
const hitPoint = points[hitIndex];
const component = PlaitElement.getComponent(targetElement) as GeometryComponent;
component.lineAutoCompleteGenerator!.recoverAutoCompleteG();
Expand All @@ -29,6 +42,9 @@ export const withLineAutoCompleteReaction = (board: PlaitBoard) => {
});
PlaitBoard.getElementActiveHost(board).append(reactionG);
PlaitBoard.getBoardContainer(board).classList.add(CursorClass.crosshair);
if (hasValidAngle(targetElement)) {
setAngleForG(reactionG, RectangleClient.getCenterPoint(board.getRectangle(targetElement)!), targetElement.angle);
}
}
}
pointerMove(event);
Expand Down
23 changes: 20 additions & 3 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,13 @@ import {
Transforms,
addSelectedElement,
clearSelectedElement,
createDebugGenerator,
createG,
distanceBetweenPointAndPoint,
hasValidAngle,
rotateAntiPointsByElement,
rotatePoints,
rotatePointsByElement,
temporaryDisableSelection,
toHostPoint,
toViewBoxPoint
Expand Down Expand Up @@ -42,7 +47,7 @@ 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 index = getHitIndexOfAutoCompletePoint(rotateAntiPointsByElement(clickPoint, targetElement) || clickPoint, points);
const hitPoint = points[index];
if (hitPoint) {
temporaryDisableSelection(board as PlaitOptionsBoard);
Expand All @@ -59,7 +64,11 @@ 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 distance = distanceBetweenPointAndPoint(
...movingPoint,
...(rotatePointsByElement(startPoint, sourceElement) || startPoint)
);

if (distance > PRESS_AND_MOVE_BUFFER) {
const rectangle = RectangleClient.getRectangleByPoints(sourceElement.points);
const shape = getShape(sourceElement);
Expand All @@ -69,7 +78,15 @@ export const withLineAutoComplete = (board: PlaitBoard) => {
const crossingPoint = engine.getNearestCrossingPoint(rectangle, startPoint);
sourcePoint = crossingPoint;
}
temporaryElement = handleLineCreating(board, LineShape.elbow, sourcePoint, movingPoint, sourceElement, lineShapeG);

temporaryElement = handleLineCreating(
board,
LineShape.elbow,
rotatePointsByElement(sourcePoint, sourceElement) || sourcePoint,
movingPoint,
sourceElement,
lineShapeG
);
}
}
pointerMove(event);
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,9 @@ import {
RectangleClient,
SELECTION_BORDER_COLOR,
drawCircle,
hasValidAngle,
rotateAntiPointsByElement,
setAngleForG,
toHostPoint,
toViewBoxPoint
} from '@plait/core';
Expand Down Expand Up @@ -37,9 +40,9 @@ 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);
let nearestPoint = getNearestPoint(hitElement, rotateAntiPointsByElement(movingPoint, hitElement) || movingPoint);
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 (hasValidAngle(hitElement)) {
setAngleForG(boundShapeG, RectangleClient.getCenterPoint(rectangle), hitElement.angle);
}
}
}
pointerMove(event);
Expand Down
8 changes: 6 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, hasValidAngle, rotateAntiPointsByElement, 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,11 @@ 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);
object.connection = getConnectionByNearestPoint(
board,
rotateAntiPointsByElement(resizeState.endPoint, hitElement) || resizeState.endPoint,
hitElement
);
object.boundId = hitElement.id;
} else {
object.connection = undefined;
Expand Down
26 changes: 16 additions & 10 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, rotatePointsByElement } from '@plait/core';
import {
getPoints,
getPointByVectorComponent,
Expand Down Expand Up @@ -40,7 +40,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 +94,21 @@ 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
);
const targetRectangle = RectangleClient.inflate(
RectangleClient.getRectangleByPoints(targetElement.points),
getStrokeWidthByElement(targetElement) * 2
);

let sourceRectangle = RectangleClient.getRectangleByPoints(sourceElement.points);
MissLixf marked this conversation as resolved.
Show resolved Hide resolved
const rotatedSourceCornerPoints =
rotatePointsByElement(RectangleClient.getCornerPoints(sourceRectangle), sourceElement) ||
RectangleClient.getCornerPoints(sourceRectangle);
sourceRectangle = RectangleClient.getRectangleByPoints(rotatedSourceCornerPoints);
sourceRectangle = RectangleClient.inflate(sourceRectangle, getStrokeWidthByElement(sourceElement) * 2);

let targetRectangle = RectangleClient.getRectangleByPoints(targetElement.points);
const rotatedTargetCornerPoints =
rotatePointsByElement(RectangleClient.getCornerPoints(targetRectangle), targetElement) ||
RectangleClient.getCornerPoints(targetRectangle);
targetRectangle = RectangleClient.getRectangleByPoints(rotatedTargetCornerPoints);
targetRectangle = RectangleClient.inflate(targetRectangle, getStrokeWidthByElement(targetElement) * 2);

return {
sourceRectangle,
targetRectangle
Expand Down
24 changes: 19 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,19 @@ import {
createMask,
createRect,
distanceBetweenPointAndPoint,
catmullRomFitting
catmullRomFitting,
rotatePoints,
hasValidAngle,
rotateAntiPointsByElement
} 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 +253,13 @@ 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;
const targetConnection = hitElement
? getConnectionByNearestPoint(board, rotateAntiPointsByElement(movingPoint, hitElement) || movingPoint, hitElement)
: undefined;
const sourceConnection = sourceElement
? getConnectionByNearestPoint(board, rotateAntiPointsByElement(sourcePoint, sourceElement) || sourcePoint, sourceElement)
: undefined;

const targetBoundId = hitElement ? hitElement.id : undefined;
const lineGenerator = new LineShapeGenerator(board);
const memorizedLatest = getLineMemorizedLatest();
Expand All @@ -257,7 +271,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