Skip to content

Commit

Permalink
feat(draw): add assembly requiredInterface and providedInterface shap… (
Browse files Browse the repository at this point in the history
  • Loading branch information
MissLixf authored Jun 11, 2024
1 parent 88ed28e commit 70b1554
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-mirrors-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@plait/draw': minor
---

add assembly requiredInterface and providedInterface shape for uml
26 changes: 24 additions & 2 deletions packages/draw/src/constants/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,22 @@ export const DefaultPortProperty = {
width: 20,
height: 20
};

export const DefaultRequiredInterfaceProperty = {
width: 70,
height: 56
};

export const DefaultAssemblyProperty = {
width: 120,
height: 56
};

export const DefaultProvidedInterfaceProperty = {
width: 70,
height: 34
};

export const DefaultCombinedFragmentProperty = {
width: 400,
height: 280,
Expand Down Expand Up @@ -260,7 +276,10 @@ export const DefaultUMLPropertyMap = {
[UMLSymbols.template]: DefaultMultiDocumentProperty,
[UMLSymbols.componentBox]: DefaultComponentBoxProperty,
[UMLSymbols.port]: DefaultPortProperty,
[UMLSymbols.branchMerge]: DefaultDeletionProperty
[UMLSymbols.branchMerge]: DefaultDeletionProperty,
[UMLSymbols.assembly]: DefaultAssemblyProperty,
[UMLSymbols.providedInterface]: DefaultProvidedInterfaceProperty,
[UMLSymbols.requiredInterface]: DefaultRequiredInterfaceProperty
};

export const MultipleTextGeometryTextKeys: { [key in GeometryShapes]?: string[] } = {
Expand All @@ -280,7 +299,10 @@ export const GEOMETRY_WITHOUT_TEXT = [
UMLSymbols.activation,
UMLSymbols.deletion,
UMLSymbols.port,
UMLSymbols.branchMerge
UMLSymbols.branchMerge,
UMLSymbols.assembly,
UMLSymbols.providedInterface,
UMLSymbols.requiredInterface
] as GeometryShapes[];

export const GEOMETRY_WITH_MULTIPLE_TEXT = [UMLSymbols.package, UMLSymbols.combinedFragment];
8 changes: 7 additions & 1 deletion packages/draw/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ import { CombinedFragmentEngine } from './uml/combined-fragment';
import { DeletionEngine } from './uml/deletion';
import { ActiveClassEngine } from './uml/activity-class';
import { NoteEngine } from './uml/note';
import { AssemblyEngine } from './uml/assembly';
import { RequiredInterfaceEngine } from './uml/required-interface';
import { ProvidedInterfaceEngine } from './uml/provided-interface';
import { ComponentEngine } from './uml/component';
import { ComponentBoxEngine } from './uml/component-box';
import { TemplateEngine } from './uml/template';
Expand Down Expand Up @@ -128,7 +131,10 @@ const ShapeEngineMap: Record<DrawShapes, ShapeEngine<any, any, any>> = {
[UMLSymbols.componentBox]: ComponentBoxEngine,
[UMLSymbols.template]: TemplateEngine,
[UMLSymbols.port]: RectangleEngine,
[UMLSymbols.branchMerge]: DiamondEngine
[UMLSymbols.branchMerge]: DiamondEngine,
[UMLSymbols.assembly]: AssemblyEngine,
[UMLSymbols.requiredInterface]: RequiredInterfaceEngine,
[UMLSymbols.providedInterface]: ProvidedInterfaceEngine
};

export const getEngine = <
Expand Down
75 changes: 75 additions & 0 deletions packages/draw/src/engines/uml/assembly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
PlaitBoard,
Point,
PointOfRectangle,
RectangleClient,
getNearestPointBetweenPointAndEllipse,
getNearestPointBetweenPointAndSegments,
setStrokeLinecap
} from '@plait/core';
import { ShapeEngine } from '../../interfaces';
import { Options } from 'roughjs/bin/core';
import { RectangleEngine } from '../basic-shapes/rectangle';
import { getUnitVectorByPointAndPoint, rotateVector } from '@plait/common';

export const AssemblyEngine: ShapeEngine = {
draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) {
const rs = PlaitBoard.getRoughSVG(board);
const shape = rs.path(
`
M${rectangle.x} ${rectangle.y + rectangle.height / 2}
H${rectangle.x + rectangle.width * 0.3}
A${rectangle.width * 0.13} ${rectangle.height * 0.285}, 0, 1, 1 ${rectangle.x +
rectangle.width * 0.3 +
rectangle.width * 0.26} ${rectangle.y + rectangle.height / 2}
A${rectangle.width * 0.13} ${rectangle.height * 0.285}, 0, 1, 1 ${rectangle.x + rectangle.width * 0.3} ${rectangle.y +
rectangle.height / 2}
M${rectangle.x + rectangle.width * 0.3 + rectangle.width * 0.13} ${rectangle.y}
A${rectangle.width * 0.233} ${rectangle.height / 2}, 0, 0, 1 ${rectangle.x +
rectangle.width * 0.3 +
rectangle.width * 0.13} ${rectangle.y + rectangle.height}
M${rectangle.x + rectangle.width * 0.3 + rectangle.width * 0.13 + rectangle.width * 0.233} ${rectangle.y +
rectangle.height / 2} H${rectangle.x + rectangle.width}
`,
{
...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);
},
getConnectorPoints(rectangle: RectangleClient) {
return RectangleClient.getEdgeCenterPoints(rectangle);
},
getNearestPoint(rectangle: RectangleClient, point: Point) {
const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
if (nearestPoint[0] === rectangle.x + rectangle.width / 2) {
return getNearestPointBetweenPointAndEllipse(
point,
[rectangle.x + rectangle.width * 0.43, rectangle.y + rectangle.height / 2],
rectangle.width * 0.223,
rectangle.height / 2
);
}
return nearestPoint;
},
getTangentVectorByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle) {
const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
if (connectionPoint[0] > rectangle.x + rectangle.width * 0.43 && connectionPoint[1] < rectangle.y + rectangle.height / 2) {
return rotateVector(getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]), -Math.PI);
}
if (connectionPoint[0] > rectangle.x + rectangle.width * 0.43 && connectionPoint[1] > rectangle.y + rectangle.height / 2) {
return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]);
}
return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]);
}
};
62 changes: 62 additions & 0 deletions packages/draw/src/engines/uml/provided-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
PlaitBoard,
Point,
PointOfRectangle,
RectangleClient,
getEllipseTangentSlope,
getNearestPointBetweenPointAndSegments,
getVectorFromPointAndSlope,
setStrokeLinecap
} from '@plait/core';
import { ShapeEngine } from '../../interfaces';
import { Options } from 'roughjs/bin/core';
import { RectangleEngine } from '../basic-shapes/rectangle';
import { getUnitVectorByPointAndPoint } from '@plait/common';

export const ProvidedInterfaceEngine: ShapeEngine = {
draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) {
const rs = PlaitBoard.getRoughSVG(board);
const shape = rs.path(
` M${rectangle.x} ${rectangle.y + rectangle.height / 2}
H${rectangle.x + rectangle.width * 0.54}
A${(rectangle.width * 0.46) / 2} ${rectangle.height / 2}, 0, 1, 1 ${rectangle.x + rectangle.width} ${rectangle.y +
rectangle.height / 2}
A${(rectangle.width * 0.46) / 2} ${rectangle.height / 2}, 0, 1, 1 ${rectangle.x + rectangle.width * 0.54} ${rectangle.y +
rectangle.height / 2}
`,
{
...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);
},
getConnectorPoints(rectangle: RectangleClient) {
return RectangleClient.getEdgeCenterPoints(rectangle);
},
getNearestPoint(rectangle: RectangleClient, point: Point) {
const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
return nearestPoint;
},
getTangentVectorByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle) {
const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
const centerPoint: Point = [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2];
if (connectionPoint[0] > rectangle.x + rectangle.width * 0.54) {
const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])];
const rx = (rectangle.width * 0.46) / 2;
const ry = rectangle.height / 2;
const slope = getEllipseTangentSlope(point[0], point[1], rx, ry) as any;
return getVectorFromPointAndSlope(point[0], point[1], slope);
}
return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]);
}
};
49 changes: 49 additions & 0 deletions packages/draw/src/engines/uml/required-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
PlaitBoard,
Point,
PointOfRectangle,
RectangleClient,
getNearestPointBetweenPointAndSegments,
setStrokeLinecap
} from '@plait/core';
import { ShapeEngine } from '../../interfaces';
import { Options } from 'roughjs/bin/core';
import { RectangleEngine } from '../basic-shapes/rectangle';
import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon';

export const RequiredInterfaceEngine: ShapeEngine = {
draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) {
const rs = PlaitBoard.getRoughSVG(board);
const shape = rs.path(
`M${rectangle.x} ${rectangle.y}
A${rectangle.width * 0.39} ${rectangle.height / 2}, 0, 0, 1 ${rectangle.x} ${rectangle.y + rectangle.height}
M${rectangle.x + rectangle.width * 0.41} ${rectangle.y + rectangle.height / 2} H${rectangle.x + rectangle.width}
`,
{
...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, RectangleEngine.getCornerPoints(rectangle));
},
getEdgeByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle): [Point, Point] | null {
const corners = RectangleEngine.getCornerPoints(rectangle);
const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
return getPolygonEdgeByConnectionPoint(corners, point);
},
getConnectorPoints(rectangle: RectangleClient) {
return RectangleClient.getEdgeCenterPoints(rectangle);
}
};
5 changes: 4 additions & 1 deletion packages/draw/src/interfaces/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ export enum UMLSymbols {
componentBox = 'componentBox',
template = 'template',
activation = 'activation',
deletion = 'deletion'
deletion = 'deletion',
assembly = 'assembly',
providedInterface = 'providedInterface',
requiredInterface = 'requiredInterface'
}

export enum MultipleTextGeometryCommonTextKeys {
Expand Down
79 changes: 79 additions & 0 deletions src/app/components/main-toolbar/main-toolbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,26 @@
<ng-template [ngTemplateOutlet]="activation"></ng-template>
</a>

<a class="action-item" [ngClass]="{ active: isPointer(UMLSymbols.assembly) }" (mousedown)="setPointer($event, UMLSymbols.assembly)">
<ng-template [ngTemplateOutlet]="assembly"></ng-template>
</a>

<a
class="action-item"
[ngClass]="{ active: isPointer(UMLSymbols.requiredInterface) }"
(mousedown)="setPointer($event, UMLSymbols.requiredInterface)"
>
<ng-template [ngTemplateOutlet]="requiredInterface"></ng-template>
</a>

<a
class="action-item"
[ngClass]="{ active: isPointer(UMLSymbols.providedInterface) }"
(mousedown)="setPointer($event, UMLSymbols.providedInterface)"
>
<ng-template [ngTemplateOutlet]="providedInterface"></ng-template>
</a>

<a class="action-item" [ngClass]="{ active: isPointer(UMLSymbols.deletion) }" (mousedown)="setPointer($event, UMLSymbols.deletion)">
<ng-template [ngTemplateOutlet]="deletion"></ng-template>
</a>
Expand Down Expand Up @@ -2024,3 +2044,62 @@
</g>
</svg>
</ng-template>

<ng-template #assembly>
<svg
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>1.Base基础/1.icon图标/11.editor/图形/UML/assembly</title>
<g id="1.Base基础/1.icon图标/11.editor/图形/UML/assembly" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path
d="M7,1.4 C10.4766452,1.4 13.3254406,4.08814437 13.5812951,7.49934192 L15.5,7.5 C15.7761424,7.5 16,7.72385763 16,8 L16,8.2 C16,8.47614237 15.7761424,8.7 15.5,8.7 L13.5633195,8.69998864 C13.2137521,12.0159905 10.4086399,14.6 7,14.6 L7,13.4 C9.98233765,13.4 12.4,10.9823376 12.4,8 C12.4,5.01766235 9.98233765,2.6 7,2.6 L7,1.4 Z M7,3.9 C9.26436747,3.9 11.1,5.73563253 11.1,8 C11.1,10.2643675 9.26436747,12.1 7,12.1 C4.97422531,12.1 3.29159537,10.6308227 2.95949447,8.69985241 L0.5,8.7 C0.223857625,8.7 3.38176876e-17,8.47614237 0,8.2 L0,8 C-3.38176876e-17,7.72385763 0.223857625,7.5 0.5,7.5 L2.93031866,7.49887152 C3.17745787,5.47088458 4.90527906,3.9 7,3.9 Z M7,5.1 C5.39837423,5.1 4.1,6.39837423 4.1,8 C4.1,9.60162577 5.39837423,10.9 7,10.9 C8.60162577,10.9 9.9,9.60162577 9.9,8 C9.9,6.39837423 8.60162577,5.1 7,5.1 Z"
id="形状结合"
fill="#999999"
></path>
</g>
</svg>
</ng-template>
<ng-template #requiredInterface>
<svg
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>1.Base基础/1.icon图标/11.editor/图形/UML/required interface</title>
<g id="1.Base基础/1.icon图标/11.editor/图形/UML/required-interface" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path
d="M4,1.4 C7.47664518,1.4 10.3254406,4.08814437 10.5812951,7.49934192 L12.5,7.5 C12.7761424,7.5 13,7.72385763 13,8 L13,8.2 C13,8.47614237 12.7761424,8.7 12.5,8.7 L10.5633195,8.69998864 C10.2137521,12.0159905 7.40863991,14.6 4,14.6 L4,13.4 C6.98233765,13.4 9.4,10.9823376 9.4,8 C9.4,5.01766235 6.98233765,2.6 4,2.6 L4,1.4 Z"
id="形状结合"
fill="#999999"
></path>
</g>
</svg>
</ng-template>

<ng-template #providedInterface>
<svg
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>1.Base基础/1.icon图标/11.editor/图形/UML/provided interface</title>
<g id="1.Base基础/1.icon图标/11.editor/图形/UML/provided-interface" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path
d="M11,3.9 C13.2643675,3.9 15.1,5.73563253 15.1,8 C15.1,10.2643675 13.2643675,12.1 11,12.1 C8.97422531,12.1 7.29159537,10.6308227 6.95949447,8.69985241 L1.5,8.7 C1.22385763,8.7 1,8.47614237 1,8.2 L1,8 C1,7.72385763 1.22385763,7.5 1.5,7.5 L6.93031866,7.49887152 C7.17745787,5.47088458 8.90527906,3.9 11,3.9 Z M11,5.1 C9.39837423,5.1 8.1,6.39837423 8.1,8 C8.1,9.60162577 9.39837423,10.9 11,10.9 C12.6016258,10.9 13.9,9.60162577 13.9,8 C13.9,6.39837423 12.6016258,5.1 11,5.1 Z"
id="形状结合"
fill="#999999"
></path>
</g>
</svg>
</ng-template>

0 comments on commit 70b1554

Please sign in to comment.