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): add document and multiDocument shape for flowchart #WIK-1… #846

Merged
merged 1 commit into from
Apr 24, 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/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
Loading