Skip to content

Commit

Permalink
feat(draw): add document and multiDocument shape for flowchart #WIK-1…
Browse files Browse the repository at this point in the history
…5209
  • Loading branch information
MissLixf authored and pubuzhixing8 committed Apr 24, 2024
1 parent 99097c5 commit ac660c6
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-lemons-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@plait/draw': minor
---

add document and multiDocument shape for flowchart
14 changes: 13 additions & 1 deletion packages/draw/src/constants/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ export const DefaultDataProperty = {
height: 60
};

export const DefaultDocumentProperty = {
width: 120,
height: 70
};

export const DefaultMultiDocumentProperty = {
width: 120,
height: 80
};

export const DefaultManualInputProperty = {
width: 117,
height: 59
Expand All @@ -86,7 +96,9 @@ export const DefaultFlowchartPropertyMap = {
[FlowchartSymbols.or]: DefaultConnectorProperty,
[FlowchartSymbols.summingJunction]: DefaultConnectorProperty,
[FlowchartSymbols.predefinedProcess]: DefaultFlowchartProperty,
[FlowchartSymbols.offPage]: DefaultFlowchartProperty
[FlowchartSymbols.offPage]: DefaultFlowchartProperty,
[FlowchartSymbols.document]: DefaultDocumentProperty,
[FlowchartSymbols.multiDocument]: DefaultMultiDocumentProperty
};

export const LINE_HIT_GEOMETRY_BUFFER = 10;
Expand Down
106 changes: 106 additions & 0 deletions packages/draw/src/engines/flowchart/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
PlaitBoard,
Point,
PointOfRectangle,
RectangleClient,
catmullRomFitting,
getNearestPointBetweenPointAndSegments,
setStrokeLinecap
} from '@plait/core';
import { getUnitVectorByPointAndPoint } from '@plait/common';
import { PlaitGeometry, ShapeEngine } from '../../interfaces';
import { Options } from 'roughjs/bin/core';
import { RectangleEngine } from '../basic-shapes/rectangle';
import { getStrokeWidthByElement } from '../../utils';
import { ShapeDefaultSpace } from '../../constants';
import { pointsOnBezierCurves } from 'points-on-curve';

export const DocumentEngine: ShapeEngine = {
draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) {
const rs = PlaitBoard.getRoughSVG(board);
const shape = rs.path(
`M${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} V${rectangle.y} H${rectangle.x +
rectangle.width} V${rectangle.y + rectangle.height - rectangle.height / 9}
Q${rectangle.x + rectangle.width - rectangle.width / 4} ${rectangle.y +
rectangle.height -
(rectangle.height / 9) * 3}, ${rectangle.x + rectangle.width / 2} ${rectangle.y +
rectangle.height -
rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9}
`,
{ ...options, fillStyle: 'solid' }
);
setStrokeLinecap(shape, 'round');
return shape;
},
isInsidePoint(rectangle: RectangleClient, point: Point) {
const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
return RectangleClient.isHit(rectangle, rangeRectangle);
},
getCornerPoints(rectangle: RectangleClient) {
return RectangleClient.getCornerPoints(rectangle);
},
getNearestPoint(rectangle: RectangleClient, point: Point) {
let nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
let curvePoints = catmullRomFitting([
[rectangle.x, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height],
[rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + (rectangle.width / 4) * 3, rectangle.y + rectangle.height - (rectangle.height / 9) * 2],
[rectangle.x + rectangle.width, rectangle.y + rectangle.height - rectangle.height / 9]
]);
curvePoints = pointsOnBezierCurves(curvePoints) as Point[];
if (nearestPoint[1] > rectangle.y + rectangle.height - rectangle.height / 9) {
if (nearestPoint[0] === rectangle.x + rectangle.width / 2) {
nearestPoint[1] = rectangle.y + rectangle.height - rectangle.height / 9;
return nearestPoint;
}
nearestPoint = getNearestPointBetweenPointAndSegments(point, curvePoints, false);
}

return nearestPoint;
},

getConnectorPoints(rectangle: RectangleClient) {
return [
[rectangle.x + rectangle.width / 2, rectangle.y],
[rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
[rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x, rectangle.y + rectangle.height / 2]
];
},

getTangentVectorByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle) {
const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
if (connectionPoint[0] > rectangle.x && connectionPoint[0] < rectangle.x + rectangle.width / 4) {
return getUnitVectorByPointAndPoint([rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height], connectionPoint);
}

if (connectionPoint[0] > rectangle.x + rectangle.width / 4 && connectionPoint[0] < rectangle.x + (rectangle.width / 4) * 3) {
return getUnitVectorByPointAndPoint(
[rectangle.x + (rectangle.width / 4) * 3, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height]
);
}

if (connectionPoint[0] > rectangle.x + (rectangle.width / 4) * 3) {
return getUnitVectorByPointAndPoint(
[rectangle.x + rectangle.width, rectangle.y + rectangle.height - rectangle.height / 9],
connectionPoint
);
}
return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x + rectangle.width / 4, rectangle.y + rectangle.height]);
},

getTextRectangle: (element: PlaitGeometry) => {
const elementRectangle = RectangleClient.getRectangleByPoints(element.points!);
const strokeWidth = getStrokeWidthByElement(element);
const height = element.textHeight;
const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2 - elementRectangle.width * 0.06 * 2;
return {
height,
width: width > 0 ? width : 0,
x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + elementRectangle.width * 0.06,
y: elementRectangle.y + (elementRectangle.height - height) / 2
};
}
};
183 changes: 183 additions & 0 deletions packages/draw/src/engines/flowchart/multi-document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import {
Direction,
PlaitBoard,
Point,
PointOfRectangle,
RectangleClient,
catmullRomFitting,
distanceBetweenPointAndPoint,
getNearestPointBetweenPointAndSegments,
setStrokeLinecap
} from '@plait/core';
import { getDirectionByPointOfRectangle, getDirectionFactor, getUnitVectorByPointAndPoint } from '@plait/common';
import { PlaitGeometry, ShapeEngine } from '../../interfaces';
import { Options } from 'roughjs/bin/core';
import { getStrokeWidthByElement } from '../../utils';
import { ShapeDefaultSpace } from '../../constants';
import { pointsOnBezierCurves } from 'points-on-curve';
import { getCrossingPointBetweenPointAndPolygon } from '../../utils/polygon';

export const getMultiDocumentPoints = (rectangle: RectangleClient): Point[] => {
const linePoints: Point[] = [
[rectangle.x, rectangle.y + 10],
[rectangle.x + 5, rectangle.y + 10],
[rectangle.x + 5, rectangle.y + 5],
[rectangle.x + 10, rectangle.y + 5],
[rectangle.x + 10, rectangle.y],
[rectangle.x + rectangle.width, rectangle.y],
[rectangle.x + rectangle.width, rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3],
[rectangle.x + rectangle.width - 5, rectangle.y + rectangle.height - rectangle.height / 9 - 10 - 3 - 4],
[rectangle.x + rectangle.width - 5, rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3],
[rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3 - 4],
[rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9]
];

let curvePoints = catmullRomFitting([
[rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + rectangle.width - 10 - (rectangle.width - 10) / 4, rectangle.y + rectangle.height - (rectangle.height / 9) * 2],
[rectangle.x + (rectangle.width - 10) / 2, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height],
[rectangle.x, rectangle.y + rectangle.height - rectangle.height / 9]
]);

curvePoints = pointsOnBezierCurves(curvePoints) as Point[];
return [...linePoints, ...curvePoints];
};

export const MultiDocumentEngine: ShapeEngine = {
draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) {
const rs = PlaitBoard.getRoughSVG(board);
const shape = rs.path(
`M${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9} V${rectangle.y + 10} H${rectangle.x +
5} V${rectangle.y + 5} H${rectangle.x + 10} V${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y +
rectangle.height -
rectangle.height / 9 -
10 -
3} L${rectangle.x + rectangle.width - 5} ${rectangle.y +
rectangle.height -
rectangle.height / 9 -
10 -
3 -
4} V${rectangle.y + rectangle.height - rectangle.height / 9 - 5 - 3}
L${rectangle.x + rectangle.width - 10} ${rectangle.y +
rectangle.height -
rectangle.height / 9 -
5 -
3 -
4} V${rectangle.y + rectangle.height - rectangle.height / 9}
Q${rectangle.x + rectangle.width - 10 - (rectangle.width - 10) / 4} ${rectangle.y +
rectangle.height -
(rectangle.height / 9) * 3}, ${rectangle.x + (rectangle.width - 10) / 2} ${rectangle.y +
rectangle.height -
rectangle.height / 9} T${rectangle.x} ${rectangle.y + rectangle.height - rectangle.height / 9}
M${rectangle.x + 5} ${rectangle.y + 10} H${rectangle.x + rectangle.width - 10} V${rectangle.y +
rectangle.height -
rectangle.height / 9}
M${rectangle.x + 10} ${rectangle.y + 5} H${rectangle.x + rectangle.width - 5} V${rectangle.y +
rectangle.height -
rectangle.height / 9 -
10 -
3}
`,
{ ...options, fillStyle: 'solid' }
);
setStrokeLinecap(shape, 'round');
return shape;
},
isInsidePoint(rectangle: RectangleClient, point: Point) {
const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
return RectangleClient.isHit(rectangle, rangeRectangle);
},
getCornerPoints(rectangle: RectangleClient) {
return RectangleClient.getCornerPoints(rectangle);
},
getNearestPoint(rectangle: RectangleClient, point: Point) {
return getNearestPointBetweenPointAndSegments(point, getMultiDocumentPoints(rectangle), false);
},

getConnectorPoints(rectangle: RectangleClient) {
let curvePoints = catmullRomFitting([
[rectangle.x, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height],
[rectangle.x + (rectangle.width - 10) / 2, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + ((rectangle.width - 10) / 4) * 3, rectangle.y + rectangle.height - (rectangle.height / 9) * 2],
[rectangle.x + rectangle.width - 10, rectangle.y + rectangle.height - rectangle.height / 9]
]);
curvePoints = pointsOnBezierCurves(curvePoints) as Point[];
const crossingPoint = getNearestPointBetweenPointAndSegments(
[rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
curvePoints
);
return [
[rectangle.x + rectangle.width / 2, rectangle.y],
[rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
[crossingPoint[0], crossingPoint[1]],
[rectangle.x, rectangle.y + rectangle.height / 2]
];
},

getNearestCrossingPoint(rectangle: RectangleClient, point: Point) {
const crossingPoints = getCrossingPointBetweenPointAndPolygon(getMultiDocumentPoints(rectangle), point);
let nearestPoint = crossingPoints[0];
let nearestDistance = distanceBetweenPointAndPoint(point[0], point[1], nearestPoint[0], nearestPoint[1]);
crossingPoints
.filter((v, index) => index > 0)
.forEach(crossingPoint => {
let distance = distanceBetweenPointAndPoint(point[0], point[1], crossingPoint[0], crossingPoint[1]);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestPoint = crossingPoint;
}
});

return nearestPoint;
},

getTangentVectorByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle) {
const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
if (connectionPoint[0] > rectangle.x && connectionPoint[0] < rectangle.x + (rectangle.width - 10) / 4) {
return getUnitVectorByPointAndPoint(
[rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height],
connectionPoint
);
}
if (
connectionPoint[0] > rectangle.x + (rectangle.width - 10) / 4 &&
connectionPoint[0] < rectangle.x + ((rectangle.width - 10) / 4) * 3
) {
return getUnitVectorByPointAndPoint(
[rectangle.x + ((rectangle.width - 10) / 4) * 3, rectangle.y + rectangle.height - rectangle.height / 9],
[rectangle.x + (rectangle.width - 10) / 4, rectangle.y + rectangle.height]
);
}

if (
connectionPoint[0] > rectangle.x + ((rectangle.width - 10) / 4) * 3 &&
connectionPoint[0] < rectangle.x + rectangle.width - 10
) {
return getUnitVectorByPointAndPoint(
[rectangle.x + (rectangle.width - 10), rectangle.y + rectangle.height - rectangle.height / 9],
connectionPoint
);
}
const direction = getDirectionByPointOfRectangle(pointOfRectangle) || Direction.bottom;
const factor = getDirectionFactor(direction!);
return [factor.x, factor.y];
},

getTextRectangle: (element: PlaitGeometry) => {
const elementRectangle = RectangleClient.getRectangleByPoints(element.points!);
const strokeWidth = getStrokeWidthByElement(element);
const height = element.textHeight;
const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2 - elementRectangle.width * 0.06 * 2;
return {
height,
width: width > 0 ? width - 10 : 0,
x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + elementRectangle.width * 0.06,
y: elementRectangle.y + (elementRectangle.height - height) / 2
};
}
};
6 changes: 5 additions & 1 deletion packages/draw/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import { OffPageEngine } from './flowchart/off-page';
import { CloudEngine } from './basic-shapes/cloud';
import { OrEngine } from './flowchart/or';
import { SummingJunctionEngine } from './flowchart/summing-junction';
import { DocumentEngine } from './flowchart/document';
import { MultiDocumentEngine } from './flowchart/multi-document';

export const ShapeEngineMap: Record<GeometryShapes, ShapeEngine> = {
[BasicShapes.rectangle]: RectangleEngine,
Expand Down Expand Up @@ -67,7 +69,9 @@ export const ShapeEngineMap: Record<GeometryShapes, ShapeEngine> = {
[FlowchartSymbols.or]: OrEngine,
[FlowchartSymbols.summingJunction]: SummingJunctionEngine,
[FlowchartSymbols.predefinedProcess]: PredefinedProcessEngine,
[FlowchartSymbols.offPage]: OffPageEngine
[FlowchartSymbols.offPage]: OffPageEngine,
[FlowchartSymbols.document]: DocumentEngine,
[FlowchartSymbols.multiDocument]: MultiDocumentEngine
};

export const getEngine = (shape: GeometryShapes) => {
Expand Down
4 changes: 3 additions & 1 deletion packages/draw/src/interfaces/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export enum FlowchartSymbols {
or = 'or',
summingJunction = 'summingJunction',
predefinedProcess = 'predefinedProcess',
offPage = 'offPage'
offPage = 'offPage',
document = 'document',
multiDocument = 'multiDocument'
}

export type GeometryShapes = BasicShapes | FlowchartSymbols;
Expand Down
Loading

0 comments on commit ac660c6

Please sign in to comment.