mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 22:39:26 +02:00
Compare commits
334 Commits
mermaid@11
...
mermaid@11
Author | SHA1 | Date | |
---|---|---|---|
![]() |
be57484573 | ||
![]() |
0cb88c21dc | ||
![]() |
0bc1f5296f | ||
![]() |
4776f97f82 | ||
![]() |
460db0aea7 | ||
![]() |
02cf111995 | ||
![]() |
14d9d92112 | ||
![]() |
fce77a19b0 | ||
![]() |
ef8c0025fd | ||
![]() |
16847ea06c | ||
![]() |
be0bbe9950 | ||
![]() |
b503c2c758 | ||
![]() |
1d14f4658d | ||
![]() |
c7382af161 | ||
![]() |
f6adca902f | ||
![]() |
d50a6cf75c | ||
![]() |
2cebf09cf2 | ||
![]() |
46419f7bfe | ||
![]() |
43e8f831c0 | ||
![]() |
760ea1e020 | ||
![]() |
07d7704ef4 | ||
![]() |
a4878d05f0 | ||
![]() |
c5bcd2f22c | ||
![]() |
df1fba7fc5 | ||
![]() |
bb521f7af8 | ||
![]() |
f9818b8bd7 | ||
![]() |
dfcb4af18b | ||
![]() |
5c67e42a55 | ||
![]() |
7441be09b5 | ||
![]() |
a75665c7bf | ||
![]() |
4cb0f87b72 | ||
![]() |
1e8cd63645 | ||
![]() |
3860a99951 | ||
![]() |
226b3bfa74 | ||
![]() |
a5559c6588 | ||
![]() |
de2c05cd54 | ||
![]() |
6d916fb66f | ||
![]() |
90b6df24a1 | ||
![]() |
3e807e0131 | ||
![]() |
e2e2caa0e9 | ||
![]() |
b3dee343d1 | ||
![]() |
608f7afa70 | ||
![]() |
58ef0ee174 | ||
![]() |
2a76dc6409 | ||
![]() |
dc7e32eb10 | ||
![]() |
6a649d347d | ||
![]() |
fd372941c7 | ||
![]() |
1c8c95367d | ||
![]() |
b3dfc60fff | ||
![]() |
bb3b95f0bd | ||
![]() |
4fc9028dc1 | ||
![]() |
02cadd87ff | ||
![]() |
1c105154a6 | ||
![]() |
daaeded0b5 | ||
![]() |
ed7d41a3cb | ||
![]() |
f052a3f686 | ||
![]() |
11da07b735 | ||
![]() |
d01e180caf | ||
![]() |
4c400966b4 | ||
![]() |
f849c36942 | ||
![]() |
c83fc753a1 | ||
![]() |
eeeb5fcdd9 | ||
![]() |
cb85d50f61 | ||
![]() |
a52949318a | ||
![]() |
dd261a54df | ||
![]() |
f035db2feb | ||
![]() |
c6410472c8 | ||
![]() |
d807466780 | ||
![]() |
c8b9c4cebc | ||
![]() |
741072a126 | ||
![]() |
be9123ee2b | ||
![]() |
3f5afe8daf | ||
![]() |
522de3abd8 | ||
![]() |
d37d77cc1a | ||
![]() |
4740be6562 | ||
![]() |
a07f1f3337 | ||
![]() |
02851fd672 | ||
![]() |
233e36c988 | ||
![]() |
2b12a658f6 | ||
![]() |
b74842ceb3 | ||
![]() |
ca84f7d4a9 | ||
![]() |
1892277273 | ||
![]() |
f12df395f8 | ||
![]() |
89e82e4d97 | ||
![]() |
af2340d889 | ||
![]() |
775a6381b4 | ||
![]() |
38c6ad5ad6 | ||
![]() |
d63dc319c7 | ||
![]() |
c5539e0aa0 | ||
![]() |
14601b7871 | ||
![]() |
7a86fd7b44 | ||
![]() |
0b3522894a | ||
![]() |
aa0836aecc | ||
![]() |
78f9238865 | ||
![]() |
5ca4fccbb0 | ||
![]() |
7f2044024b | ||
![]() |
d2de9702c5 | ||
![]() |
92a8783d95 | ||
![]() |
4bd1e5c524 | ||
![]() |
10826055f3 | ||
![]() |
6cf050e6e5 | ||
![]() |
52e1a36ffe | ||
![]() |
e83a95d3ed | ||
![]() |
44561f57d3 | ||
![]() |
4c26fe224b | ||
![]() |
61af74609a | ||
![]() |
9f2f3bd780 | ||
![]() |
38bcbcdd0a | ||
![]() |
d8c340d9c5 | ||
![]() |
8456d7b100 | ||
![]() |
2fa044d484 | ||
![]() |
4162a25425 | ||
![]() |
e26a38b619 | ||
![]() |
92aa2859a0 | ||
![]() |
1d8f524913 | ||
![]() |
59991b70a1 | ||
![]() |
a6b877676e | ||
![]() |
3c49c00f2b | ||
![]() |
c97716e780 | ||
![]() |
355e218837 | ||
![]() |
a3145a32e7 | ||
![]() |
8b6fb94835 | ||
![]() |
83ea81d975 | ||
![]() |
aae9db92ee | ||
![]() |
6c5b7ce9f4 | ||
![]() |
8bb70ea493 | ||
![]() |
bff9f65c88 | ||
![]() |
88607e8aa0 | ||
![]() |
17c6ed6303 | ||
![]() |
bfd8c63daa | ||
![]() |
6de7f1b088 | ||
![]() |
3359d87115 | ||
![]() |
e8fdc965a2 | ||
![]() |
67bcd3e0d6 | ||
![]() |
9421f63775 | ||
![]() |
01bf7af360 | ||
![]() |
4754ed01ab | ||
![]() |
a2c262f940 | ||
![]() |
7963ad9b20 | ||
![]() |
5e34810335 | ||
![]() |
e44eb5258b | ||
![]() |
e564c395ba | ||
![]() |
3d1af09090 | ||
![]() |
1cc2f323de | ||
![]() |
194684e27b | ||
![]() |
ee17e020cf | ||
![]() |
2b8ef765e7 | ||
![]() |
dbe965303b | ||
![]() |
3d9e919545 | ||
![]() |
30eb02510b | ||
![]() |
06aa72ec16 | ||
![]() |
cf45a35971 | ||
![]() |
a601d3bccf | ||
![]() |
f46a135458 | ||
![]() |
82cc473801 | ||
![]() |
9c41708d9e | ||
![]() |
a51d3aa2ad | ||
![]() |
797d72b716 | ||
![]() |
dd0304387e | ||
![]() |
e111965411 | ||
![]() |
41cf1262b2 | ||
![]() |
e5532c3418 | ||
![]() |
a881d46178 | ||
![]() |
edb99c2842 | ||
![]() |
4d04d1a8c3 | ||
![]() |
1d1766b5c3 | ||
![]() |
0ade03c4de | ||
![]() |
2aa5089930 | ||
![]() |
2259a59ac6 | ||
![]() |
d8d56df272 | ||
![]() |
ec29f749af | ||
![]() |
9467735fab | ||
![]() |
9e3aa705ae | ||
![]() |
0f8cc48d03 | ||
![]() |
95bd5dfdab | ||
![]() |
e6ce370cdd | ||
![]() |
78346cbe84 | ||
![]() |
9a953f7f80 | ||
![]() |
14bb09a3db | ||
![]() |
89fb65913d | ||
![]() |
41a75005c8 | ||
![]() |
54ecd7d423 | ||
![]() |
352194c942 | ||
![]() |
30e4ab99d9 | ||
![]() |
5f0160c036 | ||
![]() |
443c886050 | ||
![]() |
b7fd21e17a | ||
![]() |
2f24f6271a | ||
![]() |
6507624232 | ||
![]() |
e337331122 | ||
![]() |
1d5f7ce08f | ||
![]() |
adedb5f803 | ||
![]() |
9774c08336 | ||
![]() |
525dcd4760 | ||
![]() |
d75661e49d | ||
![]() |
52d6035c23 | ||
![]() |
5a9221680f | ||
![]() |
3a5fef3e50 | ||
![]() |
8883008ca1 | ||
![]() |
0eec89255f | ||
![]() |
3d4c5d1a41 | ||
![]() |
105b11fca2 | ||
![]() |
a259aeba27 | ||
![]() |
8462f21c01 | ||
![]() |
5d3a175ed6 | ||
![]() |
69bec16080 | ||
![]() |
f7e4ee61c7 | ||
![]() |
8e0bb994bb | ||
![]() |
336c3127ed | ||
![]() |
6209165b32 | ||
![]() |
f3bb5c0c26 | ||
![]() |
9cb09f1fb4 | ||
![]() |
cfeb794b34 | ||
![]() |
f4d4c784e4 | ||
![]() |
eb341bdfb6 | ||
![]() |
ee84ed4236 | ||
![]() |
f27feb268a | ||
![]() |
56e9095619 | ||
![]() |
b3495219ae | ||
![]() |
71753671e0 | ||
![]() |
9746497673 | ||
![]() |
882a2cc63e | ||
![]() |
f29f5eb207 | ||
![]() |
600247df79 | ||
![]() |
8fa1fed8b5 | ||
![]() |
63e9194c5d | ||
![]() |
06310faa32 | ||
![]() |
c9c821da5c | ||
![]() |
c2c65b4df2 | ||
![]() |
9b7f5ed963 | ||
![]() |
75f755b823 | ||
![]() |
1cfe987fa1 | ||
![]() |
4cf7c97b8f | ||
![]() |
3715254a78 | ||
![]() |
ab44053ba4 | ||
![]() |
5c6f3e66a6 | ||
![]() |
5e5265c8b0 | ||
![]() |
13719aa694 | ||
![]() |
9d9109a68c | ||
![]() |
cb91c3f8a8 | ||
![]() |
c08fad6487 | ||
![]() |
23329f1ee9 | ||
![]() |
e842b72aaa | ||
![]() |
7c26148252 | ||
![]() |
7d6a97e264 | ||
![]() |
99f7d65f47 | ||
![]() |
db1db1ab0e | ||
![]() |
cec28464ec | ||
![]() |
a2fba0284e | ||
![]() |
f424a51133 | ||
![]() |
46b8faddae | ||
![]() |
19cce07ab5 | ||
![]() |
439dcb10b2 | ||
![]() |
98cb8b998e | ||
![]() |
1654a4b01c | ||
![]() |
8e72611922 | ||
![]() |
a857d3807f | ||
![]() |
3dc0af3159 | ||
![]() |
abf7ecd2ca | ||
![]() |
8d8b6c1c33 | ||
![]() |
262790b22d | ||
![]() |
4e8a7e0b4c | ||
![]() |
121e27e850 | ||
![]() |
0152ed7027 | ||
![]() |
f233bfd75e | ||
![]() |
4298a57297 | ||
![]() |
6e8ea148bc | ||
![]() |
777522e8d5 | ||
![]() |
a4007ffb80 | ||
![]() |
de875c9e27 | ||
![]() |
3cfcb2da41 | ||
![]() |
4c3db653f5 | ||
![]() |
90b7a3be63 | ||
![]() |
0fbe5f2f10 | ||
![]() |
36f2989249 | ||
![]() |
99956b4283 | ||
![]() |
9c5a8f7ef1 | ||
![]() |
e2f3041aa2 | ||
![]() |
9926ae4e74 | ||
![]() |
58463b5e80 | ||
![]() |
7b51327b24 | ||
![]() |
06dd350aab | ||
![]() |
4f2085765d | ||
![]() |
eceae9b64c | ||
![]() |
98754b9fc6 | ||
![]() |
f813870e7d | ||
![]() |
763a0c0d83 | ||
![]() |
d30444dae7 | ||
![]() |
c983c0605e | ||
![]() |
2fa03ea369 | ||
![]() |
6c1ae90972 | ||
![]() |
a17e7d92d9 | ||
![]() |
b3a4e3f40e | ||
![]() |
b7acf94b41 | ||
![]() |
9e5c3ec1b5 | ||
![]() |
5550753193 | ||
![]() |
394c6aea74 | ||
![]() |
3febd7dfdf | ||
![]() |
ef895a3d8c | ||
![]() |
8f5f7fc2a8 | ||
![]() |
c673cf22bd | ||
![]() |
4d401c127d | ||
![]() |
3c119e4a52 | ||
![]() |
303a160359 | ||
![]() |
3cd5244949 | ||
![]() |
31b8707819 | ||
![]() |
a0893b8e6c | ||
![]() |
e1c8eb8a72 | ||
![]() |
bc546ef562 | ||
![]() |
251b94647b | ||
![]() |
4aeb2ae9a2 | ||
![]() |
0f635ed344 | ||
![]() |
efe6affe64 | ||
![]() |
f140d1ab45 | ||
![]() |
89e4de1dd0 | ||
![]() |
ac48f01e25 | ||
![]() |
4c48b8cc54 | ||
![]() |
b807e330c8 | ||
![]() |
04b15e7ece | ||
![]() |
575c39e8a5 | ||
![]() |
933f3f391c | ||
![]() |
cfb5b88ee6 | ||
![]() |
9cd44eb92c | ||
![]() |
d0942d4897 | ||
![]() |
3d789a0675 | ||
![]() |
1d3f214109 | ||
![]() |
e51077c765 | ||
![]() |
809fc1ce30 | ||
![]() |
f176b71c7c | ||
![]() |
4d8e519298 | ||
![]() |
958204e831 | ||
![]() |
82c247bb11 | ||
![]() |
2a56b640c0 | ||
![]() |
eae67a9696 |
@@ -26,6 +26,7 @@ concat
|
||||
controlx
|
||||
controly
|
||||
CSSCLASS
|
||||
curv
|
||||
CYLINDEREND
|
||||
CYLINDERSTART
|
||||
DAGA
|
||||
|
@@ -28,6 +28,9 @@ dictionaryDefinitions:
|
||||
- name: suggestions
|
||||
words:
|
||||
- none
|
||||
- disp
|
||||
- subproc
|
||||
- tria
|
||||
suggestWords:
|
||||
- seperator:separator
|
||||
- vertice:vertex
|
||||
|
@@ -5,6 +5,7 @@ bmatrix
|
||||
braintree
|
||||
catmull
|
||||
compositTitleSize
|
||||
curv
|
||||
doublecircle
|
||||
elems
|
||||
gantt
|
||||
@@ -24,6 +25,7 @@ multigraph
|
||||
nodesep
|
||||
NOTEGROUP
|
||||
Pinterest
|
||||
procs
|
||||
rankdir
|
||||
ranksep
|
||||
rect
|
||||
|
5
.github/lychee.toml
vendored
5
.github/lychee.toml
vendored
@@ -44,7 +44,10 @@ exclude = [
|
||||
"https://chromewebstore.google.com",
|
||||
|
||||
# Drupal 403
|
||||
"https://(www.)?drupal.org"
|
||||
"https://(www.)?drupal.org",
|
||||
|
||||
# Swimm returns 404, eventhough the link is valid
|
||||
"https://docs.swimm.io"
|
||||
]
|
||||
|
||||
# Exclude all private IPs from checking.
|
||||
|
1
.github/workflows/release-preview.yml
vendored
1
.github/workflows/release-preview.yml
vendored
@@ -3,6 +3,7 @@ name: Preview release
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
types: [opened, synchronize, labeled, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.number }}
|
||||
|
@@ -581,4 +581,63 @@ class C13["With Città foreign language"]
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a class diagram with a generic class in a namespace', () => {
|
||||
const diagramDefinition = `
|
||||
classDiagram-v2
|
||||
namespace Company.Project.Module {
|
||||
class GenericClass~T~ {
|
||||
+addItem(item: T)
|
||||
+getItem() T
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
imgSnapshotTest(diagramDefinition);
|
||||
});
|
||||
|
||||
it('renders a class diagram with nested namespaces and relationships', () => {
|
||||
const diagramDefinition = `
|
||||
classDiagram-v2
|
||||
namespace Company.Project.Module.SubModule {
|
||||
class Report {
|
||||
+generatePDF(data: List)
|
||||
+generateCSV(data: List)
|
||||
}
|
||||
}
|
||||
namespace Company.Project.Module {
|
||||
class Admin {
|
||||
+generateReport()
|
||||
}
|
||||
}
|
||||
Admin --> Report : generates
|
||||
`;
|
||||
|
||||
imgSnapshotTest(diagramDefinition);
|
||||
});
|
||||
|
||||
it('renders a class diagram with multiple classes and relationships in a namespace', () => {
|
||||
const diagramDefinition = `
|
||||
classDiagram-v2
|
||||
namespace Company.Project.Module {
|
||||
class User {
|
||||
+login(username: String, password: String)
|
||||
+logout()
|
||||
}
|
||||
class Admin {
|
||||
+addUser(user: User)
|
||||
+removeUser(user: User)
|
||||
+generateReport()
|
||||
}
|
||||
class Report {
|
||||
+generatePDF(reportData: List)
|
||||
+generateCSV(reportData: List)
|
||||
}
|
||||
}
|
||||
Admin --> User : manages
|
||||
Admin --> Report : generates
|
||||
`;
|
||||
|
||||
imgSnapshotTest(diagramDefinition);
|
||||
});
|
||||
});
|
||||
|
142
cypress/integration/rendering/flowchart-shape-alias.spec.ts
Normal file
142
cypress/integration/rendering/flowchart-shape-alias.spec.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
const aliasSet1 = ['process', 'rect', 'proc', 'rectangle'] as const;
|
||||
|
||||
const aliasSet2 = ['event', 'rounded'] as const;
|
||||
|
||||
const aliasSet3 = ['stadium', 'pill', 'terminal'] as const;
|
||||
|
||||
const aliasSet4 = ['fr-rect', 'subproc', 'subprocess', 'framed-rectangle', 'subroutine'] as const;
|
||||
|
||||
const aliasSet5 = ['db', 'database', 'cylinder', 'cyl'] as const;
|
||||
|
||||
const aliasSet6 = ['diam', 'decision', 'diamond'] as const;
|
||||
|
||||
const aliasSet7 = ['hex', 'hexagon', 'prepare'] as const;
|
||||
|
||||
const aliasSet8 = ['lean-r', 'lean-right', 'in-out'] as const;
|
||||
|
||||
const aliasSet9 = ['lean-l', 'lean-left', 'out-in'] as const;
|
||||
|
||||
const aliasSet10 = ['trap-b', 'trapezoid-bottom', 'priority'] as const;
|
||||
|
||||
const aliasSet11 = ['trap-t', 'trapezoid-top', 'manual'] as const;
|
||||
|
||||
const aliasSet12 = ['dbl-circ', 'double-circle'] as const;
|
||||
|
||||
const aliasSet13 = ['notched-rectangle', 'card', 'notch-rect'] as const;
|
||||
|
||||
const aliasSet14 = [
|
||||
'lin-rect',
|
||||
'lined-rectangle',
|
||||
'lin-proc',
|
||||
'lined-process',
|
||||
'shaded-process',
|
||||
] as const;
|
||||
|
||||
const aliasSet15 = ['sm-circ', 'small-circle', 'start'] as const;
|
||||
|
||||
const aliasSet16 = ['fr-circ', 'framed-circle', 'stop'] as const;
|
||||
|
||||
const aliasSet17 = ['fork', 'join'] as const;
|
||||
// brace-r', 'braces'
|
||||
const aliasSet18 = ['comment', 'brace-l'] as const;
|
||||
|
||||
const aliasSet19 = ['bolt', 'com-link', 'lightning-bolt'] as const;
|
||||
|
||||
const aliasSet20 = ['doc', 'document'] as const;
|
||||
|
||||
const aliasSet21 = ['delay', 'half-rounded-rectangle'] as const;
|
||||
|
||||
const aliasSet22 = ['h-cyl', 'das', 'horizontal-cylinder'] as const;
|
||||
|
||||
const aliasSet23 = ['lin-cyl', 'disk', 'lined-cylinder'] as const;
|
||||
|
||||
const aliasSet24 = ['curv-trap', 'display', 'curved-trapezoid'] as const;
|
||||
|
||||
const aliasSet25 = ['div-rect', 'div-proc', 'divided-rectangle', 'divided-process'] as const;
|
||||
|
||||
const aliasSet26 = ['extract', 'tri', 'triangle'] as const;
|
||||
|
||||
const aliasSet27 = ['win-pane', 'internal-storage', 'window-pane'] as const;
|
||||
|
||||
const aliasSet28 = ['f-circ', 'junction', 'filled-circle'] as const;
|
||||
|
||||
const aliasSet29 = ['lin-doc', 'lined-document'] as const;
|
||||
|
||||
const aliasSet30 = ['notch-pent', 'loop-limit', 'notched-pentagon'] as const;
|
||||
|
||||
const aliasSet31 = ['flip-tri', 'manual-file', 'flipped-triangle'] as const;
|
||||
|
||||
const aliasSet32 = ['sl-rect', 'manual-input', 'sloped-rectangle'] as const;
|
||||
|
||||
const aliasSet33 = ['docs', 'documents', 'st-doc', 'stacked-document'] as const;
|
||||
|
||||
const aliasSet34 = ['procs', 'processes', 'st-rect', 'stacked-rectangle'] as const;
|
||||
|
||||
const aliasSet35 = ['flag', 'paper-tape'] as const;
|
||||
|
||||
const aliasSet36 = ['bow-rect', 'stored-data', 'bow-tie-rectangle'] as const;
|
||||
|
||||
const aliasSet37 = ['cross-circ', 'summary', 'crossed-circle'] as const;
|
||||
|
||||
const aliasSet38 = ['tag-doc', 'tagged-document'] as const;
|
||||
|
||||
const aliasSet39 = ['tag-rect', 'tag-proc', 'tagged-rectangle', 'tagged-process'] as const;
|
||||
|
||||
const aliasSet40 = ['collate', 'hourglass'] as const;
|
||||
|
||||
// Aggregate all alias sets into a single array
|
||||
const aliasSets = [
|
||||
aliasSet1,
|
||||
aliasSet2,
|
||||
aliasSet3,
|
||||
aliasSet4,
|
||||
aliasSet5,
|
||||
aliasSet6,
|
||||
aliasSet7,
|
||||
aliasSet8,
|
||||
aliasSet9,
|
||||
aliasSet10,
|
||||
aliasSet11,
|
||||
aliasSet12,
|
||||
aliasSet13,
|
||||
aliasSet14,
|
||||
aliasSet15,
|
||||
aliasSet16,
|
||||
aliasSet17,
|
||||
aliasSet18,
|
||||
aliasSet19,
|
||||
aliasSet20,
|
||||
aliasSet21,
|
||||
aliasSet22,
|
||||
aliasSet23,
|
||||
aliasSet24,
|
||||
aliasSet25,
|
||||
aliasSet26,
|
||||
aliasSet27,
|
||||
aliasSet28,
|
||||
aliasSet29,
|
||||
aliasSet30,
|
||||
aliasSet31,
|
||||
aliasSet32,
|
||||
aliasSet33,
|
||||
aliasSet34,
|
||||
aliasSet35,
|
||||
aliasSet36,
|
||||
aliasSet37,
|
||||
aliasSet38,
|
||||
aliasSet39,
|
||||
] as const;
|
||||
|
||||
aliasSets.forEach((aliasSet) => {
|
||||
describe(`Test ${aliasSet.join(',')} `, () => {
|
||||
it(`All ${aliasSet.join(',')} should render same shape`, () => {
|
||||
let flowchartCode = `flowchart \n`;
|
||||
aliasSet.forEach((alias, index) => {
|
||||
flowchartCode += ` n${index}@{ shape: ${alias}, label: "${alias}" }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode);
|
||||
});
|
||||
});
|
||||
});
|
126
cypress/integration/rendering/iconShape.spec.ts
Normal file
126
cypress/integration/rendering/iconShape.spec.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
const looks = ['classic', 'handDrawn'] as const;
|
||||
const directions = [
|
||||
'TB',
|
||||
//'BT',
|
||||
'LR',
|
||||
// 'RL'
|
||||
] as const;
|
||||
const forms = [undefined, 'square', 'circle', 'rounded'] as const;
|
||||
const labelPos = [undefined, 't', 'b'] as const;
|
||||
|
||||
looks.forEach((look) => {
|
||||
directions.forEach((direction) => {
|
||||
forms.forEach((form) => {
|
||||
labelPos.forEach((pos) => {
|
||||
describe(`Test iconShape in ${form ? `${form} form,` : ''} ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => {
|
||||
it(`without label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a label for icon shape'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with very long label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a very very very very very long long long label for icon shape'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:true`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** </br>and <strong>strong</strong> for icon shape'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:false`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** </br>and <strong>strong</strong> for icon shape'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, {
|
||||
look,
|
||||
htmlLabels: false,
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
});
|
||||
|
||||
it(`with styles`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with classDef`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
|
||||
flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`;
|
||||
if (form) {
|
||||
flowchartCode += `, form: '${form}'`;
|
||||
}
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
flowchartCode += ` nAA:::customClazz\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test iconShape with different h', () => {
|
||||
it('with different h', () => {
|
||||
let flowchartCode = `flowchart TB\n`;
|
||||
const icon = 'fa:bell';
|
||||
const iconHeight = 64;
|
||||
flowchartCode += ` nA --> nAA@{ icon: '${icon}', label: 'icon with different h', h: ${iconHeight} }\n`;
|
||||
imgSnapshotTest(flowchartCode);
|
||||
});
|
||||
});
|
103
cypress/integration/rendering/imageShape.spec.ts
Normal file
103
cypress/integration/rendering/imageShape.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
const looks = ['classic', 'handDrawn'] as const;
|
||||
const directions = [
|
||||
'TB',
|
||||
//'BT',
|
||||
'LR',
|
||||
// 'RL'
|
||||
] as const;
|
||||
const labelPos = [undefined, 't', 'b'] as const;
|
||||
|
||||
looks.forEach((look) => {
|
||||
directions.forEach((direction) => {
|
||||
labelPos.forEach((pos) => {
|
||||
describe(`Test imageShape in ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => {
|
||||
it(`without label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', w: '100', h: '100' }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a label for image shape'`;
|
||||
|
||||
flowchartCode += `, w: '100', h: '200'`;
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with very long label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a very very very very very long long long label for image shape'`;
|
||||
|
||||
flowchartCode += `, w: '100', h: '250'`;
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:true`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** </br>and <strong>strong</strong> for image shape'`;
|
||||
|
||||
flowchartCode += `, w: '550', h: '200'`;
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, { look, htmlLabels: true });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:false`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** </br>and <strong>strong</strong> for image shape'`;
|
||||
flowchartCode += `, w: '250', h: '200'`;
|
||||
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
imgSnapshotTest(flowchartCode, {
|
||||
look,
|
||||
htmlLabels: false,
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
});
|
||||
|
||||
it(`with styles`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`;
|
||||
flowchartCode += `, w: '550', h: '200'`;
|
||||
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
flowchartCode += ` style A fill:#f9f,stroke:#333,stroke-width:4px \n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with classDef`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#000000,stroke-dasharray: 5 5\n`;
|
||||
flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`;
|
||||
|
||||
flowchartCode += `, w: '500', h: '550'`;
|
||||
if (pos) {
|
||||
flowchartCode += `, pos: '${pos}'`;
|
||||
}
|
||||
flowchartCode += ` }\n`;
|
||||
flowchartCode += ` A:::customClazz\n`;
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
146
cypress/integration/rendering/newShapes.spec.ts
Normal file
146
cypress/integration/rendering/newShapes.spec.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
const looks = ['classic', 'handDrawn'] as const;
|
||||
const directions = [
|
||||
'TB',
|
||||
//'BT',
|
||||
'LR',
|
||||
//'RL'
|
||||
] as const;
|
||||
const newShapesSet1 = [
|
||||
'triangle',
|
||||
'sloped-rectangle',
|
||||
'horizontal-cylinder',
|
||||
'flipped-triangle',
|
||||
'hourglass',
|
||||
] as const;
|
||||
const newShapesSet2 = [
|
||||
'tagged-rectangle',
|
||||
'documents',
|
||||
'lightning-bolt',
|
||||
'filled-circle',
|
||||
'window-pane',
|
||||
] as const;
|
||||
|
||||
const newShapesSet3 = [
|
||||
'curved-trapezoid',
|
||||
'bow-rect',
|
||||
'tagged-document',
|
||||
'divided-rectangle',
|
||||
'crossed-circle',
|
||||
] as const;
|
||||
|
||||
const newShapesSet4 = [
|
||||
'document',
|
||||
'notched-pentagon',
|
||||
'lined-cylinder',
|
||||
'stacked-document',
|
||||
'half-rounded-rectangle',
|
||||
] as const;
|
||||
|
||||
const newShapesSet5 = [
|
||||
'lined-document',
|
||||
'tagged-document',
|
||||
'brace-l',
|
||||
'comment',
|
||||
'braces',
|
||||
'brace-r',
|
||||
] as const;
|
||||
|
||||
const newShapesSet6 = ['brace-r', 'braces'] as const;
|
||||
// Aggregate all shape sets into a single array
|
||||
const newShapesSets = [
|
||||
newShapesSet1,
|
||||
newShapesSet2,
|
||||
newShapesSet3,
|
||||
newShapesSet4,
|
||||
newShapesSet5,
|
||||
newShapesSet6,
|
||||
];
|
||||
|
||||
looks.forEach((look) => {
|
||||
directions.forEach((direction) => {
|
||||
newShapesSets.forEach((newShapesSet) => {
|
||||
describe(`Test ${newShapesSet.join(', ')} in ${look} look and dir ${direction}`, () => {
|
||||
it(`without label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`connect all shapes with each other`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
|
||||
});
|
||||
for (let i = 0; i < newShapesSet.length; i++) {
|
||||
for (let j = i + 1; j < newShapesSet.length; j++) {
|
||||
flowchartCode += ` n${i}${i} --> n${j}${j}\n`;
|
||||
}
|
||||
}
|
||||
if (!(direction === 'TB' && look === 'handDrawn' && newShapesSet === newShapesSet1)) {
|
||||
//skip this test, works in real. Need to look
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
}
|
||||
});
|
||||
|
||||
it(`with very long label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:true`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:false`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, {
|
||||
look,
|
||||
htmlLabels: false,
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
});
|
||||
|
||||
it(`with styles`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
|
||||
flowchartCode += ` style n${index}${index} fill:#f9f,stroke:#333,stroke-width:4px \n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with classDef`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
|
||||
newShapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
|
||||
flowchartCode += ` n${index}${index}:::customClazz\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
107
cypress/integration/rendering/oldShapes.spec.ts
Normal file
107
cypress/integration/rendering/oldShapes.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
const looks = ['classic', 'handDrawn'] as const;
|
||||
const directions = [
|
||||
'TB',
|
||||
//'BT',
|
||||
'LR',
|
||||
//'RL'
|
||||
] as const;
|
||||
|
||||
const shapesSet1 = ['text', 'card', 'lin-rect', 'diamond', 'hexagon'] as const;
|
||||
|
||||
// removing labelRect, need have alias for it
|
||||
const shapesSet2 = ['rounded', 'rect', 'start', 'stop'] as const;
|
||||
|
||||
const shapesSet3 = ['fork', 'choice', 'note', 'stadium', 'odd'] as const;
|
||||
|
||||
const shapesSet4 = ['subroutine', 'cylinder', 'circle', 'doublecircle', 'odd'] as const;
|
||||
|
||||
const shapesSet5 = ['anchor', 'lean-r', 'lean-l', 'trap-t', 'trap-b'] as const;
|
||||
|
||||
// Aggregate all shape sets into a single array
|
||||
const shapesSets = [shapesSet1, shapesSet2, shapesSet3, shapesSet4, shapesSet5] as const;
|
||||
|
||||
looks.forEach((look) => {
|
||||
directions.forEach((direction) => {
|
||||
shapesSets.forEach((shapesSet) => {
|
||||
describe(`Test ${shapesSet.join(', ')} in ${look} look and dir ${direction}`, () => {
|
||||
it(`without label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`connect all shapes with each other`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
|
||||
});
|
||||
for (let i = 0; i < shapesSet.length; i++) {
|
||||
for (let j = i + 1; j < shapesSet.length; j++) {
|
||||
flowchartCode += ` n${i}${i} --> n${j}${j}\n`;
|
||||
}
|
||||
}
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with very long label`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:true`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with markdown htmlLabels:false`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** </br>and <strong>strong</strong> for ${newShape} shape' }\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, {
|
||||
look,
|
||||
htmlLabels: false,
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
});
|
||||
|
||||
it(`with styles`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
|
||||
flowchartCode += ` style n${index}${index} fill:#f9f,stroke:#333,stroke-width:4px \n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
|
||||
it(`with classDef`, () => {
|
||||
let flowchartCode = `flowchart ${direction}\n`;
|
||||
flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
|
||||
shapesSet.forEach((newShape, index) => {
|
||||
flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
|
||||
flowchartCode += ` n${index}${index}:::customClazz\n`;
|
||||
});
|
||||
imgSnapshotTest(flowchartCode, { look });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -83,7 +83,8 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre id="diagram" class="mermaid">
|
||||
<div class="flex">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
title: hello2
|
||||
config:
|
||||
@@ -242,8 +243,190 @@ stateDiagram-v2
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
look: neo
|
||||
---
|
||||
flowchart RL
|
||||
subgraph " "
|
||||
A5@{ shape: manual-file, label: "a label"}
|
||||
B5@{ shape: manual-input, label: "a label" }
|
||||
C5@{ shape: mul-doc, label: "a label" }
|
||||
D5@{ shape: mul-proc, label: "a label" }
|
||||
E5@{ shape: paper-tape, label: "a label" }
|
||||
B3@{ shape: das, label: "a label" }
|
||||
C3@{ shape: disk, label: "a label" }
|
||||
D4@{ shape: lin-doc, label: "a label" }
|
||||
E4@{ shape: loop-limit, label: "a label" }
|
||||
end
|
||||
subgraph " "
|
||||
B6@{ shape: summary, label: "a label" }
|
||||
C6@{ shape: tag-we-rect, label: "a label" }
|
||||
D6@{ shape: tag-rect, label: "a label" }
|
||||
A2@{ shape: fork}
|
||||
B2@{ shape: hourglass }
|
||||
C2@{ shape: comment, label: "I am a comment" }
|
||||
D2@{ shape: bolt }
|
||||
D3@{ shape: disp, label: "a label" }
|
||||
C4@{ shape: junction, label: "a label" }
|
||||
A4@{ shape: extract, label: "a label"}
|
||||
B52[a fr]@{ shape: fr }
|
||||
end
|
||||
subgraph " "
|
||||
A1@{ shape: text, label: This is a textblock}
|
||||
B1@{ shape: card, label: "a label" }
|
||||
C1@{ shape: lined-proc, label: "a label" }
|
||||
D1@{ shape: start, label: "a label" }
|
||||
E1@{ shape: stop, label: "a label" }
|
||||
E2@{ shape: doc, label: "a label" }
|
||||
A6@{ shape: stored-data, label: "a label"}
|
||||
A3@{ shape: delay, label: "a label" }
|
||||
E3@{ shape: div-proc, label: "a label" }
|
||||
B4[a label]@{ shape: win-pane }
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
title: hello2
|
||||
config:
|
||||
look: handDrawn
|
||||
elk:
|
||||
<!-- nodePlacementStrategy: SIMPLE -->
|
||||
---
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
flowchart TD
|
||||
|
||||
A([Start]) -->|go to booking page| B("select
|
||||
ISBS booking no")
|
||||
A --> QQ{cancel booking}
|
||||
A --> RR{no show}
|
||||
A --> SS{change booking}
|
||||
B -->C(wmpay_request_payment.request_type= 'partial',
|
||||
wmpay_request_payment.status= 'paid',
|
||||
pos_booking.booking_status= ‘partial’ and 'full_deposit')
|
||||
style C text-align:left
|
||||
C -->D{manage booking}
|
||||
|
||||
D -->|cancel|E[ระบบแสดงช่องให้กรอกเหตุผล]
|
||||
E -->F{กดปุ่ม 'cancel' หรือไม่}
|
||||
F -->|Yes|G[ระบบบันทึกค่าใหม่
|
||||
และไม่สามารถแก้ไขข้อมูลได้]
|
||||
F -->|No|H[กดปุ่ม 'close']
|
||||
H -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
|
||||
G -->|ระบบส่งข้อมูล|I[(POS_database)]
|
||||
I -->|pos_booking.booking_status='cancel'|Z([End])
|
||||
|
||||
|
||||
D -->|no show|J[ระบบแสดงช่องให้กรอกเหตุผล]
|
||||
J -->K{กดปุ่ม 'noshow' หรือไม่}
|
||||
K -->|Yes|L[ระบบสร้างใบเสร็จอัตโนมัติ
|
||||
Product_id: 439,
|
||||
ItemName: no show]
|
||||
style L text-align:left
|
||||
|
||||
K -->|No|O[กดปุ่ม 'close']
|
||||
O -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
|
||||
L -->M[ระบบบันทึกค่าใหม่]
|
||||
M -->|ระบบส่งข้อมูล|N[(POS_database)]
|
||||
N -->|pos_booking.booking_status=‘noshow’|Z
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
title: hello2
|
||||
config:
|
||||
look: handDrawn
|
||||
layout: dagre
|
||||
elk:
|
||||
nodePlacementStrategy: BRANDES_KOEPF
|
||||
---
|
||||
flowchart
|
||||
A --> A
|
||||
subgraph A
|
||||
B --> B
|
||||
subgraph B
|
||||
C
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
look: handdrawn
|
||||
flowchart:
|
||||
htmlLabels: true
|
||||
---
|
||||
flowchart
|
||||
A[I am a long text, where do I go??? handdrawn - true]
|
||||
</pre
|
||||
>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
flowchart:
|
||||
htmlLabels: false
|
||||
---
|
||||
flowchart
|
||||
A[I am a long text, where do I go??? classic - false]
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
flowchart:
|
||||
htmlLabels: true
|
||||
---
|
||||
flowchart
|
||||
A[I am a long text, where do I go??? classic - true]
|
||||
</pre
|
||||
>
|
||||
</div>
|
||||
<pre id="diagram2" class="mermaid2">
|
||||
flowchart LR
|
||||
id1(Start)-->id2(Stop)
|
||||
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<pre id="diagram3" class="mermaid2">
|
||||
flowchart LR
|
||||
A:::foo & B:::bar --> C:::foobar
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef ash color:red
|
||||
class C ash
|
||||
style C stroke:#00f, fill:black
|
||||
|
||||
</pre>
|
||||
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
stateDiagram
|
||||
A:::foo
|
||||
B:::bar --> C:::foobar
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
style C stroke:#00f, fill:black, color:white
|
||||
|
||||
</pre>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart TB
|
||||
A@{
|
||||
label: "aksljhf kasjdh"
|
||||
}
|
||||
</pre>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||
|
174
cypress/platform/saurabh.html
Normal file
174
cypress/platform/saurabh.html
Normal file
@@ -0,0 +1,174 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
/>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<style>
|
||||
body {
|
||||
/* background: rgb(221, 208, 208); */
|
||||
/* background: #333; */
|
||||
font-family: 'Arial';
|
||||
/* font-size: 18px !important; */
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.mermaid2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mermaid svg {
|
||||
/* font-size: 18px !important; */
|
||||
|
||||
/* background-color: #efefef;
|
||||
background-image: radial-gradient(#fff 51%, transparent 91%),
|
||||
radial-gradient(#fff 51%, transparent 91%);
|
||||
background-size: 20px 20px;
|
||||
background-position:
|
||||
0 0,
|
||||
10px 10px;
|
||||
background-repeat: repeat; */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="display: flex; gap: 2rem; flex-direction: row">
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart LR
|
||||
A@{ icon: "fa:window-minimize", form: circle }
|
||||
E@{ icon: "fa:window-minimize", form: circle }
|
||||
B@{ icon: "fa:bell", form: circle }
|
||||
B2@{ icon: "fa:bell", form: circle }
|
||||
C@{ icon: "fa:address-book", form: square }
|
||||
D@{ icon: "fa:star-half", form: square }
|
||||
A --> E
|
||||
B --> B2
|
||||
|
||||
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart TB
|
||||
A --test2--> B2@{ icon: "fa:bell", form: "rounded", label: "B2 aiduaid uyawduad uaduabd uyduadb", pos: "b" }
|
||||
B2 --test--> C
|
||||
D --> B2 --> E
|
||||
style B2 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram43" class="mermaid2">
|
||||
flowchart BT
|
||||
A --test2--> B2@{ icon: "fa:bell", form: "square", label: "B2", pos: "t", h: 40, w: 30 }
|
||||
B2 --test--> C
|
||||
D --> B2 --> E
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart BT
|
||||
A --test2--> B2@{ icon: "fa:bell", label: "B2 awiugdawu uydgayuiwd wuydguy", pos: "b", h: 40, w: 30 }
|
||||
B2 --test--> C
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram43" class="mermaid2">
|
||||
flowchart BT
|
||||
A --test2--> B2@{ icon: "fa:bell", label: "B2 dawuygd ayuwgd uy", pos: "t", h: 40, w: 30 }
|
||||
B2 --test--> C
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram6" class="mermaid2">
|
||||
flowchart TB
|
||||
A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "t", w: 200, h: 100 } --> C
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram6" class="mermaid2">
|
||||
flowchart TB
|
||||
A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "b", w: 200, h: 100 } --> C
|
||||
D --> B2 --> E
|
||||
</pre
|
||||
>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||
mermaid.registerLayoutLoaders(layouts);
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
|
||||
},
|
||||
{
|
||||
name: 'fa',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/fa6-solid/icons.json').then((res) => res.json()),
|
||||
},
|
||||
]);
|
||||
mermaid.parseError = function (err, hash) {
|
||||
console.error('Mermaid error: ', err);
|
||||
};
|
||||
window.callback = function () {
|
||||
alert('A callback was triggered');
|
||||
};
|
||||
mermaid.initialize({
|
||||
// theme: 'base',
|
||||
// handdrawnSeed: 12,
|
||||
look: 'classic',
|
||||
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
|
||||
// 'elk.nodePlacement.strategy': 'SIMPLE',
|
||||
// 'elk.nodePlacement.strategy': 'LAYERED',
|
||||
// 'elk.mergeEdges': true,
|
||||
// layout: 'dagre',
|
||||
// layout: 'elk',
|
||||
// layout: 'fixed',
|
||||
// htmlLabels: false,
|
||||
flowchart: { titleTopMargin: 10, padding: 0, htmlLabels: true },
|
||||
// fontFamily: 'Caveat',
|
||||
// fontFamily: 'Kalam',
|
||||
fontFamily: 'courier',
|
||||
sequence: {
|
||||
actorFontFamily: 'courier',
|
||||
noteFontFamily: 'courier',
|
||||
messageFontFamily: 'courier',
|
||||
},
|
||||
themeVariables: {
|
||||
fontSize: 50,
|
||||
fontFamily: 'courier',
|
||||
},
|
||||
logLevel: 0,
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
function callback() {
|
||||
alert('It worked');
|
||||
}
|
||||
mermaid.parseError = function (err, hash) {
|
||||
console.error('In parse error:');
|
||||
console.error(err);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -50,6 +50,23 @@ const contentLoaded = async function () {
|
||||
|
||||
mermaid.registerLayoutLoaders(layouts);
|
||||
mermaid.initialize(graphObj.mermaid);
|
||||
const staticBellIconPack = {
|
||||
prefix: 'fa6-regular',
|
||||
icons: {
|
||||
bell: {
|
||||
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
|
||||
width: 448,
|
||||
},
|
||||
},
|
||||
width: 512,
|
||||
height: 512,
|
||||
};
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'fa',
|
||||
loader: () => staticBellIconPack,
|
||||
},
|
||||
]);
|
||||
await mermaid.run();
|
||||
}
|
||||
};
|
||||
|
@@ -159,30 +159,87 @@
|
||||
class People List~List~Person~~
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
classDiagram
|
||||
A1 --> B1
|
||||
namespace A {
|
||||
class A1 {
|
||||
+foo : string
|
||||
}
|
||||
class A2 {
|
||||
+bar : int
|
||||
namespace Company.Project.Module {
|
||||
class GenericClass~T~ {
|
||||
+addItem(item: T)
|
||||
+getItem() T
|
||||
}
|
||||
}
|
||||
namespace B {
|
||||
class B1 {
|
||||
+foo : bool
|
||||
}
|
||||
class B2 {
|
||||
+bar : float
|
||||
}
|
||||
}
|
||||
A2 --> B2
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
classDiagram
|
||||
namespace Company.Project.Module.SubModule {
|
||||
class Report {
|
||||
+generatePDF(data: List)
|
||||
+generateCSV(data: List)
|
||||
}
|
||||
}
|
||||
namespace Company.Project.Module {
|
||||
class Admin {
|
||||
+generateReport()
|
||||
}
|
||||
}
|
||||
Admin --> Report : generates
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
classDiagram
|
||||
namespace Company.Project.Module {
|
||||
class User {
|
||||
+login(username: String, password: String)
|
||||
+logout()
|
||||
}
|
||||
class Admin {
|
||||
+addUser(user: User)
|
||||
+removeUser(user: User)
|
||||
+generateReport()
|
||||
}
|
||||
class Report {
|
||||
+generatePDF(reportData: List)
|
||||
+generateCSV(reportData: List)
|
||||
}
|
||||
}
|
||||
Admin --> User : manages
|
||||
Admin --> Report : generates
|
||||
</pre>
|
||||
<hr />
|
||||
<pre class="mermaid">
|
||||
classDiagram
|
||||
namespace Shapes {
|
||||
class Shape {
|
||||
+calculateArea() double
|
||||
}
|
||||
class Circle {
|
||||
+double radius
|
||||
}
|
||||
class Square {
|
||||
+double side
|
||||
}
|
||||
}
|
||||
|
||||
Shape <|-- Circle
|
||||
Shape <|-- Square
|
||||
|
||||
namespace Vehicles {
|
||||
class Vehicle {
|
||||
+String brand
|
||||
}
|
||||
class Car {
|
||||
+int horsepower
|
||||
}
|
||||
class Bike {
|
||||
+boolean hasGears
|
||||
}
|
||||
}
|
||||
|
||||
Vehicle <|-- Car
|
||||
Vehicle <|-- Bike
|
||||
Car --> Circle : "Logo Shape"
|
||||
Bike --> Square : "Logo Shape"
|
||||
|
||||
</pre>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
|
228
docs/adding-new-shape.md
Normal file
228
docs/adding-new-shape.md
Normal file
@@ -0,0 +1,228 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/adding-new-shape.md](../packages/mermaid/src/docs/adding-new-shape.md).
|
||||
|
||||
# Custom SVG Shapes Library
|
||||
|
||||
This library provides a collection of custom SVG shapes, utilities, and helpers for generating diagram components. The shapes are designed to be used within an SVG container and include a variety of common and complex shapes.
|
||||
|
||||
## Overview
|
||||
|
||||
## Shape Helpers and Utilities
|
||||
|
||||
Before starting with shape creation, it's essential to familiarize yourself with the utilities provided in the `utils.ts` file from `packages/mermaid/src/rendering-util/rendering-elements/shapes/util.js`. These utilities are designed to assist with various aspects of SVG shape manipulation and ensure consistent and accurate rendering.
|
||||
|
||||
## Available Utilities
|
||||
|
||||
### 1. `labelHelper`
|
||||
|
||||
- **Purpose**: This function creates and inserts labels inside SVG shapes.
|
||||
- **Features**:
|
||||
- Handles both HTML labels and plain text.
|
||||
- Calculates the bounding box dimensions of the label.
|
||||
- Ensures proper positioning of labels within shapes.
|
||||
|
||||
### 2. `updateNodeBounds`
|
||||
|
||||
- **Purpose**: Updates the bounding box dimensions (width and height) of a node.
|
||||
- **Usage**:
|
||||
- Adjusts the size of the node to fit the content or shape.
|
||||
- Useful for ensuring that shapes resize appropriately based on their content.
|
||||
|
||||
### 3. `insertPolygonShape`
|
||||
|
||||
- **Purpose**: Inserts a polygon shape into an SVG container.
|
||||
- **Features**:
|
||||
- Handles the creation and insertion of complex polygonal shapes.
|
||||
- Configures the shape's appearance and positioning within the SVG container.
|
||||
|
||||
### 4. `getNodeClasses`
|
||||
|
||||
- **Purpose**: Returns the appropriate CSS classes for a node based on its configuration.
|
||||
- **Usage**:
|
||||
- Dynamically applies CSS classes to nodes for styling purposes.
|
||||
- Ensures that nodes adhere to the desired design and theme.
|
||||
|
||||
### 5. `createPathFromPoints`
|
||||
|
||||
- **Purpose**: Generates an SVG path string from an array of points.
|
||||
- **Usage**:
|
||||
- Converts a list of points into a smooth path.
|
||||
- Useful for creating custom shapes or paths within the SVG.
|
||||
|
||||
### 6. `generateFullSineWavePoints`
|
||||
|
||||
- **Purpose**: Generates points for a sine wave, useful for creating wavy-edged shapes.
|
||||
- **Usage**:
|
||||
- Facilitates the creation of shapes with wavy or sine-wave edges.
|
||||
- Can be used to add decorative or dynamic edges to shapes.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To utilize these utilities, simply import them from the `utils.ts` file into your shape creation script. These helpers will streamline the process of building and customizing SVG shapes, ensuring consistent results across your projects.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
labelHelper,
|
||||
updateNodeBounds,
|
||||
insertPolygonShape,
|
||||
getNodeClasses,
|
||||
createPathFromPoints,
|
||||
generateFullSineWavePoints,
|
||||
} from './utils.ts';
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here’s a basic example of how you might use some of these utilities:
|
||||
|
||||
```typescript
|
||||
import { labelHelper, insertPolygonShape } from './utils.ts';
|
||||
|
||||
const svgContainer = document.getElementById('svgContainer');
|
||||
|
||||
// Insert a polygon shape
|
||||
insertPolygonShape(svgContainer /* shape-specific parameters */);
|
||||
|
||||
// Create and insert a label inside the shape
|
||||
labelHelper(svgContainer /* label-specific parameters */);
|
||||
```
|
||||
|
||||
## Adding New Shapes
|
||||
|
||||
### 1. Create the Shape Function
|
||||
|
||||
To add a new shape:
|
||||
|
||||
- **Create the shape function**: Create a new file of name of the shape and export a function in the `shapes` directory that generates your shape. The file and function should follow the pattern used in existing shapes and return an SVG element.
|
||||
|
||||
- **Example**:
|
||||
|
||||
```typescript
|
||||
import { Node, RenderOptions } from '../../types.d.ts';
|
||||
|
||||
export const myNewShape = async (
|
||||
parent: SVGAElement,
|
||||
node: Node,
|
||||
renderOptions: RenderOptions
|
||||
) => {
|
||||
// Create your shape here
|
||||
const shape = parent.insert('g').attr('class', 'my-new-shape');
|
||||
// Add other elements or styles as needed
|
||||
return shape;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Register the Shape
|
||||
|
||||
- **Register the shape**: Add your shape to the `shapes` object in the main shapes module. This allows your shape to be recognized and used within the system.
|
||||
|
||||
- **Example**:
|
||||
|
||||
```typescript
|
||||
import { myNewShape } from './shapes/myNewShape';
|
||||
|
||||
const shapes = {
|
||||
...,
|
||||
'my-new-shape': myNewShape,
|
||||
// Shortened alias (if any).
|
||||
'm-nsh': myNewShape
|
||||
};
|
||||
```
|
||||
|
||||
# Shape Intersection Algorithms
|
||||
|
||||
This contains algorithms and utilities for calculating intersection points for various shapes in SVG. Arrow intersection points are crucial for accurately determining where arrows connect with shapes. Ensuring precise intersection points enhances the clarity and accuracy of flowcharts and diagrams.
|
||||
|
||||
## Shape Intersection Functions
|
||||
|
||||
### 1. `Ellipse`
|
||||
|
||||
Calculates the intersection points for an ellipse.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```javascript
|
||||
import intersectEllipse from './intersect-ellipse.js';
|
||||
|
||||
const intersection = intersectEllipse(node, rx, ry, point);
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `node`: The SVG node element.
|
||||
- `rx`: The x-radius of the ellipse.
|
||||
- `ry`: The y-radius of the ellipse.
|
||||
- `point`: The point from which the intersection is calculated.
|
||||
|
||||
### 2. `intersectRect`
|
||||
|
||||
Calculates the intersection points for a rectangle.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```javascript
|
||||
import intersectRect from './intersect-rect.js';
|
||||
|
||||
const intersection = intersectRect(node, point);
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `node`: The SVG node element.
|
||||
- `point`: The point from which the intersection is calculated.
|
||||
|
||||
### 3. `intersectPolygon`
|
||||
|
||||
Calculates the intersection points for a polygon.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```javascript
|
||||
import intersectPolygon from './intersect-polygon.js';
|
||||
|
||||
const intersection = intersectPolygon(node, polyPoints, point);
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `node`: The SVG node element.
|
||||
- `polyPoints`: Array of points defining the polygon.
|
||||
- `point`: The point from which the intersection is calculated.
|
||||
|
||||
## Cypress Tests
|
||||
|
||||
To ensure the robustness of the flowchart shapes, there are implementation of comprehensive Cypress test cases in `newShapes.spec.ts` file. These tests cover various aspects such as:
|
||||
|
||||
- **Shapes**: Testing new shapes like `bowTieRect`, `waveRectangle`, `trapezoidalPentagon`, etc.
|
||||
- **Looks**: Verifying shapes under different visual styles (`classic` and `handDrawn`).
|
||||
- **Directions**: Ensuring correct rendering in all flow directions of arrows :
|
||||
- `TB` `(Top -> Bottom)`
|
||||
- `BT` `(Bottom -> Top)`
|
||||
- `LR` `(Left -> Right)`
|
||||
- `RL` `(Right -> Left)`
|
||||
- **Labels**: Testing shapes with different labels, including:
|
||||
- No labels
|
||||
- Short labels
|
||||
- Very long labels
|
||||
- Markdown with `htmlLabels:true` and `htmlLabels:false`
|
||||
- **Styles**: Applying custom styles to shapes and verifying correct rendering.
|
||||
- **Class Definitions**: Using `classDef` to apply custom classes and testing their impact.
|
||||
|
||||
### Running the Tests
|
||||
|
||||
To run the Cypress tests, follow these steps:
|
||||
|
||||
1. Ensure you have all dependencies installed by running:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
2. Start the Cypress test runner:
|
||||
|
||||
```bash
|
||||
cypress open --env updateSnapshots=true
|
||||
|
||||
```
|
||||
|
||||
3. Select the test suite from the Cypress interface to run all the flowchart shape tests.
|
@@ -127,7 +127,7 @@ Error.prepareStackTrace
|
||||
|
||||
#### Defined in
|
||||
|
||||
node_modules/@types/node/globals.d.ts:28
|
||||
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:28
|
||||
|
||||
---
|
||||
|
||||
@@ -141,7 +141,7 @@ Error.stackTraceLimit
|
||||
|
||||
#### Defined in
|
||||
|
||||
node_modules/@types/node/globals.d.ts:30
|
||||
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:30
|
||||
|
||||
## Methods
|
||||
|
||||
@@ -168,4 +168,4 @@ Error.captureStackTrace
|
||||
|
||||
#### Defined in
|
||||
|
||||
node_modules/@types/node/globals.d.ts:21
|
||||
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:21
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/types.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L117)
|
||||
[packages/mermaid/src/rendering-util/types.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L125)
|
||||
|
||||
---
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L116)
|
||||
[packages/mermaid/src/rendering-util/types.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L124)
|
||||
|
||||
---
|
||||
|
||||
@@ -40,4 +40,4 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L115)
|
||||
[packages/mermaid/src/rendering-util/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L123)
|
||||
|
@@ -19,4 +19,4 @@ The `parseError` function will not be called.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L45)
|
||||
[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56)
|
||||
|
@@ -18,7 +18,7 @@ The config passed as YAML frontmatter or directives
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56)
|
||||
[packages/mermaid/src/types.ts:67](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L67)
|
||||
|
||||
---
|
||||
|
||||
@@ -30,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:52](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L52)
|
||||
[packages/mermaid/src/types.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L63)
|
||||
|
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L79)
|
||||
[packages/mermaid/src/types.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L90)
|
||||
|
||||
---
|
||||
|
||||
@@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:69](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L69)
|
||||
[packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80)
|
||||
|
||||
---
|
||||
|
||||
@@ -63,4 +63,4 @@ The svg code for the rendered graph.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:65](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L65)
|
||||
[packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L76)
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
|
||||
[packages/mermaid/src/defaultConfig.ts:267](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L267)
|
||||
|
||||
---
|
||||
|
||||
|
@@ -64,7 +64,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
|
||||
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅
|
||||
- [Mermerd](https://github.com/KarnerTh/mermerd)
|
||||
- [Slab](https://slab.com) ✅
|
||||
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-) ✅
|
||||
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts) ✅
|
||||
- [NotesHub](https://noteshub.app) ✅
|
||||
- [Notion](https://notion.so) ✅
|
||||
- [Observable](https://observablehq.com/@observablehq/mermaid) ✅
|
||||
|
@@ -12,7 +12,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
|
||||
<br />
|
||||
|
||||
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid Whiteboard - Drag & Drop your Nodes with Mermaid's new Whiteboard! | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## About
|
||||
|
||||
@@ -22,17 +22,20 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
|
||||
- **Editor** - A web based editor for creating and editing Mermaid diagrams.
|
||||
|
||||
- **Visual Editor** - The Visual Editor enables users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
|
||||
- **Mermaid AI** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
|
||||
|
||||
- **AI Chat** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
|
||||
- **Whiteboard** - A virtual whiteboard for creating and editing Mermaid diagrams.
|
||||
|
||||
- **Plugins** - A plugin system for extending the functionality of Mermaid.
|
||||
|
||||
Official Mermaid Chart plugins:
|
||||
|
||||
- [Mermaid Chart GPT](https://chat.openai.com/g/g-1IRFKwq4G-mermaid-chart)
|
||||
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)
|
||||
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart)
|
||||
- [JetBrains IDE](https://plugins.jetbrains.com/plugin/23043-mermaid-chart)
|
||||
- [Google Docs](https://gsuite.google.com/marketplace/app/mermaidchart/947683068472)
|
||||
- [Microsoft PowerPoint and Word](https://appsource.microsoft.com/en-us/product/office/WA200006214?tab=Overview)
|
||||
|
||||
Visit our [Plugins](https://www.mermaidchart.com/plugins) page for more information.
|
||||
|
@@ -6,6 +6,12 @@
|
||||
|
||||
# Blog
|
||||
|
||||
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
|
||||
|
||||
24 September 2024 · 5 mins
|
||||
|
||||
Discover 30 new shapes in Mermaid flowcharts, offering enhanced clarity, customization, and versatility for more dynamic and expressive visualizations.
|
||||
|
||||
## [Introducing Architecture Diagrams in Mermaid](https://www.mermaidchart.com/blog/posts/mermaid-supports-architecture-diagrams/)
|
||||
|
||||
2 September 2024 · 2 mins
|
||||
|
@@ -211,6 +211,27 @@ block-beta
|
||||
|
||||
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
|
||||
|
||||
**Merging Blocks Horizontally:**
|
||||
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
|
||||
|
||||
```mermaid-example
|
||||
block-beta
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block-beta
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
end
|
||||
```
|
||||
|
||||
In this example, the width of the merged block dynamically adjusts to the width of the largest child block.
|
||||
|
||||
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
|
||||
|
||||
## 4. Block Varieties and Shapes
|
||||
|
@@ -298,6 +298,634 @@ flowchart TD
|
||||
id1(((This is the text in the circle)))
|
||||
```
|
||||
|
||||
## Expanded Node Shapes in Mermaid Flowcharts (v11.3.0+)
|
||||
|
||||
Mermaid introduces 30 new shapes to enhance the flexibility and precision of flowchart creation. These new shapes provide more options to represent processes, decisions, events, data storage visually, and other elements within your flowcharts, improving clarity and semantic meaning.
|
||||
|
||||
New Syntax for Shape Definition
|
||||
|
||||
Mermaid now supports a general syntax for defining shape types to accommodate the growing number of shapes. This syntax allows you to assign specific shapes to nodes using a clear and flexible format:
|
||||
|
||||
```
|
||||
A@{ shape: rect }
|
||||
```
|
||||
|
||||
This syntax creates a node A as a rectangle. It renders in the same way as `A["A"]`, or `A`.
|
||||
|
||||
### Complete List of New Shapes
|
||||
|
||||
Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases:
|
||||
|
||||
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||
| ------------------------------------- | ---------------------- | -------------- | ------------------------------ | -------------------------------------------------------------- |
|
||||
| **Process** | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
|
||||
| **Event** | Rounded Rectangle | `rounded` | Represents an event | `event` |
|
||||
| **Terminal Point** | Stadium | `stadium` | Terminal point | `terminal`, `pill` |
|
||||
| **Subprocess** | Framed Rectangle | `fr-rect` | Subprocess | `subprocess`,`subproc`, `framed-rectangle`, `subroutine` |
|
||||
| **Database** | Cylinder | `cyl` | Database storage | `db`, `database`, `cylinder` |
|
||||
| **Start** | Circle | `circle` | Starting point | `circ` |
|
||||
| **Odd** | Odd | `odd` | Odd shape | |
|
||||
| **Decision** | Diamond | `diam` | Decision-making step | `decision`, `diamond` |
|
||||
| **Prepare Conditional** | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
|
||||
| **Data Input/Output** | Lean Right | `lean-r` | Represents input or output | `lean-right`, `in-out` |
|
||||
| **Data Input/Output** | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
|
||||
| **Priority Action** | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid-bottom` |
|
||||
| **Manual Operation** | Trapezoid Base Top | `trap-t` | Represents a manual task | `manual`, `trapezoid-top` |
|
||||
| **Stop** | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
|
||||
| **Text Block** | Text Block | `text` | Text block | - |
|
||||
| **Card** | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
||||
| **Lined/Shaded Process** | Lined Rectangle | `lin-rect` | Lined process shape | `lined-rectangle`,`lined-process`, `lin-proc`,`shaded-process` |
|
||||
| **Start** | Small Circle | `sm-circ` | Small starting point | `start`, `small-circle` |
|
||||
| **Stop** | Framed Circle | `fr-circ` | Stop point | `stop`, `framed-circle` |
|
||||
| **Fork/Join** | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
|
||||
| **Collate** | Hourglass | `hourglass` | Represents a collate operation | `hourglass` |
|
||||
| **Comment** | Curly Brace | `brace` | Adds a comment | `comment`, `brace-l` |
|
||||
| **Comment Right** | Curly Brace | `brace-r` | Adds a comment | - |
|
||||
| **Comment with braces on both sides** | Curly Braces | `braces` | Adds a comment | - |
|
||||
| **Com Link** | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
||||
| **Document** | Document | `doc` | Represents a document | `doc`, `document` |
|
||||
| **Delay** | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
|
||||
| **Direct Access Storage** | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
|
||||
| **Disk Storage** | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
|
||||
| **Display** | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
|
||||
| **Divided Process** | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-rectangle`, `divided-process` |
|
||||
| **Extract** | Triangle | `tri` | Extraction process | `extract`, `triangle` |
|
||||
| **Internal Storage** | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
|
||||
| **Junction** | Filled Circle | `f-circ` | Junction point | `junction`, `filled-circle` |
|
||||
| **Lined Document** | Lined Document | `lin-doc` | Lined document | `lined-document` |
|
||||
| **Loop Limit** | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
|
||||
| **Manual File** | Flipped Triangle | `flip-tri` | Manual file operation | `manual-file`, `flipped-triangle` |
|
||||
| **Manual Input** | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
|
||||
| **Multi-Document** | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
|
||||
| **Multi-Process** | Stacked Rectangle | `st-rect` | Multiple processes | `procs`, `processes`, `stacked-rectangle` |
|
||||
| **Paper Tape** | Flag | `flag` | Paper tape | `paper-tape` |
|
||||
| **Stored Data** | Bow Tie Rectangle | `bow-rect` | Stored data | `stored-data`, `bow-tie-rectangle` |
|
||||
| **Summary** | Crossed Circle | `cross-circ` | Summary | `summary`, `crossed-circle` |
|
||||
| **Tagged Document** | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
|
||||
| **Tagged Process** | Tagged Rectangle | `tag-rect` | Tagged process | `tagged-rectangle`,`tag-proc`, `tagged-process` |
|
||||
|
||||
### Example Flowchart with New Shapes
|
||||
|
||||
Here’s an example flowchart that utilizes some of the newly introduced shapes:
|
||||
|
||||
```mermaid-example
|
||||
flowchart RL
|
||||
A@{ shape: manual-file, label: "File Handling"}
|
||||
B@{ shape: manual-input, label: "User Input"}
|
||||
C@{ shape: docs, label: "Multiple Documents"}
|
||||
D@{ shape: procs, label: "Process Automation"}
|
||||
E@{ shape: paper-tape, label: "Paper Records"}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart RL
|
||||
A@{ shape: manual-file, label: "File Handling"}
|
||||
B@{ shape: manual-input, label: "User Input"}
|
||||
C@{ shape: docs, label: "Multiple Documents"}
|
||||
D@{ shape: procs, label: "Process Automation"}
|
||||
E@{ shape: paper-tape, label: "Paper Records"}
|
||||
```
|
||||
|
||||
### Process
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: rect, label: "This is a process" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: rect, label: "This is a process" }
|
||||
```
|
||||
|
||||
### Event
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: rounded, label: "This is an event" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: rounded, label: "This is an event" }
|
||||
```
|
||||
|
||||
### Terminal Point (Stadium)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: stadium, label: "Terminal point" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: stadium, label: "Terminal point" }
|
||||
```
|
||||
|
||||
### Subprocess
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: subproc, label: "This is a subprocess" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: subproc, label: "This is a subprocess" }
|
||||
```
|
||||
|
||||
### Database (Cylinder)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: cyl, label: "Database" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: cyl, label: "Database" }
|
||||
```
|
||||
|
||||
### Start (Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: circle, label: "Start" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: circle, label: "Start" }
|
||||
```
|
||||
|
||||
### Odd
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: odd, label: "Odd shape" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: odd, label: "Odd shape" }
|
||||
```
|
||||
|
||||
### Decision (Diamond)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: diamond, label: "Decision" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: diamond, label: "Decision" }
|
||||
```
|
||||
|
||||
### Prepare Conditional (Hexagon)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: hex, label: "Prepare conditional" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: hex, label: "Prepare conditional" }
|
||||
```
|
||||
|
||||
### Data Input/Output (Lean Right)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lean-r, label: "Input/Output" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: lean-r, label: "Input/Output" }
|
||||
```
|
||||
|
||||
### Data Input/Output (Lean Left)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lean-l, label: "Output/Input" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: lean-l, label: "Output/Input" }
|
||||
```
|
||||
|
||||
### Priority Action (Trapezoid Base Bottom)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: trap-b, label: "Priority action" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: trap-b, label: "Priority action" }
|
||||
```
|
||||
|
||||
### Manual Operation (Trapezoid Base Top)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: trap-t, label: "Manual operation" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: trap-t, label: "Manual operation" }
|
||||
```
|
||||
|
||||
### Stop (Double Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: dbl-circ, label: "Stop" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: dbl-circ, label: "Stop" }
|
||||
```
|
||||
|
||||
### Text Block
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: text, label: "This is a text block" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: text, label: "This is a text block" }
|
||||
```
|
||||
|
||||
### Card (Notched Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: notch-rect, label: "Card" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: notch-rect, label: "Card" }
|
||||
```
|
||||
|
||||
### Lined/Shaded Process
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lin-rect, label: "Lined process" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: lin-rect, label: "Lined process" }
|
||||
```
|
||||
|
||||
### Start (Small Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: sm-circ, label: "Small start" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: sm-circ, label: "Small start" }
|
||||
```
|
||||
|
||||
### Stop (Framed Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: framed-circle, label: "Stop" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: framed-circle, label: "Stop" }
|
||||
```
|
||||
|
||||
### Fork/Join (Long Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: fork, label: "Fork or Join" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: fork, label: "Fork or Join" }
|
||||
```
|
||||
|
||||
### Collate (Hourglass)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: hourglass, label: "Collate" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: hourglass, label: "Collate" }
|
||||
```
|
||||
|
||||
### Comment (Curly Brace)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: comment, label: "Comment" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: comment, label: "Comment" }
|
||||
```
|
||||
|
||||
### Comment Right (Curly Brace Right)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: brace-r, label: "Comment" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: brace-r, label: "Comment" }
|
||||
```
|
||||
|
||||
### Comment with braces on both sides
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: braces, label: "Comment" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: braces, label: "Comment" }
|
||||
```
|
||||
|
||||
### Com Link (Lightning Bolt)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: bolt, label: "Communication link" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: bolt, label: "Communication link" }
|
||||
```
|
||||
|
||||
### Document
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: doc, label: "Document" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: doc, label: "Document" }
|
||||
```
|
||||
|
||||
### Delay (Half-Rounded Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: delay, label: "Delay" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: delay, label: "Delay" }
|
||||
```
|
||||
|
||||
### Direct Access Storage (Horizontal Cylinder)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: das, label: "Direct access storage" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: das, label: "Direct access storage" }
|
||||
```
|
||||
|
||||
### Disk Storage (Lined Cylinder)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lin-cyl, label: "Disk storage" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: lin-cyl, label: "Disk storage" }
|
||||
```
|
||||
|
||||
### Display (Curved Trapezoid)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: curv-trap, label: "Display" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: curv-trap, label: "Display" }
|
||||
```
|
||||
|
||||
### Divided Process (Divided Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: div-rect, label: "Divided process" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: div-rect, label: "Divided process" }
|
||||
```
|
||||
|
||||
### Extract (Small Triangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: tri, label: "Extract" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: tri, label: "Extract" }
|
||||
```
|
||||
|
||||
### Internal Storage (Window Pane)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: win-pane, label: "Internal storage" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: win-pane, label: "Internal storage" }
|
||||
```
|
||||
|
||||
### Junction (Filled Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: f-circ, label: "Junction" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: f-circ, label: "Junction" }
|
||||
```
|
||||
|
||||
### Lined Document
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lin-doc, label: "Lined document" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: lin-doc, label: "Lined document" }
|
||||
```
|
||||
|
||||
### Loop Limit (Notched Pentagon)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: notch-pent, label: "Loop limit" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: notch-pent, label: "Loop limit" }
|
||||
```
|
||||
|
||||
### Manual File (Flipped Triangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: flip-tri, label: "Manual file" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: flip-tri, label: "Manual file" }
|
||||
```
|
||||
|
||||
### Manual Input (Sloped Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: sl-rect, label: "Manual input" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: sl-rect, label: "Manual input" }
|
||||
```
|
||||
|
||||
### Multi-Document (Stacked Document)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: docs, label: "Multiple documents" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: docs, label: "Multiple documents" }
|
||||
```
|
||||
|
||||
### Multi-Process (Stacked Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: processes, label: "Multiple processes" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: processes, label: "Multiple processes" }
|
||||
```
|
||||
|
||||
### Paper Tape (Flag)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: flag, label: "Paper tape" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: flag, label: "Paper tape" }
|
||||
```
|
||||
|
||||
### Stored Data (Bow Tie Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: bow-rect, label: "Stored data" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: bow-rect, label: "Stored data" }
|
||||
```
|
||||
|
||||
### Summary (Crossed Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: cross-circ, label: "Summary" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: cross-circ, label: "Summary" }
|
||||
```
|
||||
|
||||
### Tagged Document
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: tag-doc, label: "Tagged document" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: tag-doc, label: "Tagged document" }
|
||||
```
|
||||
|
||||
### Tagged Process (Tagged Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: tag-rect, label: "Tagged process" }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A@{ shape: tag-rect, label: "Tagged process" }
|
||||
```
|
||||
|
||||
## Links between nodes
|
||||
|
||||
Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link.
|
||||
|
@@ -155,6 +155,9 @@ end
|
||||
box rgb(33,66,99)
|
||||
... actors ...
|
||||
end
|
||||
box rgba(33,66,99,0.5)
|
||||
... actors ...
|
||||
end
|
||||
```
|
||||
|
||||
> **Note**
|
||||
@@ -580,6 +583,12 @@ sequenceDiagram
|
||||
|
||||
It is possible to highlight flows by providing colored background rects. This is done by the notation
|
||||
|
||||
```
|
||||
rect COLOR
|
||||
... content ...
|
||||
end
|
||||
```
|
||||
|
||||
The colors are defined using rgb and rgba syntax.
|
||||
|
||||
```
|
||||
|
@@ -131,5 +131,10 @@
|
||||
},
|
||||
"nyc": {
|
||||
"report-dir": "coverage/cypress"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"roughjs": "patches/roughjs.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,20 @@
|
||||
# @mermaid-js/layout-elk
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5825](https://github.com/mermaid-js/mermaid/pull/5825) [`233e36c`](https://github.com/mermaid-js/mermaid/commit/233e36c9884fcce141a72ce7c845179781e18632) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - chore: Update render options
|
||||
|
||||
- Updated dependencies [[`6c5b7ce`](https://github.com/mermaid-js/mermaid/commit/6c5b7ce9f41c0fbd59fe03dbefc8418d97697f0a), [`9e3aa70`](https://github.com/mermaid-js/mermaid/commit/9e3aa705ae21fd4898504ab22d775a9e437b898e), [`de2c05c`](https://github.com/mermaid-js/mermaid/commit/de2c05cd5463af68d19dd7b6b3f1303d69ddb2dd)]:
|
||||
- mermaid@11.3.0
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5847](https://github.com/mermaid-js/mermaid/pull/5847) [`dd03043`](https://github.com/mermaid-js/mermaid/commit/dd0304387e85fc57a9ebb666f89ef788c012c2c5) Thanks [@sidharthv96](https://github.com/sidharthv96)! - chore: fix render types
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mermaid-js/layout-elk",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.5",
|
||||
"description": "ELK layout engine for mermaid",
|
||||
"module": "dist/mermaid-layout-elk.core.mjs",
|
||||
"types": "dist/layouts.d.ts",
|
||||
|
@@ -33,10 +33,11 @@ export const render = async (
|
||||
};
|
||||
graph.children.push(child);
|
||||
nodeDb[node.id] = child;
|
||||
const config = getConfig();
|
||||
|
||||
// Add the element to the DOM
|
||||
if (!node.isGroup) {
|
||||
const childNodeEl = await insertNode(nodeEl, node, node.dir);
|
||||
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
|
||||
boundingBox = childNodeEl.node().getBBox();
|
||||
child.domId = childNodeEl;
|
||||
child.width = boundingBox.width;
|
||||
@@ -50,7 +51,7 @@ export const render = async (
|
||||
// @ts-ignore TODO: fix this
|
||||
const { shapeSvg, bbox } = await labelHelper(nodeEl, node, undefined, true);
|
||||
labelData.width = bbox.width;
|
||||
labelData.wrappingWidth = getConfig().flowchart!.wrappingWidth;
|
||||
labelData.wrappingWidth = config.flowchart!.wrappingWidth;
|
||||
// Give some padding for elk
|
||||
labelData.height = bbox.height - 2;
|
||||
labelData.labelNode = shapeSvg.node();
|
||||
|
@@ -1,5 +1,35 @@
|
||||
# mermaid
|
||||
|
||||
## 11.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#5825](https://github.com/mermaid-js/mermaid/pull/5825) [`9e3aa70`](https://github.com/mermaid-js/mermaid/commit/9e3aa705ae21fd4898504ab22d775a9e437b898e) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - New Flowchart Shapes (with new syntax)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5849](https://github.com/mermaid-js/mermaid/pull/5849) [`6c5b7ce`](https://github.com/mermaid-js/mermaid/commit/6c5b7ce9f41c0fbd59fe03dbefc8418d97697f0a) Thanks [@ReneLombard](https://github.com/ReneLombard)! - Fixed an issue when the mermaid classdiagram crashes when adding a . to the namespace.
|
||||
Forexample
|
||||
|
||||
```mermaid
|
||||
|
||||
classDiagram
|
||||
namespace Company.Project.Module {
|
||||
class GenericClass~T~ {
|
||||
+addItem(item: T)
|
||||
+getItem() T
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [#5914](https://github.com/mermaid-js/mermaid/pull/5914) [`de2c05c`](https://github.com/mermaid-js/mermaid/commit/de2c05cd5463af68d19dd7b6b3f1303d69ddb2dd) Thanks [@aloisklink](https://github.com/aloisklink)! - Ban DOMPurify v3.1.7 as a dependency
|
||||
|
||||
## 11.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5856](https://github.com/mermaid-js/mermaid/pull/5856) [`bfd8c63`](https://github.com/mermaid-js/mermaid/commit/bfd8c63daaa8420e57da9953922b9f0c94123064) Thanks [@knsv](https://github.com/knsv)! - Fix for issue with calculation of label width when using in firefox
|
||||
|
||||
## 11.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "11.2.0",
|
||||
"version": "11.3.0",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@@ -77,7 +77,7 @@
|
||||
"d3-sankey": "^0.12.3",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.10",
|
||||
"dompurify": "^3.0.11",
|
||||
"dompurify": "^3.0.11 <3.1.7",
|
||||
"katex": "^0.16.9",
|
||||
"khroma": "^2.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
@@ -87,7 +87,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
||||
// insertCluster(clusters, graph.node(v));
|
||||
} else {
|
||||
log.info('Node - the non recursive path', v, node.id, node);
|
||||
await insertNode(nodes, graph.node(v), dir);
|
||||
await insertNode(nodes, graph.node(v), { config: siteConfig, dir });
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -1131,7 +1131,7 @@ const shapes = {
|
||||
|
||||
let nodeElems = {};
|
||||
|
||||
export const insertNode = async (elem, node, dir) => {
|
||||
export const insertNode = async (elem, node, renderOptions) => {
|
||||
let newEl;
|
||||
let el;
|
||||
|
||||
@@ -1144,9 +1144,9 @@ export const insertNode = async (elem, node, dir) => {
|
||||
target = node.linkTarget || '_blank';
|
||||
}
|
||||
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
|
||||
el = await shapes[node.shape](newEl, node, dir);
|
||||
el = await shapes[node.shape](newEl, node, renderOptions);
|
||||
} else {
|
||||
el = await shapes[node.shape](elem, node, dir);
|
||||
el = await shapes[node.shape](elem, node, renderOptions);
|
||||
newEl = el;
|
||||
}
|
||||
if (node.tooltip) {
|
||||
|
@@ -21,8 +21,9 @@ const config: RequiredDeep<MermaidConfig> = {
|
||||
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
|
||||
deterministicIDSeed: undefined,
|
||||
elk: {
|
||||
// mergeEdges is needed here to be considered
|
||||
mergeEdges: false,
|
||||
nodePlacementStrategy: 'SIMPLE',
|
||||
nodePlacementStrategy: 'BRANDES_KOEPF',
|
||||
},
|
||||
themeCSS: undefined,
|
||||
|
||||
|
@@ -124,7 +124,8 @@ async function calculateBlockSize(
|
||||
}
|
||||
|
||||
// Add the element to the DOM to size it
|
||||
const nodeEl = await insertNode(elem, node);
|
||||
const config = getConfig();
|
||||
const nodeEl = await insertNode(elem, node, { config });
|
||||
const boundingBox = nodeEl.node().getBBox();
|
||||
const obj = db.getBlock(node.id);
|
||||
obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
|
||||
@@ -138,7 +139,8 @@ export async function insertBlockPositioned(elem: any, block: Block, db: any) {
|
||||
// Add the element to the DOM to size it
|
||||
const obj = db.getBlock(node.id);
|
||||
if (obj.type !== 'space') {
|
||||
await insertNode(elem, node);
|
||||
const config = getConfig();
|
||||
await insertNode(elem, node, { config });
|
||||
block.intersect = node?.intersect;
|
||||
positionNode(node);
|
||||
}
|
||||
|
@@ -389,7 +389,6 @@ const setupToolTips = function (element: Element) {
|
||||
// @ts-ignore - getBoundingClientRect is not part of the d3 type definition
|
||||
const rect = this.getBoundingClientRect();
|
||||
|
||||
// @ts-expect-error - Incorrect types
|
||||
tooltipElem.transition().duration(200).style('opacity', '.9');
|
||||
tooltipElem
|
||||
.text(el.attr('title'))
|
||||
@@ -399,7 +398,6 @@ const setupToolTips = function (element: Element) {
|
||||
el.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
// @ts-expect-error - Incorrect types
|
||||
tooltipElem.transition().duration(500).style('opacity', 0);
|
||||
const el = select(this);
|
||||
el.classed('hover', false);
|
||||
|
@@ -14,6 +14,78 @@ describe('given a basic class diagram, ', function () {
|
||||
classDb.clear();
|
||||
parser.yy = classDb;
|
||||
});
|
||||
it('should handle classes within namespaces', () => {
|
||||
const str = `classDiagram
|
||||
namespace Company.Project {
|
||||
class User {
|
||||
+login(username: String, password: String)
|
||||
+logout()
|
||||
}
|
||||
}
|
||||
namespace Company.Project.Module {
|
||||
class Admin {
|
||||
+addUser(user: User)
|
||||
+removeUser(user: User)
|
||||
}
|
||||
}`;
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const user = classDb.getClass('User');
|
||||
const admin = classDb.getClass('Admin');
|
||||
|
||||
// Check if the classes are correctly registered under their respective namespaces
|
||||
expect(user.parent).toBe('Company.Project');
|
||||
expect(admin.parent).toBe('Company.Project.Module');
|
||||
expect(user.methods.length).toBe(2);
|
||||
expect(admin.methods.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle generic classes within namespaces', () => {
|
||||
const str = `classDiagram
|
||||
namespace Company.Project.Module {
|
||||
class GenericClass~T~ {
|
||||
+addItem(item: T)
|
||||
+getItem() T
|
||||
}
|
||||
}`;
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const genericClass = classDb.getClass('GenericClass');
|
||||
expect(genericClass.type).toBe('T');
|
||||
expect(genericClass.methods[0].getDisplayDetails().displayText).toBe('+addItem(item: T)');
|
||||
expect(genericClass.methods[1].getDisplayDetails().displayText).toBe('+getItem() : T');
|
||||
});
|
||||
|
||||
it('should handle nested namespaces and relationships', () => {
|
||||
const str = ` classDiagram
|
||||
namespace Company.Project.Module.SubModule {
|
||||
class Report {
|
||||
+generatePDF(data: List)
|
||||
+generateCSV(data: List)
|
||||
}
|
||||
}
|
||||
namespace Company.Project.Module {
|
||||
class Admin {
|
||||
+generateReport()
|
||||
}
|
||||
}
|
||||
Admin --> Report : generates`;
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const report = classDb.getClass('Report');
|
||||
const admin = classDb.getClass('Admin');
|
||||
const relations = classDb.getRelations();
|
||||
|
||||
expect(report.parent).toBe('Company.Project.Module.SubModule');
|
||||
expect(admin.parent).toBe('Company.Project.Module');
|
||||
expect(relations[0].id1).toBe('Admin');
|
||||
expect(relations[0].id2).toBe('Report');
|
||||
expect(relations[0].title).toBe('generates');
|
||||
});
|
||||
|
||||
it('should handle accTitle and accDescr', function () {
|
||||
const str = `classDiagram
|
||||
accTitle: My Title
|
||||
@@ -48,6 +120,22 @@ describe('given a basic class diagram, ', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle fully qualified class names in relationships', () => {
|
||||
const str = `classDiagram
|
||||
namespace Company.Project.Module {
|
||||
class User
|
||||
class Admin
|
||||
}
|
||||
Admin --> User : manages`;
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const relations = classDb.getRelations();
|
||||
expect(relations[0].id1).toBe('Admin');
|
||||
expect(relations[0].id2).toBe('User');
|
||||
expect(relations[0].title).toBe('manages');
|
||||
});
|
||||
|
||||
it('should handle backquoted class names', function () {
|
||||
const str = 'classDiagram\n' + 'class `Car`';
|
||||
|
||||
@@ -393,27 +481,23 @@ class C13["With Città foreign language"]
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides`);
|
||||
|
||||
const studentClass = classDb.getClasses().get('Student');
|
||||
// Check that the important properties match, but ignore the domId
|
||||
expect(studentClass).toMatchObject({
|
||||
id: 'Student',
|
||||
label: 'Student',
|
||||
members: [
|
||||
expect.objectContaining({
|
||||
id: 'idCard : IdCard',
|
||||
visibility: '-',
|
||||
}),
|
||||
],
|
||||
methods: [],
|
||||
annotations: [],
|
||||
cssClasses: [],
|
||||
});
|
||||
|
||||
expect(classDb.getClasses().size).toBe(3);
|
||||
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
|
||||
{
|
||||
"annotations": [],
|
||||
"cssClasses": [],
|
||||
"domId": "classId-Student-134",
|
||||
"id": "Student",
|
||||
"label": "Student",
|
||||
"members": [
|
||||
ClassMember {
|
||||
"classifier": "",
|
||||
"id": "idCard : IdCard",
|
||||
"memberType": "attribute",
|
||||
"visibility": "-",
|
||||
},
|
||||
],
|
||||
"methods": [],
|
||||
"styles": [],
|
||||
"type": "",
|
||||
}
|
||||
`);
|
||||
expect(classDb.getRelations().length).toBe(2);
|
||||
expect(classDb.getRelations()).toMatchInlineSnapshot(`
|
||||
[
|
||||
|
@@ -241,11 +241,13 @@ classLabel
|
||||
|
||||
namespaceName
|
||||
: alphaNumToken { $$=$1; }
|
||||
| alphaNumToken DOT namespaceName { $$=$1+'.'+$3; }
|
||||
| alphaNumToken namespaceName { $$=$1+$2; }
|
||||
;
|
||||
|
||||
className
|
||||
: alphaNumToken { $$=$1; }
|
||||
| alphaNumToken DOT className { $$=$1+'.'+$3; }
|
||||
| classLiteralName { $$=$1; }
|
||||
| alphaNumToken className { $$=$1+$2; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
|
||||
@@ -270,12 +272,12 @@ statement
|
||||
;
|
||||
|
||||
namespaceStatement
|
||||
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
|
||||
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
|
||||
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $3); }
|
||||
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $4); }
|
||||
;
|
||||
|
||||
namespaceIdentifier
|
||||
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
|
||||
: NAMESPACE namespaceName { $$=$2; yy.addNamespace($2); }
|
||||
;
|
||||
|
||||
classStatements
|
||||
|
@@ -4,6 +4,7 @@ import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import common from '../common/common.js';
|
||||
import type { Node, Edge } from '../../rendering-util/types.js';
|
||||
import { log } from '../../logger.js';
|
||||
import * as yaml from 'js-yaml';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
getDiagramTitle,
|
||||
} from '../common/commonDb.js';
|
||||
import type { FlowVertex, FlowClass, FlowSubGraph, FlowText, FlowEdge, FlowLink } from './types.js';
|
||||
import type { NodeMetaData } from '../../types.js';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'flowchart-';
|
||||
let vertexCounter = 0;
|
||||
@@ -60,8 +62,10 @@ export const addVertex = function (
|
||||
style: string[],
|
||||
classes: string[],
|
||||
dir: string,
|
||||
props = {}
|
||||
props = {},
|
||||
shapeData: any
|
||||
) {
|
||||
// console.log('addVertex', id, shapeData);
|
||||
if (!id || id.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -115,6 +119,59 @@ export const addVertex = function (
|
||||
} else if (props !== undefined) {
|
||||
Object.assign(vertex.props, props);
|
||||
}
|
||||
|
||||
if (shapeData !== undefined) {
|
||||
let yamlData;
|
||||
// detect if shapeData contains a newline character
|
||||
// console.log('shapeData', shapeData);
|
||||
if (!shapeData.includes('\n')) {
|
||||
// console.log('yamlData shapeData has no new lines', shapeData);
|
||||
yamlData = '{\n' + shapeData + '\n}';
|
||||
} else {
|
||||
// console.log('yamlData shapeData has new lines', shapeData);
|
||||
yamlData = shapeData + '\n';
|
||||
}
|
||||
// console.log('yamlData', yamlData);
|
||||
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
|
||||
if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) {
|
||||
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
|
||||
}
|
||||
|
||||
// console.log('yamlData doc', doc);
|
||||
if (doc?.shape) {
|
||||
vertex.type = doc?.shape;
|
||||
}
|
||||
if (doc?.label) {
|
||||
vertex.text = doc?.label;
|
||||
}
|
||||
if (doc?.icon) {
|
||||
vertex.icon = doc?.icon;
|
||||
if (!doc.label?.trim() && vertex.text === id) {
|
||||
vertex.text = '';
|
||||
}
|
||||
}
|
||||
if (doc?.form) {
|
||||
vertex.form = doc?.form;
|
||||
}
|
||||
if (doc?.pos) {
|
||||
vertex.pos = doc?.pos;
|
||||
}
|
||||
if (doc?.img) {
|
||||
vertex.img = doc?.img;
|
||||
if (!doc.label?.trim() && vertex.text === id) {
|
||||
vertex.text = '';
|
||||
}
|
||||
}
|
||||
if (doc?.constraint) {
|
||||
vertex.constraint = doc.constraint;
|
||||
}
|
||||
if (doc.w) {
|
||||
vertex.assetWidth = Number(doc.w);
|
||||
}
|
||||
if (doc.h) {
|
||||
vertex.assetHeight = Number(doc.h);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -425,7 +482,6 @@ const setupToolTips = function (element: Element) {
|
||||
}
|
||||
const rect = (this as Element)?.getBoundingClientRect();
|
||||
|
||||
// @ts-ignore TODO: fix this
|
||||
tooltipElem.transition().duration(200).style('opacity', '.9');
|
||||
tooltipElem
|
||||
.text(el.attr('title'))
|
||||
@@ -435,7 +491,6 @@ const setupToolTips = function (element: Element) {
|
||||
el.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
// @ts-ignore TODO: fix this
|
||||
tooltipElem.transition().duration(500).style('opacity', 0);
|
||||
const el = select(this);
|
||||
el.classed('hover', false);
|
||||
@@ -762,6 +817,21 @@ export const lex = {
|
||||
};
|
||||
|
||||
const getTypeFromVertex = (vertex: FlowVertex) => {
|
||||
if (vertex.img) {
|
||||
return 'imageSquare';
|
||||
}
|
||||
if (vertex.icon) {
|
||||
if (vertex.form === 'circle') {
|
||||
return 'iconCircle';
|
||||
}
|
||||
if (vertex.form === 'square') {
|
||||
return 'iconSquare';
|
||||
}
|
||||
if (vertex.form === 'rounded') {
|
||||
return 'iconRounded';
|
||||
}
|
||||
return 'icon';
|
||||
}
|
||||
if (vertex.type === 'square') {
|
||||
return 'squareRect';
|
||||
}
|
||||
@@ -827,6 +897,12 @@ const addNodeFromVertex = (
|
||||
link: vertex.link,
|
||||
linkTarget: vertex.linkTarget,
|
||||
tooltip: getTooltip(vertex.id),
|
||||
icon: vertex.icon,
|
||||
pos: vertex.pos,
|
||||
img: vertex.img,
|
||||
assetWidth: vertex.assetWidth,
|
||||
assetHeight: vertex.assetHeight,
|
||||
constraint: vertex.constraint,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@@ -0,0 +1,278 @@
|
||||
import flowDb from '../flowDb.js';
|
||||
import flow from './flow.jison';
|
||||
import { setConfig } from '../../../config.js';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('when parsing directions', function () {
|
||||
beforeEach(function () {
|
||||
flow.parser.yy = flowDb;
|
||||
flow.parser.yy.clear();
|
||||
flow.parser.yy.setGen('gen-2');
|
||||
});
|
||||
|
||||
it('should handle basic shape data statements', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded}`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
});
|
||||
it('should handle basic shape data statements', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded }`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
});
|
||||
|
||||
it('should handle basic shape data statements with &', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } & E`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(2);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should handle shape data statements with edges', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } --> E`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(2);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should handle basic shape data statements with amp and edges 1', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } & E --> F`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(3);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should handle basic shape data statements with amp and edges 2', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } & E@{ shape: rounded } --> F`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(3);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should handle basic shape data statements with amp and edges 3', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } & E@{ shape: rounded } --> F & G@{ shape: rounded }`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(4);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should handle basic shape data statements with amp and edges 4', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } & E@{ shape: rounded } --> F@{ shape: rounded } & G@{ shape: rounded }`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(4);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should handle basic shape data statements with amp and edges 5, trailing space', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded } & E@{ shape: rounded } --> F{ shape: rounded } & G{ shape: rounded } `);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(4);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].label).toEqual('E');
|
||||
});
|
||||
it('should no matter of there are no leading spaces', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{shape: rounded}`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
});
|
||||
|
||||
it('should no matter of there are many leading spaces', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded}`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
});
|
||||
|
||||
it('should be forgiving with many spaces before teh end', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded }`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('D');
|
||||
});
|
||||
it('should be possible to add multiple properties on the same line', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
D@{ shape: rounded , label: "DD"}`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('rounded');
|
||||
expect(data4Layout.nodes[0].label).toEqual('DD');
|
||||
});
|
||||
it('should be possible to link to a node with more data', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A --> D@{
|
||||
shape: circle
|
||||
other: "clock"
|
||||
}
|
||||
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(2);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('A');
|
||||
expect(data4Layout.nodes[1].label).toEqual('D');
|
||||
expect(data4Layout.nodes[1].shape).toEqual('circle');
|
||||
|
||||
expect(data4Layout.edges.length).toBe(1);
|
||||
});
|
||||
it('should not disturb adding multiple nodes after each other', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A[hello]
|
||||
B@{
|
||||
shape: circle
|
||||
other: "clock"
|
||||
}
|
||||
C[Hello]@{
|
||||
shape: circle
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(3);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('hello');
|
||||
expect(data4Layout.nodes[1].shape).toEqual('circle');
|
||||
expect(data4Layout.nodes[1].label).toEqual('B');
|
||||
expect(data4Layout.nodes[2].shape).toEqual('circle');
|
||||
expect(data4Layout.nodes[2].label).toEqual('Hello');
|
||||
});
|
||||
it('should use handle bracket end (}) character inside the shape data', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A@{
|
||||
label: "This is }"
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is }');
|
||||
});
|
||||
it('Diamond shapes should work as usual', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A{This is a label}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('diamond');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is a label');
|
||||
});
|
||||
it('Multi line strings should be supported', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A@{
|
||||
label: |
|
||||
This is a
|
||||
multiline string
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is a\nmultiline string\n');
|
||||
});
|
||||
it('Multi line strings should be supported', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A@{
|
||||
label: "This is a
|
||||
multiline string"
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is a<br/>multiline string');
|
||||
});
|
||||
it(' should be possible to use } in strings', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A@{
|
||||
label: "This is a string with }"
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is a string with }');
|
||||
});
|
||||
it(' should be possible to use @ in strings', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A@{
|
||||
label: "This is a string with @"
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is a string with @');
|
||||
});
|
||||
it(' should be possible to use @ in strings', function () {
|
||||
const res = flow.parser.parse(`flowchart TB
|
||||
A@{
|
||||
label: "This is a string with}"
|
||||
other: "clock"
|
||||
}
|
||||
`);
|
||||
|
||||
const data4Layout = flow.parser.yy.getData();
|
||||
expect(data4Layout.nodes.length).toBe(1);
|
||||
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
|
||||
expect(data4Layout.nodes[0].label).toEqual('This is a string with}');
|
||||
});
|
||||
});
|
@@ -23,6 +23,9 @@
|
||||
%x href
|
||||
%x callbackname
|
||||
%x callbackargs
|
||||
%x shapeData
|
||||
%x shapeDataStr
|
||||
%x shapeDataEndBracket
|
||||
|
||||
%%
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
@@ -34,6 +37,32 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multilin
|
||||
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||
// <acc_descr_multiline>.*[^\n]* { return "acc_descr_line"}
|
||||
|
||||
|
||||
\@\{ {
|
||||
// console.log('=> shapeData', yytext);
|
||||
this.pushState("shapeData"); yytext=""; return 'SHAPE_DATA' }
|
||||
<shapeData>["] {
|
||||
// console.log('=> shapeDataStr', yytext);
|
||||
this.pushState("shapeDataStr");
|
||||
return 'SHAPE_DATA';
|
||||
}
|
||||
<shapeDataStr>["] {
|
||||
// console.log('shapeData <==', yytext);
|
||||
this.popState(); return 'SHAPE_DATA'}
|
||||
<shapeDataStr>[^\"]+ {
|
||||
// console.log('shapeData', yytext);
|
||||
const re = /\n\s*/g;
|
||||
yytext = yytext.replace(re,"<br/>");
|
||||
return 'SHAPE_DATA'}
|
||||
<shapeData>[^}^"]+ {
|
||||
// console.log('shapeData', yytext);
|
||||
return 'SHAPE_DATA';
|
||||
}
|
||||
<shapeData>"}" {
|
||||
// console.log('<== root', yytext)
|
||||
this.popState();
|
||||
}
|
||||
|
||||
/*
|
||||
---interactivity command---
|
||||
'call' adds a callback to the specified node. 'call' can only be specified when
|
||||
@@ -49,10 +78,11 @@ Function arguments are optional: 'call <callbackname>()' simply executes 'callba
|
||||
<callbackargs>\) this.popState();
|
||||
<callbackargs>[^)]* return 'CALLBACKARGS';
|
||||
|
||||
|
||||
<md_string>[^`"]+ { return "MD_STR";}
|
||||
<md_string>[`]["] { this.popState();}
|
||||
<*>["][`] { this.begin("md_string");}
|
||||
<string>[^"]+ return "STR";
|
||||
<string>[^"]+ { return "STR"; }
|
||||
<string>["] this.popState();
|
||||
<*>["] this.pushState("string");
|
||||
"style" return 'STYLE';
|
||||
@@ -62,6 +92,8 @@ Function arguments are optional: 'call <callbackname>()' simply executes 'callba
|
||||
"classDef" return 'CLASSDEF';
|
||||
"class" return 'CLASS';
|
||||
|
||||
|
||||
|
||||
/*
|
||||
---interactivity command---
|
||||
'href' adds a link to the specified node. 'href' can only be specified when the
|
||||
@@ -356,23 +388,38 @@ statement
|
||||
|
||||
separator: NEWLINE | SEMI | EOF ;
|
||||
|
||||
shapeData:
|
||||
shapeData SHAPE_DATA
|
||||
{ $$ = $1 + $2; }
|
||||
| SHAPE_DATA
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
vertexStatement: vertexStatement link node
|
||||
{ /* console.warn('vs',$vertexStatement.stmt,$node); */ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
|
||||
vertexStatement: vertexStatement link node shapeData
|
||||
{ /* console.warn('vs shapeData',$vertexStatement.stmt,$node, $shapeData);*/ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
|
||||
| vertexStatement link node
|
||||
{ /*console.warn('vs',$vertexStatement.stmt,$node);*/ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
|
||||
| vertexStatement link node spaceList
|
||||
{ /* console.warn('vs',$vertexStatement.stmt,$node); */ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
|
||||
|node spaceList {/*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }}
|
||||
|node { /*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }}
|
||||
|node spaceList { /*console.warn('vertexStatement: node spaceList', $node);*/ $$ = {stmt: $node, nodes:$node }}
|
||||
|node shapeData {
|
||||
/*console.warn('vertexStatement: node shapeData', $node[0], $shapeData);*/
|
||||
yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData);
|
||||
$$ = {stmt: $node, nodes:$node, shapeData: $shapeData}
|
||||
}
|
||||
|node { /* console.warn('vertexStatement: single node', $node); */ $$ = {stmt: $node, nodes:$node }}
|
||||
;
|
||||
|
||||
node: styledVertex
|
||||
{ /* console.warn('nod', $styledVertex); */ $$ = [$styledVertex];}
|
||||
{ /*console.warn('nod', $styledVertex);*/ $$ = [$styledVertex];}
|
||||
| node shapeData spaceList AMP spaceList styledVertex
|
||||
{ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/ }
|
||||
| node spaceList AMP spaceList styledVertex
|
||||
{ $$ = $node.concat($styledVertex); /* console.warn('pip', $node[0], $styledVertex, $$); */ }
|
||||
{ $$ = $node.concat($styledVertex); /*console.warn('pip', $node[0], $styledVertex, $$);*/ }
|
||||
;
|
||||
|
||||
styledVertex: vertex
|
||||
{ /* console.warn('nod', $vertex); */ $$ = $vertex;}
|
||||
{ /* console.warn('nodc', $vertex);*/ $$ = $vertex;}
|
||||
| vertex STYLE_SEPARATOR idString
|
||||
{$$ = $vertex;yy.setClass($vertex,$idString)}
|
||||
;
|
||||
|
@@ -59,7 +59,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
stroke: ${options.nodeBorder};
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.rough-node .label text , .node .label text {
|
||||
.rough-node .label text , .node .label text, .image-shape .label, .icon-shape .label {
|
||||
text-anchor: middle;
|
||||
}
|
||||
// .flowchart-label .text-outer-tspan {
|
||||
@@ -75,13 +75,20 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.node .label {
|
||||
.rough-node .label,.node .label, .image-shape .label, .icon-shape .label {
|
||||
text-align: center;
|
||||
}
|
||||
.node.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.root .anchor path {
|
||||
fill: ${options.lineColor} !important;
|
||||
stroke-width: 0;
|
||||
stroke: ${options.lineColor};
|
||||
}
|
||||
|
||||
.arrowheadPath {
|
||||
fill: ${options.arrowheadColor};
|
||||
}
|
||||
@@ -151,6 +158,25 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
font-size: 18px;
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
|
||||
rect.text {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.icon-shape, .image-shape {
|
||||
background-color: ${options.edgeLabelBackground};
|
||||
p {
|
||||
background-color: ${options.edgeLabelBackground};
|
||||
padding: 2px;
|
||||
}
|
||||
rect {
|
||||
opacity: 0.5;
|
||||
background-color: ${options.edgeLabelBackground};
|
||||
fill: ${options.edgeLabelBackground};
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export default getStyles;
|
||||
|
@@ -11,6 +11,15 @@ export interface FlowVertex {
|
||||
styles: string[];
|
||||
text?: string;
|
||||
type?: string;
|
||||
icon?: string;
|
||||
form?: string;
|
||||
pos?: 't' | 'b';
|
||||
img?: string;
|
||||
assetWidth?: number;
|
||||
assetHeight?: number;
|
||||
defaultWidth?: number;
|
||||
imageAspectRatio?: number;
|
||||
constraint?: 'on' | 'off';
|
||||
}
|
||||
|
||||
export interface FlowText {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { parser } from './gantt.jison';
|
||||
import ganttDb from '../ganttDb.js';
|
||||
import { convert } from '../../../tests/util.js';
|
||||
import { vi } from 'vitest';
|
||||
import { vi, it } from 'vitest';
|
||||
const spyOn = vi.spyOn;
|
||||
const parserFnConstructor = (str) => {
|
||||
return () => {
|
||||
@@ -150,14 +149,14 @@ describe('when parsing a gantt diagram it', function () {
|
||||
expect(tasks[3].id).toEqual('d');
|
||||
expect(tasks[3].task).toEqual('task D');
|
||||
});
|
||||
it.each(convert`
|
||||
it.each`
|
||||
tags | milestone | done | crit | active
|
||||
${'milestone'} | ${true} | ${false} | ${false} | ${false}
|
||||
${'done'} | ${false} | ${true} | ${false} | ${false}
|
||||
${'crit'} | ${false} | ${false} | ${true} | ${false}
|
||||
${'active'} | ${false} | ${false} | ${false} | ${true}
|
||||
${'crit,milestone,done'} | ${true} | ${true} | ${true} | ${false}
|
||||
`)('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => {
|
||||
`('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => {
|
||||
const str =
|
||||
'gantt\n' +
|
||||
'dateFormat YYYY-MM-DD\n' +
|
||||
|
@@ -130,8 +130,8 @@ describe('pie', () => {
|
||||
expect(sections.get('bat')).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with negative decimal', () => {
|
||||
expect(async () => {
|
||||
it('should handle simple pie with negative decimal', async () => {
|
||||
await expect(async () => {
|
||||
await parser.parse(`pie
|
||||
"ash" : -60.67
|
||||
"bat" : 40.12
|
||||
|
@@ -2063,8 +2063,8 @@ participant Alice`;
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['__proto__', 'constructor'])('should allow %s as an actor name', function (prop) {
|
||||
expect(
|
||||
it.each(['__proto__', 'constructor'])('should allow %s as an actor name', async function (prop) {
|
||||
await expect(
|
||||
mermaidAPI.parse(`
|
||||
sequenceDiagram
|
||||
${prop}-->>A: Hello, how are you?`)
|
||||
|
@@ -329,7 +329,7 @@ export const fixLifeLineHeights = (diagram, actors, actorKeys, conf) => {
|
||||
const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||
const center = actor.x + actor.width / 2;
|
||||
const centerY = actorY + 5;
|
||||
const centerY = actorY + actor.height;
|
||||
|
||||
const boxplusLineGroup = elem.append('g').lower();
|
||||
var g = boxplusLineGroup;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="-mt-6 mb-8">
|
||||
<a
|
||||
href="https://www.producthunt.com/posts/mermaid-ai-2?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-ai-2"
|
||||
href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-whiteboard"
|
||||
target="_blank"
|
||||
><img
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=465568&theme=light"
|
||||
alt="Mermaid AI - Maximize your diagramming efficiency with Mermaid AI | Product Hunt"
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light"
|
||||
alt="Mermaid Whiteboard - Drag & Drop your Nodes with Mermaid's new Whiteboard! | Product Hunt"
|
||||
style="width: 250px; height: 54px"
|
||||
width="250"
|
||||
height="54"
|
||||
|
@@ -9,6 +9,8 @@ import Contributors from '../components/Contributors.vue';
|
||||
import HomePage from '../components/HomePage.vue';
|
||||
// @ts-ignore Type not available
|
||||
import TopBar from '../components/TopBar.vue';
|
||||
// @ts-ignore Type not available
|
||||
import ProductHuntBadge from '../components/ProductHuntBadge.vue';
|
||||
import { getRedirect } from './redirect.js';
|
||||
// @ts-ignore Type not available
|
||||
import 'uno.css';
|
||||
@@ -23,6 +25,7 @@ export default {
|
||||
return h(Theme.Layout, null, {
|
||||
// Keeping this as comment as it took a lot of time to figure out how to add a component to the top bar.
|
||||
'home-hero-before': () => h(TopBar),
|
||||
'home-hero-info-before': () => h(ProductHuntBadge),
|
||||
'home-features-after': () => h(HomePage),
|
||||
'doc-before': () => h(TopBar),
|
||||
});
|
||||
|
220
packages/mermaid/src/docs/adding-new-shape.md
Normal file
220
packages/mermaid/src/docs/adding-new-shape.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Custom SVG Shapes Library
|
||||
|
||||
This library provides a collection of custom SVG shapes, utilities, and helpers for generating diagram components. The shapes are designed to be used within an SVG container and include a variety of common and complex shapes.
|
||||
|
||||
## Overview
|
||||
|
||||
## Shape Helpers and Utilities
|
||||
|
||||
Before starting with shape creation, it's essential to familiarize yourself with the utilities provided in the `utils.ts` file from `packages/mermaid/src/rendering-util/rendering-elements/shapes/util.js`. These utilities are designed to assist with various aspects of SVG shape manipulation and ensure consistent and accurate rendering.
|
||||
|
||||
## Available Utilities
|
||||
|
||||
### 1. `labelHelper`
|
||||
|
||||
- **Purpose**: This function creates and inserts labels inside SVG shapes.
|
||||
- **Features**:
|
||||
- Handles both HTML labels and plain text.
|
||||
- Calculates the bounding box dimensions of the label.
|
||||
- Ensures proper positioning of labels within shapes.
|
||||
|
||||
### 2. `updateNodeBounds`
|
||||
|
||||
- **Purpose**: Updates the bounding box dimensions (width and height) of a node.
|
||||
- **Usage**:
|
||||
- Adjusts the size of the node to fit the content or shape.
|
||||
- Useful for ensuring that shapes resize appropriately based on their content.
|
||||
|
||||
### 3. `insertPolygonShape`
|
||||
|
||||
- **Purpose**: Inserts a polygon shape into an SVG container.
|
||||
- **Features**:
|
||||
- Handles the creation and insertion of complex polygonal shapes.
|
||||
- Configures the shape's appearance and positioning within the SVG container.
|
||||
|
||||
### 4. `getNodeClasses`
|
||||
|
||||
- **Purpose**: Returns the appropriate CSS classes for a node based on its configuration.
|
||||
- **Usage**:
|
||||
- Dynamically applies CSS classes to nodes for styling purposes.
|
||||
- Ensures that nodes adhere to the desired design and theme.
|
||||
|
||||
### 5. `createPathFromPoints`
|
||||
|
||||
- **Purpose**: Generates an SVG path string from an array of points.
|
||||
- **Usage**:
|
||||
- Converts a list of points into a smooth path.
|
||||
- Useful for creating custom shapes or paths within the SVG.
|
||||
|
||||
### 6. `generateFullSineWavePoints`
|
||||
|
||||
- **Purpose**: Generates points for a sine wave, useful for creating wavy-edged shapes.
|
||||
- **Usage**:
|
||||
- Facilitates the creation of shapes with wavy or sine-wave edges.
|
||||
- Can be used to add decorative or dynamic edges to shapes.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To utilize these utilities, simply import them from the `utils.ts` file into your shape creation script. These helpers will streamline the process of building and customizing SVG shapes, ensuring consistent results across your projects.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
labelHelper,
|
||||
updateNodeBounds,
|
||||
insertPolygonShape,
|
||||
getNodeClasses,
|
||||
createPathFromPoints,
|
||||
generateFullSineWavePoints,
|
||||
} from './utils.ts';
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here’s a basic example of how you might use some of these utilities:
|
||||
|
||||
```typescript
|
||||
import { labelHelper, insertPolygonShape } from './utils.ts';
|
||||
|
||||
const svgContainer = document.getElementById('svgContainer');
|
||||
|
||||
// Insert a polygon shape
|
||||
insertPolygonShape(svgContainer /* shape-specific parameters */);
|
||||
|
||||
// Create and insert a label inside the shape
|
||||
labelHelper(svgContainer /* label-specific parameters */);
|
||||
```
|
||||
|
||||
## Adding New Shapes
|
||||
|
||||
### 1. Create the Shape Function
|
||||
|
||||
To add a new shape:
|
||||
|
||||
- **Create the shape function**: Create a new file of name of the shape and export a function in the `shapes` directory that generates your shape. The file and function should follow the pattern used in existing shapes and return an SVG element.
|
||||
|
||||
- **Example**:
|
||||
|
||||
```typescript
|
||||
import { Node, RenderOptions } from '../../types.d.ts';
|
||||
|
||||
export const myNewShape = async (
|
||||
parent: SVGAElement,
|
||||
node: Node,
|
||||
renderOptions: RenderOptions
|
||||
) => {
|
||||
// Create your shape here
|
||||
const shape = parent.insert('g').attr('class', 'my-new-shape');
|
||||
// Add other elements or styles as needed
|
||||
return shape;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Register the Shape
|
||||
|
||||
- **Register the shape**: Add your shape to the `shapes` object in the main shapes module. This allows your shape to be recognized and used within the system.
|
||||
|
||||
- **Example**:
|
||||
|
||||
```typescript
|
||||
import { myNewShape } from './shapes/myNewShape';
|
||||
|
||||
const shapes = {
|
||||
...,
|
||||
'my-new-shape': myNewShape,
|
||||
// Shortened alias (if any).
|
||||
'm-nsh': myNewShape
|
||||
};
|
||||
```
|
||||
|
||||
# Shape Intersection Algorithms
|
||||
|
||||
This contains algorithms and utilities for calculating intersection points for various shapes in SVG. Arrow intersection points are crucial for accurately determining where arrows connect with shapes. Ensuring precise intersection points enhances the clarity and accuracy of flowcharts and diagrams.
|
||||
|
||||
## Shape Intersection Functions
|
||||
|
||||
### 1. `Ellipse`
|
||||
|
||||
Calculates the intersection points for an ellipse.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```javascript
|
||||
import intersectEllipse from './intersect-ellipse.js';
|
||||
|
||||
const intersection = intersectEllipse(node, rx, ry, point);
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `node`: The SVG node element.
|
||||
- `rx`: The x-radius of the ellipse.
|
||||
- `ry`: The y-radius of the ellipse.
|
||||
- `point`: The point from which the intersection is calculated.
|
||||
|
||||
### 2. `intersectRect`
|
||||
|
||||
Calculates the intersection points for a rectangle.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```javascript
|
||||
import intersectRect from './intersect-rect.js';
|
||||
|
||||
const intersection = intersectRect(node, point);
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `node`: The SVG node element.
|
||||
- `point`: The point from which the intersection is calculated.
|
||||
|
||||
### 3. `intersectPolygon`
|
||||
|
||||
Calculates the intersection points for a polygon.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```javascript
|
||||
import intersectPolygon from './intersect-polygon.js';
|
||||
|
||||
const intersection = intersectPolygon(node, polyPoints, point);
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `node`: The SVG node element.
|
||||
- `polyPoints`: Array of points defining the polygon.
|
||||
- `point`: The point from which the intersection is calculated.
|
||||
|
||||
## Cypress Tests
|
||||
|
||||
To ensure the robustness of the flowchart shapes, there are implementation of comprehensive Cypress test cases in `newShapes.spec.ts` file. These tests cover various aspects such as:
|
||||
|
||||
- **Shapes**: Testing new shapes like `bowTieRect`, `waveRectangle`, `trapezoidalPentagon`, etc.
|
||||
- **Looks**: Verifying shapes under different visual styles (`classic` and `handDrawn`).
|
||||
- **Directions**: Ensuring correct rendering in all flow directions of arrows :
|
||||
- `TB` `(Top -> Bottom)`
|
||||
- `BT` `(Bottom -> Top)`
|
||||
- `LR` `(Left -> Right)`
|
||||
- `RL` `(Right -> Left)`
|
||||
- **Labels**: Testing shapes with different labels, including:
|
||||
- No labels
|
||||
- Short labels
|
||||
- Very long labels
|
||||
- Markdown with `htmlLabels:true` and `htmlLabels:false`
|
||||
- **Styles**: Applying custom styles to shapes and verifying correct rendering.
|
||||
- **Class Definitions**: Using `classDef` to apply custom classes and testing their impact.
|
||||
|
||||
### Running the Tests
|
||||
|
||||
To run the Cypress tests, follow these steps:
|
||||
|
||||
1. Ensure you have all dependencies installed by running:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
2. Start the Cypress test runner:
|
||||
|
||||
```bash
|
||||
cypress open --env updateSnapshots=true
|
||||
|
||||
```
|
||||
|
||||
3. Select the test suite from the Cypress interface to run all the flowchart shape tests.
|
@@ -59,7 +59,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
|
||||
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅
|
||||
- [Mermerd](https://github.com/KarnerTh/mermerd)
|
||||
- [Slab](https://slab.com) ✅
|
||||
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-) ✅
|
||||
- [Swimm](https://docs.swimm.io/features/diagrams-and-charts) ✅
|
||||
- [NotesHub](https://noteshub.app) ✅
|
||||
- [Notion](https://notion.so) ✅
|
||||
- [Observable](https://observablehq.com/@observablehq/mermaid) ✅
|
||||
|
@@ -6,7 +6,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
|
||||
<br />
|
||||
|
||||
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid Whiteboard - Drag & Drop your Nodes with Mermaid's new Whiteboard! | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## About
|
||||
|
||||
@@ -16,17 +16,20 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
|
||||
- **Editor** - A web based editor for creating and editing Mermaid diagrams.
|
||||
|
||||
- **Visual Editor** - The Visual Editor enables users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options.
|
||||
- **Mermaid AI** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
|
||||
|
||||
- **AI Chat** - Use our embedded AI Chat to generate diagrams from natural language descriptions.
|
||||
- **Whiteboard** - A virtual whiteboard for creating and editing Mermaid diagrams.
|
||||
|
||||
- **Plugins** - A plugin system for extending the functionality of Mermaid.
|
||||
|
||||
Official Mermaid Chart plugins:
|
||||
|
||||
- [Mermaid Chart GPT](https://chat.openai.com/g/g-1IRFKwq4G-mermaid-chart)
|
||||
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)
|
||||
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart)
|
||||
- [JetBrains IDE](https://plugins.jetbrains.com/plugin/23043-mermaid-chart)
|
||||
- [Google Docs](https://gsuite.google.com/marketplace/app/mermaidchart/947683068472)
|
||||
- [Microsoft PowerPoint and Word](https://appsource.microsoft.com/en-us/product/office/WA200006214?tab=Overview)
|
||||
|
||||
Visit our [Plugins](https://www.mermaidchart.com/plugins) page for more information.
|
||||
|
@@ -1,5 +1,11 @@
|
||||
# Blog
|
||||
|
||||
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
|
||||
|
||||
24 September 2024 · 5 mins
|
||||
|
||||
Discover 30 new shapes in Mermaid flowcharts, offering enhanced clarity, customization, and versatility for more dynamic and expressive visualizations.
|
||||
|
||||
## [Introducing Architecture Diagrams in Mermaid](https://www.mermaidchart.com/blog/posts/mermaid-supports-architecture-diagrams/)
|
||||
|
||||
2 September 2024 · 2 mins
|
||||
|
@@ -153,6 +153,19 @@ block-beta
|
||||
|
||||
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
|
||||
|
||||
**Merging Blocks Horizontally:**
|
||||
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
|
||||
|
||||
```mermaid-example
|
||||
block-beta
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
end
|
||||
```
|
||||
|
||||
In this example, the width of the merged block dynamically adjusts to the width of the largest child block.
|
||||
|
||||
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
|
||||
|
||||
## 4. Block Varieties and Shapes
|
||||
|
@@ -194,6 +194,400 @@ flowchart TD
|
||||
id1(((This is the text in the circle)))
|
||||
```
|
||||
|
||||
## Expanded Node Shapes in Mermaid Flowcharts (v11.3.0+)
|
||||
|
||||
Mermaid introduces 30 new shapes to enhance the flexibility and precision of flowchart creation. These new shapes provide more options to represent processes, decisions, events, data storage visually, and other elements within your flowcharts, improving clarity and semantic meaning.
|
||||
|
||||
New Syntax for Shape Definition
|
||||
|
||||
Mermaid now supports a general syntax for defining shape types to accommodate the growing number of shapes. This syntax allows you to assign specific shapes to nodes using a clear and flexible format:
|
||||
|
||||
```
|
||||
A@{ shape: rect }
|
||||
```
|
||||
|
||||
This syntax creates a node A as a rectangle. It renders in the same way as `A["A"]`, or `A`.
|
||||
|
||||
### Complete List of New Shapes
|
||||
|
||||
Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases:
|
||||
|
||||
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||
| ------------------------------------- | ---------------------- | -------------- | ------------------------------ | -------------------------------------------------------------- |
|
||||
| **Process** | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
|
||||
| **Event** | Rounded Rectangle | `rounded` | Represents an event | `event` |
|
||||
| **Terminal Point** | Stadium | `stadium` | Terminal point | `terminal`, `pill` |
|
||||
| **Subprocess** | Framed Rectangle | `fr-rect` | Subprocess | `subprocess`,`subproc`, `framed-rectangle`, `subroutine` |
|
||||
| **Database** | Cylinder | `cyl` | Database storage | `db`, `database`, `cylinder` |
|
||||
| **Start** | Circle | `circle` | Starting point | `circ` |
|
||||
| **Odd** | Odd | `odd` | Odd shape | |
|
||||
| **Decision** | Diamond | `diam` | Decision-making step | `decision`, `diamond` |
|
||||
| **Prepare Conditional** | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
|
||||
| **Data Input/Output** | Lean Right | `lean-r` | Represents input or output | `lean-right`, `in-out` |
|
||||
| **Data Input/Output** | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
|
||||
| **Priority Action** | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid-bottom` |
|
||||
| **Manual Operation** | Trapezoid Base Top | `trap-t` | Represents a manual task | `manual`, `trapezoid-top` |
|
||||
| **Stop** | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
|
||||
| **Text Block** | Text Block | `text` | Text block | - |
|
||||
| **Card** | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
||||
| **Lined/Shaded Process** | Lined Rectangle | `lin-rect` | Lined process shape | `lined-rectangle`,`lined-process`, `lin-proc`,`shaded-process` |
|
||||
| **Start** | Small Circle | `sm-circ` | Small starting point | `start`, `small-circle` |
|
||||
| **Stop** | Framed Circle | `fr-circ` | Stop point | `stop`, `framed-circle` |
|
||||
| **Fork/Join** | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
|
||||
| **Collate** | Hourglass | `hourglass` | Represents a collate operation | `hourglass` |
|
||||
| **Comment** | Curly Brace | `brace` | Adds a comment | `comment`, `brace-l` |
|
||||
| **Comment Right** | Curly Brace | `brace-r` | Adds a comment | - |
|
||||
| **Comment with braces on both sides** | Curly Braces | `braces` | Adds a comment | - |
|
||||
| **Com Link** | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
||||
| **Document** | Document | `doc` | Represents a document | `doc`, `document` |
|
||||
| **Delay** | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
|
||||
| **Direct Access Storage** | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
|
||||
| **Disk Storage** | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
|
||||
| **Display** | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
|
||||
| **Divided Process** | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-rectangle`, `divided-process` |
|
||||
| **Extract** | Triangle | `tri` | Extraction process | `extract`, `triangle` |
|
||||
| **Internal Storage** | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
|
||||
| **Junction** | Filled Circle | `f-circ` | Junction point | `junction`, `filled-circle` |
|
||||
| **Lined Document** | Lined Document | `lin-doc` | Lined document | `lined-document` |
|
||||
| **Loop Limit** | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
|
||||
| **Manual File** | Flipped Triangle | `flip-tri` | Manual file operation | `manual-file`, `flipped-triangle` |
|
||||
| **Manual Input** | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
|
||||
| **Multi-Document** | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
|
||||
| **Multi-Process** | Stacked Rectangle | `st-rect` | Multiple processes | `procs`, `processes`, `stacked-rectangle` |
|
||||
| **Paper Tape** | Flag | `flag` | Paper tape | `paper-tape` |
|
||||
| **Stored Data** | Bow Tie Rectangle | `bow-rect` | Stored data | `stored-data`, `bow-tie-rectangle` |
|
||||
| **Summary** | Crossed Circle | `cross-circ` | Summary | `summary`, `crossed-circle` |
|
||||
| **Tagged Document** | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
|
||||
| **Tagged Process** | Tagged Rectangle | `tag-rect` | Tagged process | `tagged-rectangle`,`tag-proc`, `tagged-process` |
|
||||
|
||||
### Example Flowchart with New Shapes
|
||||
|
||||
Here’s an example flowchart that utilizes some of the newly introduced shapes:
|
||||
|
||||
```mermaid-example
|
||||
flowchart RL
|
||||
A@{ shape: manual-file, label: "File Handling"}
|
||||
B@{ shape: manual-input, label: "User Input"}
|
||||
C@{ shape: docs, label: "Multiple Documents"}
|
||||
D@{ shape: procs, label: "Process Automation"}
|
||||
E@{ shape: paper-tape, label: "Paper Records"}
|
||||
```
|
||||
|
||||
### Process
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: rect, label: "This is a process" }
|
||||
```
|
||||
|
||||
### Event
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: rounded, label: "This is an event" }
|
||||
```
|
||||
|
||||
### Terminal Point (Stadium)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: stadium, label: "Terminal point" }
|
||||
```
|
||||
|
||||
### Subprocess
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: subproc, label: "This is a subprocess" }
|
||||
```
|
||||
|
||||
### Database (Cylinder)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: cyl, label: "Database" }
|
||||
```
|
||||
|
||||
### Start (Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: circle, label: "Start" }
|
||||
```
|
||||
|
||||
### Odd
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: odd, label: "Odd shape" }
|
||||
```
|
||||
|
||||
### Decision (Diamond)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: diamond, label: "Decision" }
|
||||
```
|
||||
|
||||
### Prepare Conditional (Hexagon)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: hex, label: "Prepare conditional" }
|
||||
```
|
||||
|
||||
### Data Input/Output (Lean Right)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lean-r, label: "Input/Output" }
|
||||
```
|
||||
|
||||
### Data Input/Output (Lean Left)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lean-l, label: "Output/Input" }
|
||||
```
|
||||
|
||||
### Priority Action (Trapezoid Base Bottom)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: trap-b, label: "Priority action" }
|
||||
```
|
||||
|
||||
### Manual Operation (Trapezoid Base Top)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: trap-t, label: "Manual operation" }
|
||||
```
|
||||
|
||||
### Stop (Double Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: dbl-circ, label: "Stop" }
|
||||
```
|
||||
|
||||
### Text Block
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: text, label: "This is a text block" }
|
||||
```
|
||||
|
||||
### Card (Notched Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: notch-rect, label: "Card" }
|
||||
```
|
||||
|
||||
### Lined/Shaded Process
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lin-rect, label: "Lined process" }
|
||||
```
|
||||
|
||||
### Start (Small Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: sm-circ, label: "Small start" }
|
||||
```
|
||||
|
||||
### Stop (Framed Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: framed-circle, label: "Stop" }
|
||||
```
|
||||
|
||||
### Fork/Join (Long Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: fork, label: "Fork or Join" }
|
||||
```
|
||||
|
||||
### Collate (Hourglass)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: hourglass, label: "Collate" }
|
||||
```
|
||||
|
||||
### Comment (Curly Brace)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: comment, label: "Comment" }
|
||||
```
|
||||
|
||||
### Comment Right (Curly Brace Right)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: brace-r, label: "Comment" }
|
||||
```
|
||||
|
||||
### Comment with braces on both sides
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: braces, label: "Comment" }
|
||||
```
|
||||
|
||||
### Com Link (Lightning Bolt)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: bolt, label: "Communication link" }
|
||||
```
|
||||
|
||||
### Document
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: doc, label: "Document" }
|
||||
```
|
||||
|
||||
### Delay (Half-Rounded Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: delay, label: "Delay" }
|
||||
```
|
||||
|
||||
### Direct Access Storage (Horizontal Cylinder)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: das, label: "Direct access storage" }
|
||||
```
|
||||
|
||||
### Disk Storage (Lined Cylinder)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lin-cyl, label: "Disk storage" }
|
||||
```
|
||||
|
||||
### Display (Curved Trapezoid)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: curv-trap, label: "Display" }
|
||||
```
|
||||
|
||||
### Divided Process (Divided Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: div-rect, label: "Divided process" }
|
||||
```
|
||||
|
||||
### Extract (Small Triangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: tri, label: "Extract" }
|
||||
```
|
||||
|
||||
### Internal Storage (Window Pane)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: win-pane, label: "Internal storage" }
|
||||
```
|
||||
|
||||
### Junction (Filled Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: f-circ, label: "Junction" }
|
||||
```
|
||||
|
||||
### Lined Document
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: lin-doc, label: "Lined document" }
|
||||
```
|
||||
|
||||
### Loop Limit (Notched Pentagon)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: notch-pent, label: "Loop limit" }
|
||||
```
|
||||
|
||||
### Manual File (Flipped Triangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: flip-tri, label: "Manual file" }
|
||||
```
|
||||
|
||||
### Manual Input (Sloped Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: sl-rect, label: "Manual input" }
|
||||
```
|
||||
|
||||
### Multi-Document (Stacked Document)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: docs, label: "Multiple documents" }
|
||||
```
|
||||
|
||||
### Multi-Process (Stacked Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: processes, label: "Multiple processes" }
|
||||
```
|
||||
|
||||
### Paper Tape (Flag)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: flag, label: "Paper tape" }
|
||||
```
|
||||
|
||||
### Stored Data (Bow Tie Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: bow-rect, label: "Stored data" }
|
||||
```
|
||||
|
||||
### Summary (Crossed Circle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: cross-circ, label: "Summary" }
|
||||
```
|
||||
|
||||
### Tagged Document
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: tag-doc, label: "Tagged document" }
|
||||
```
|
||||
|
||||
### Tagged Process (Tagged Rectangle)
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
A@{ shape: tag-rect, label: "Tagged process" }
|
||||
```
|
||||
|
||||
## Links between nodes
|
||||
|
||||
Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link.
|
||||
|
@@ -105,6 +105,9 @@ end
|
||||
box rgb(33,66,99)
|
||||
... actors ...
|
||||
end
|
||||
box rgba(33,66,99,0.5)
|
||||
... actors ...
|
||||
end
|
||||
```
|
||||
|
||||
```note
|
||||
@@ -394,6 +397,12 @@ sequenceDiagram
|
||||
|
||||
It is possible to highlight flows by providing colored background rects. This is done by the notation
|
||||
|
||||
```
|
||||
rect COLOR
|
||||
... content ...
|
||||
end
|
||||
```
|
||||
|
||||
The colors are defined using rgb and rgba syntax.
|
||||
|
||||
```
|
||||
|
@@ -20,6 +20,11 @@ function applyStyle(dom, styleFn) {
|
||||
|
||||
async function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
||||
const fo = element.append('foreignObject');
|
||||
// This is not the final width but used in order to make sure the foreign
|
||||
// object in firefox gets a width at all. The final width is fetched from the div
|
||||
fo.attr('width', `${10 * width}px`);
|
||||
fo.attr('height', `${10 * width}px`);
|
||||
|
||||
const div = fo.append('xhtml:div');
|
||||
let label = node.label;
|
||||
if (node.label && hasKatex(node.label)) {
|
||||
@@ -136,8 +141,8 @@ function createFormattedText(
|
||||
const bbox = textElement.node().getBBox();
|
||||
const padding = 2;
|
||||
bkg
|
||||
.attr('x', -padding)
|
||||
.attr('y', -padding)
|
||||
.attr('x', bbox.x - padding)
|
||||
.attr('y', bbox.y - padding)
|
||||
.attr('width', bbox.width + 2 * padding)
|
||||
.attr('height', bbox.height + 2 * padding);
|
||||
|
||||
@@ -201,7 +206,7 @@ export const createText = async (
|
||||
} = {},
|
||||
config: MermaidConfig
|
||||
) => {
|
||||
log.info(
|
||||
log.debug(
|
||||
'XYZ createText',
|
||||
text,
|
||||
style,
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { select } from 'd3';
|
||||
import { insertNode } from '../dagre-wrapper/nodes.js';
|
||||
|
||||
export const getDiagramElement = (id, securityLevel) => {
|
||||
let sandboxElement;
|
||||
@@ -17,36 +16,3 @@ export const getDiagramElement = (id, securityLevel) => {
|
||||
|
||||
return svg;
|
||||
};
|
||||
|
||||
export function insertElementsForSize(el, data) {
|
||||
const nodesElem = el.insert('g').attr('class', 'nodes');
|
||||
el.insert('g').attr('class', 'edges');
|
||||
data.nodes.forEach(async (item) => {
|
||||
item.shape = 'rect';
|
||||
await insertNode(nodesElem, {
|
||||
...item,
|
||||
class: 'default flowchart-label',
|
||||
labelStyle: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
height: 100,
|
||||
shape: 'rect',
|
||||
padding: 8,
|
||||
});
|
||||
// Create a new DOM element
|
||||
// const element = document.createElement('div');
|
||||
|
||||
// // Set the content of the element to the name of the item
|
||||
// element.textContent = item.name;
|
||||
|
||||
// // Set the size of the element to the size of the item
|
||||
// element.style.width = `${item.size}px`;
|
||||
// element.style.height = `${item.size}px`;
|
||||
|
||||
// Append the element to the body of the document
|
||||
// document.body.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
@@ -125,7 +125,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
||||
// insertCluster(clusters, graph.node(v));
|
||||
} else {
|
||||
log.trace('Node - the non recursive path XAX', v, nodes, graph.node(v), dir);
|
||||
await insertNode(nodes, graph.node(v), dir);
|
||||
await insertNode(nodes, graph.node(v), { config: siteConfig, dir });
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -9,7 +9,6 @@ import { choice } from './shapes/choice.ts';
|
||||
import { note } from './shapes/note.ts';
|
||||
import { stadium } from './shapes/stadium.js';
|
||||
import { rectWithTitle } from './shapes/rectWithTitle.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import { subroutine } from './shapes/subroutine.js';
|
||||
import { cylinder } from './shapes/cylinder.js';
|
||||
import { circle } from './shapes/circle.js';
|
||||
@@ -17,44 +16,299 @@ import { doublecircle } from './shapes/doubleCircle.js';
|
||||
import { rect_left_inv_arrow } from './shapes/rectLeftInvArrow.js';
|
||||
import { question } from './shapes/question.js';
|
||||
import { hexagon } from './shapes/hexagon.js';
|
||||
import { text } from './shapes/text.js';
|
||||
import { card } from './shapes/card.js';
|
||||
import { shadedProcess } from './shapes/shadedProcess.js';
|
||||
import { anchor } from './shapes/anchor.js';
|
||||
import { lean_right } from './shapes/leanRight.js';
|
||||
import { lean_left } from './shapes/leanLeft.js';
|
||||
import { trapezoid } from './shapes/trapezoid.js';
|
||||
import { inv_trapezoid } from './shapes/invertedTrapezoid.js';
|
||||
import { labelRect } from './shapes/labelRect.js';
|
||||
import { triangle } from './shapes/triangle.js';
|
||||
import { halfRoundedRectangle } from './shapes/halfRoundedRectangle.js';
|
||||
import { curvedTrapezoid } from './shapes/curvedTrapezoid.js';
|
||||
import { slopedRect } from './shapes/slopedRect.js';
|
||||
import { bowTieRect } from './shapes/bowTieRect.js';
|
||||
import { dividedRectangle } from './shapes/dividedRect.js';
|
||||
import { crossedCircle } from './shapes/crossedCircle.js';
|
||||
import { waveRectangle } from './shapes/waveRectangle.js';
|
||||
import { tiltedCylinder } from './shapes/tiltedCylinder.js';
|
||||
import { trapezoidalPentagon } from './shapes/trapezoidalPentagon.js';
|
||||
import { flippedTriangle } from './shapes/flippedTriangle.js';
|
||||
import { hourglass } from './shapes/hourglass.js';
|
||||
import { taggedRect } from './shapes/taggedRect.js';
|
||||
import { multiRect } from './shapes/multiRect.js';
|
||||
import { linedCylinder } from './shapes/linedCylinder.js';
|
||||
import { waveEdgedRectangle } from './shapes/waveEdgedRectangle.js';
|
||||
import { lightningBolt } from './shapes/lightningBolt.js';
|
||||
import { filledCircle } from './shapes/filledCircle.js';
|
||||
import { multiWaveEdgedRectangle } from './shapes/multiWaveEdgedRectangle.js';
|
||||
import { windowPane } from './shapes/windowPane.js';
|
||||
import { linedWaveEdgedRect } from './shapes/linedWaveEdgedRect.js';
|
||||
import { taggedWaveEdgedRectangle } from './shapes/taggedWaveEdgedRectangle.js';
|
||||
import { curlyBraceLeft } from './shapes/curlyBraceLeft.js';
|
||||
import { curlyBraceRight } from './shapes/curlyBraceRight.js';
|
||||
import { curlyBraces } from './shapes/curlyBraces.js';
|
||||
import { iconSquare } from './shapes/iconSquare.js';
|
||||
import { iconCircle } from './shapes/iconCircle.js';
|
||||
import { icon } from './shapes/icon.js';
|
||||
import { imageSquare } from './shapes/imageSquare.js';
|
||||
import { iconRounded } from './shapes/iconRounded.js';
|
||||
|
||||
const shapes = {
|
||||
//Use these names as the left side to render shapes.
|
||||
export const shapes = {
|
||||
// States
|
||||
state,
|
||||
stateStart,
|
||||
stateEnd,
|
||||
fork: forkJoin,
|
||||
join: forkJoin,
|
||||
forkJoin,
|
||||
choice,
|
||||
note,
|
||||
roundedRect,
|
||||
|
||||
// Rectangles
|
||||
rectWithTitle,
|
||||
roundedRect,
|
||||
squareRect,
|
||||
|
||||
// Rectangle with alias: 'process', 'rect', 'proc', 'rectangle'
|
||||
rectangle: squareRect,
|
||||
rect: squareRect,
|
||||
process: squareRect,
|
||||
proc: squareRect,
|
||||
|
||||
// Rounded Rectangle with alias: 'event', 'rounded'
|
||||
rounded: roundedRect,
|
||||
event: roundedRect,
|
||||
|
||||
// Stadium with alias: 'terminal','pill', 'stadium'
|
||||
stadium,
|
||||
pill: stadium,
|
||||
terminal: stadium,
|
||||
|
||||
// Subprocess with alias: 'fr-rect', 'subproc', 'subprocess', 'framed-rectangle', 'subroutine'
|
||||
subroutine,
|
||||
'framed-rectangle': subroutine,
|
||||
'fr-rect': subroutine,
|
||||
subprocess: subroutine,
|
||||
subproc: subroutine,
|
||||
|
||||
// Cylinder with alias: 'db', 'database', 'cylinder', 'cyl'
|
||||
cylinder,
|
||||
circle,
|
||||
doublecircle,
|
||||
odd: rect_left_inv_arrow,
|
||||
db: cylinder,
|
||||
cyl: cylinder,
|
||||
database: cylinder,
|
||||
|
||||
// Diamond with alias: 'diam', 'decision', 'diamond'
|
||||
question,
|
||||
diam: question,
|
||||
diamond: question,
|
||||
decision: question,
|
||||
|
||||
// Hexagon with alias: 'hex', 'hexagon', 'prepare'
|
||||
hexagon,
|
||||
lean_right,
|
||||
lean_left,
|
||||
trapezoid,
|
||||
inv_trapezoid,
|
||||
hex: hexagon,
|
||||
prepare: hexagon,
|
||||
|
||||
// Lean Right with alias: 'lean-r', 'lean-right', 'in-out'
|
||||
lean_right, // used in old syntax for flowchart
|
||||
'lean-r': lean_right,
|
||||
'lean-right': lean_right,
|
||||
'in-out': lean_right,
|
||||
|
||||
// Lean Left with alias: 'lean-l', 'lean-left', 'out-in'
|
||||
lean_left, // used in old syntax for flowchart
|
||||
'lean-l': lean_left,
|
||||
'lean-left': lean_left,
|
||||
'out-in': lean_left,
|
||||
|
||||
// Trapezoid with alias: 'trap-b', 'trapezoid-bottom', 'priority'
|
||||
trapezoid, // used in old syntax for flowchart
|
||||
'trap-b': trapezoid,
|
||||
'trapezoid-bottom': trapezoid,
|
||||
priority: trapezoid,
|
||||
|
||||
// Inverted Trapezoid with alias: 'inv-trapezoid', 'trapezoid-top', 'trap-t', 'manual'
|
||||
inv_trapezoid, // used in old syntax for flowchart
|
||||
'inv-trapezoid': inv_trapezoid,
|
||||
'trapezoid-top': inv_trapezoid,
|
||||
'trap-t': inv_trapezoid,
|
||||
manual: inv_trapezoid,
|
||||
|
||||
// Double Circle with alias: 'dbl-circ', 'double-circle'
|
||||
doublecircle, // used in old syntax for flowchart
|
||||
'dbl-circ': doublecircle,
|
||||
'double-circle': doublecircle,
|
||||
|
||||
// circle with alias: 'circ', 'circle'
|
||||
circle,
|
||||
circ: circle,
|
||||
|
||||
// Rect Left Inv Arrow with alias: 'odd', 'rect-left-inv-arrow'
|
||||
rect_left_inv_arrow,
|
||||
odd: rect_left_inv_arrow,
|
||||
|
||||
// Notched Rectangle with alias: 'notched-rectangle', 'notch-rect', 'card'
|
||||
card,
|
||||
'notched-rectangle': card,
|
||||
'notch-rect': card,
|
||||
|
||||
// Lined rectangle with alias: 'lin-rect', 'lined-rectangle', 'lin-proc', lined-process', 'shaded-process'
|
||||
'lined-rectangle': shadedProcess,
|
||||
'lin-rect': shadedProcess,
|
||||
'lin-proc': shadedProcess,
|
||||
'lined-process': shadedProcess,
|
||||
'shaded-process': shadedProcess,
|
||||
|
||||
// Small circle with alias: 'sm-circ', 'small-circle', 'start'
|
||||
'small-circle': stateStart,
|
||||
'sm-circ': stateStart,
|
||||
start: stateStart,
|
||||
|
||||
// framed circle with alias: 'stop', 'framed-circle', 'fr-circ'
|
||||
stop: stateEnd,
|
||||
'framed-circle': stateEnd,
|
||||
'fr-circ': stateEnd,
|
||||
|
||||
// fork with alias: 'join', 'fork'
|
||||
join: forkJoin,
|
||||
fork: forkJoin,
|
||||
|
||||
// comment with alias: 'comment', 'brace-l'
|
||||
comment: curlyBraceLeft,
|
||||
'brace-l': curlyBraceLeft,
|
||||
|
||||
// lightening bolt with alias: 'bolt', 'com-link', 'lightning-bolt'
|
||||
bolt: lightningBolt,
|
||||
'com-link': lightningBolt,
|
||||
'lightning-bolt': lightningBolt,
|
||||
|
||||
// document with alias: 'doc', 'document'
|
||||
doc: waveEdgedRectangle,
|
||||
document: waveEdgedRectangle,
|
||||
|
||||
// delay with alias: 'delay', 'half-rounded-rectangle'
|
||||
delay: halfRoundedRectangle,
|
||||
'half-rounded-rectangle': halfRoundedRectangle,
|
||||
|
||||
// horizontal cylinder with alias: 'h-cyl', 'das', 'horizontal-cylinder'
|
||||
'horizontal-cylinder': tiltedCylinder,
|
||||
'h-cyl': tiltedCylinder,
|
||||
das: tiltedCylinder,
|
||||
|
||||
// lined cylinder with alias: 'lin-cyl', 'lined-cylinder', 'disk'
|
||||
'lined-cylinder': linedCylinder,
|
||||
'lin-cyl': linedCylinder,
|
||||
disk: linedCylinder,
|
||||
|
||||
// curved trapezoid with alias: 'curv-trap', 'curved-trapezoid', 'display'
|
||||
'curved-trapezoid': curvedTrapezoid,
|
||||
'curv-trap': curvedTrapezoid,
|
||||
display: curvedTrapezoid,
|
||||
|
||||
// divided rectangle with alias: 'div-rect', 'divided-rectangle', 'div-proc', 'divided-process'
|
||||
'divided-rectangle': dividedRectangle,
|
||||
'div-rect': dividedRectangle,
|
||||
'div-proc': dividedRectangle,
|
||||
'divided-process': dividedRectangle,
|
||||
|
||||
// triangle with alias: 'tri', 'triangle', 'extract'
|
||||
triangle,
|
||||
tri: triangle,
|
||||
extract: triangle,
|
||||
|
||||
// window pane with alias: 'window-pane', 'win-pane', 'internal-storage'
|
||||
'window-pane': windowPane,
|
||||
'win-pane': windowPane,
|
||||
'internal-storage': windowPane,
|
||||
|
||||
// filled circle with alias: 'f-circ', 'filled-circle', 'junction'
|
||||
'f-circ': filledCircle,
|
||||
junction: filledCircle,
|
||||
'filled-circle': filledCircle,
|
||||
|
||||
// lined document with alias: 'lin-doc', 'lined-document'
|
||||
'lin-doc': linedWaveEdgedRect,
|
||||
'lined-document': linedWaveEdgedRect,
|
||||
|
||||
// notched pentagon with alias: 'notch-pent', 'notched-pentagon', 'loop-limit'
|
||||
'notched-pentagon': trapezoidalPentagon,
|
||||
'notch-pent': trapezoidalPentagon,
|
||||
'loop-limit': trapezoidalPentagon,
|
||||
|
||||
// flipped triangle with alias: 'flip-tri', 'flipped-triangle', 'manual-file'
|
||||
'flipped-triangle': flippedTriangle,
|
||||
'flip-tri': flippedTriangle,
|
||||
'manual-file': flippedTriangle,
|
||||
|
||||
// sloped rectangle with alias: 'sl-rect', 'sloped-rectangle', 'manual-input'
|
||||
'sloped-rectangle': slopedRect,
|
||||
'sl-rect': slopedRect,
|
||||
'manual-input': slopedRect,
|
||||
|
||||
// documents with alias: 'docs', 'documents', 'st-doc', 'stacked-document'
|
||||
docs: multiWaveEdgedRectangle,
|
||||
documents: multiWaveEdgedRectangle,
|
||||
'st-doc': multiWaveEdgedRectangle,
|
||||
'stacked-document': multiWaveEdgedRectangle,
|
||||
|
||||
// 'processes' with alias: 'procs', 'processes', 'st-rect', 'stacked-rectangle'
|
||||
processes: multiRect,
|
||||
procs: multiRect,
|
||||
'stacked-rectangle': multiRect,
|
||||
'st-rect': multiRect,
|
||||
|
||||
// flag with alias: 'flag', 'paper-tape'
|
||||
flag: waveRectangle,
|
||||
'paper-tape': waveRectangle,
|
||||
|
||||
// bow tie rectangle with alias: 'bow-rect', 'bow-tie-rectangle', 'stored-data'
|
||||
'bow-tie-rectangle': bowTieRect,
|
||||
'bow-rect': bowTieRect,
|
||||
'stored-data': bowTieRect,
|
||||
|
||||
// crossed circle with alias: 'cross-circ', 'crossed-circle', 'summary'
|
||||
'crossed-circle': crossedCircle,
|
||||
'cross-circ': crossedCircle,
|
||||
summary: crossedCircle,
|
||||
|
||||
// tagged document with alias: 'tag-doc', 'tagged-document'
|
||||
'tag-doc': taggedWaveEdgedRectangle,
|
||||
'tagged-document': taggedWaveEdgedRectangle,
|
||||
|
||||
// tagged rectangle with alias: 'tag-rect', 'tagged-rectangle', 'tag-proc', 'tagged-process'
|
||||
'tag-rect': taggedRect,
|
||||
'tagged-rectangle': taggedRect,
|
||||
'tag-proc': taggedRect,
|
||||
'tagged-process': taggedRect,
|
||||
|
||||
// hourglass with alias: 'hourglass', 'collate'
|
||||
hourglass,
|
||||
collate: hourglass,
|
||||
|
||||
text,
|
||||
anchor,
|
||||
|
||||
brace: curlyBraceLeft,
|
||||
|
||||
labelRect,
|
||||
'brace-r': curlyBraceRight,
|
||||
braces: curlyBraces,
|
||||
iconSquare,
|
||||
iconCircle,
|
||||
icon,
|
||||
iconRounded,
|
||||
imageSquare,
|
||||
};
|
||||
|
||||
const nodeElems = new Map();
|
||||
|
||||
export const insertNode = async (elem, node, dir) => {
|
||||
export const insertNode = async (elem, node, renderOptions) => {
|
||||
let newEl;
|
||||
let el;
|
||||
|
||||
// console.log("node is ", node.icon, node.shape)
|
||||
|
||||
//special check for rect shape (with or without rounded corners)
|
||||
if (node.shape === 'rect') {
|
||||
if (node.rx && node.ry) {
|
||||
@@ -64,18 +318,22 @@ export const insertNode = async (elem, node, dir) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Add link when appropriate
|
||||
if (!shapes[node.shape]) {
|
||||
throw new Error(`No such shape: ${node.shape}. Please check your syntax.`);
|
||||
}
|
||||
|
||||
if (node.link) {
|
||||
// Add link when appropriate
|
||||
let target;
|
||||
if (getConfig().securityLevel === 'sandbox') {
|
||||
if (renderOptions.config.securityLevel === 'sandbox') {
|
||||
target = '_top';
|
||||
} else if (node.linkTarget) {
|
||||
target = node.linkTarget || '_blank';
|
||||
}
|
||||
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
|
||||
el = await shapes[node.shape](newEl, node, dir);
|
||||
el = await shapes[node.shape](newEl, node, renderOptions);
|
||||
} else {
|
||||
el = await shapes[node.shape](elem, node, dir);
|
||||
el = await shapes[node.shape](elem, node, renderOptions);
|
||||
newEl = el;
|
||||
}
|
||||
if (node.tooltip) {
|
||||
|
@@ -0,0 +1,236 @@
|
||||
import exp from 'constants';
|
||||
import { shapes } from './nodes.js';
|
||||
|
||||
describe('Test Alias for shapes', function () {
|
||||
// for each shape in docs/syntax/flowchart.md, along with its semantic name, short name, and alias name, add a test case
|
||||
// Process | rect | proc | rectangle
|
||||
it('should support alias for rectangle shape ', function () {
|
||||
expect(shapes.process).toBe(shapes.rect);
|
||||
expect(shapes.proc).toBe(shapes.rect);
|
||||
expect(shapes.rectangle).toBe(shapes.rect);
|
||||
});
|
||||
|
||||
// event | rounded
|
||||
it('should support alias for rounded shape ', function () {
|
||||
expect(shapes.event).toBe(shapes.rounded);
|
||||
});
|
||||
|
||||
// stadium | pill | term
|
||||
it('should support alias for stadium shape ', function () {
|
||||
expect(shapes.pill).toBe(shapes.stadium);
|
||||
expect(shapes.terminal).toBe(shapes.stadium);
|
||||
});
|
||||
|
||||
// fr-rect | subproc | framed-rectangle | subroutine
|
||||
it('should support alias for subroutine shape ', function () {
|
||||
expect(shapes['framed-rectangle']).toBe(shapes['fr-rect']);
|
||||
expect(shapes.subproc).toBe(shapes['fr-rect']);
|
||||
expect(shapes.subroutine).toBe(shapes['fr-rect']);
|
||||
});
|
||||
|
||||
// cyl | db | cylinder
|
||||
it('should support alias for cylinder shape ', function () {
|
||||
expect(shapes.db).toBe(shapes.cylinder);
|
||||
expect(shapes.cyl).toBe(shapes.cylinder);
|
||||
});
|
||||
|
||||
// diam | decision | diamond
|
||||
it('should support alias for diamond shape ', function () {
|
||||
expect(shapes.diam).toBe(shapes.decision);
|
||||
expect(shapes.diamond).toBe(shapes.decision);
|
||||
});
|
||||
|
||||
// hex | hexagon | prepare
|
||||
it('should support alias for hexagon shape ', function () {
|
||||
expect(shapes.hex).toBe(shapes.hexagon);
|
||||
expect(shapes.prepare).toBe(shapes.hexagon);
|
||||
});
|
||||
|
||||
// l-r | lean-right | in-out
|
||||
it('should support alias for lean-right shape ', function () {
|
||||
expect(shapes['lean-r']).toBe(shapes['lean-right']);
|
||||
expect(shapes['in-out']).toBe(shapes['lean-right']);
|
||||
});
|
||||
|
||||
// l-l | lean-left | out-in
|
||||
it('should support alias for lean-left shape ', function () {
|
||||
expect(shapes['lean-l']).toBe(shapes['lean-left']);
|
||||
expect(shapes['out-in']).toBe(shapes['lean-left']);
|
||||
});
|
||||
|
||||
// trap-b | trapezoid-bottom | priority | trapezoid
|
||||
it('should support alias for trapezoid shape ', function () {
|
||||
expect(shapes['trapezoid-bottom']).toBe(shapes['trap-b']);
|
||||
expect(shapes.priority).toBe(shapes['trap-b']);
|
||||
expect(shapes.trapezoid).toBe(shapes['trap-b']);
|
||||
});
|
||||
|
||||
// trap-t | trapezoid-top | manual | inv-trapezoid
|
||||
it('should support alias for inv_trapezoid shape ', function () {
|
||||
expect(shapes['trapezoid-top']).toBe(shapes['trap-t']);
|
||||
expect(shapes.manual).toBe(shapes['trap-t']);
|
||||
expect(shapes['inv-trapezoid']).toBe(shapes['trap-t']);
|
||||
});
|
||||
|
||||
// dbl-circ| double-circle
|
||||
it('should support alias for doublecircle shape ', function () {
|
||||
expect(shapes['double-circle']).toBe(shapes['dbl-circ']);
|
||||
});
|
||||
|
||||
// notched-rectangle | card | notch-rect
|
||||
it('should support alias for notched-rectangle shape ', function () {
|
||||
expect(shapes.card).toBe(shapes['notched-rectangle']);
|
||||
expect(shapes['notch-rect']).toBe(shapes['notched-rectangle']);
|
||||
});
|
||||
|
||||
it('should support alias for shadedProcess shape ', function () {
|
||||
const aliases = ['lined-process', 'lined-rectangle', 'lin-proc', 'lin-rect'];
|
||||
for (const alias of aliases) {
|
||||
expect(shapes[alias]).toBe(shapes['shaded-process']);
|
||||
}
|
||||
});
|
||||
|
||||
// sm-circ | small-circle | start
|
||||
it('should support alias for smallCircle shape ', function () {
|
||||
expect(shapes['small-circle']).toBe(shapes['sm-circ']);
|
||||
expect(shapes.start).toBe(shapes['sm-circ']);
|
||||
});
|
||||
|
||||
// framed-circle | stop
|
||||
it('should support alias for framed circle shape ', function () {
|
||||
expect(shapes.stop).toBe(shapes['framed-circle']);
|
||||
});
|
||||
|
||||
// fork | join
|
||||
it('should support alias for fork shape ', function () {
|
||||
expect(shapes.join).toBe(shapes.fork);
|
||||
});
|
||||
|
||||
// brace | comment | brace-l
|
||||
it('should support alias for brace shape ', function () {
|
||||
expect(shapes.comment).toBe(shapes.brace);
|
||||
expect(shapes['brace-l']).toBe(shapes.brace);
|
||||
});
|
||||
|
||||
// bolt | com-link | lightning-bolt
|
||||
it('should support alias for bolt shape ', function () {
|
||||
expect(shapes['com-link']).toBe(shapes.bolt);
|
||||
expect(shapes['lightning-bolt']).toBe(shapes.bolt);
|
||||
});
|
||||
|
||||
// document | doc
|
||||
it('should support alias for waveEdgedRectangle shape ', function () {
|
||||
expect(shapes.doc).toBe(shapes.document);
|
||||
});
|
||||
|
||||
// delay | half-rounded-rectangle
|
||||
it('should support alias for halfRoundedRectangle shape ', function () {
|
||||
expect(shapes.delay).toBe(shapes['half-rounded-rectangle']);
|
||||
});
|
||||
|
||||
// h-cyl | das | horizontal-cylinder
|
||||
it('should support alias for horizontal-cylinder shape ', function () {
|
||||
expect(shapes.das).toBe(shapes['h-cyl']);
|
||||
expect(shapes['horizontal-cylinder']).toBe(shapes['h-cyl']);
|
||||
});
|
||||
|
||||
// lin-cyl | disk | lined-cylinder
|
||||
it('should support alias for linedCylinder shape ', function () {
|
||||
expect(shapes.disk).toBe(shapes['lin-cyl']);
|
||||
expect(shapes['lined-cylinder']).toBe(shapes['lin-cyl']);
|
||||
});
|
||||
|
||||
// curv-trap | display | curved-trapezoid
|
||||
it('should support alias for curvedTrapezoid shape ', function () {
|
||||
expect(shapes.display).toBe(shapes['curv-trap']);
|
||||
expect(shapes['curved-trapezoid']).toBe(shapes['curv-trap']);
|
||||
});
|
||||
|
||||
// div-rect | div-proc | divided-rectangle
|
||||
it('should support alias for dividedRectangle shape ', function () {
|
||||
expect(shapes['div-proc']).toBe(shapes['div-rect']);
|
||||
expect(shapes['divided-rectangle']).toBe(shapes['div-rect']);
|
||||
});
|
||||
|
||||
// sm-tri | extract | small-triangle | triangle
|
||||
it('should support alias for smallTriangle shape ', function () {
|
||||
expect(shapes.extract).toBe(shapes.tri);
|
||||
expect(shapes.triangle).toBe(shapes.tri);
|
||||
});
|
||||
|
||||
// win-pane | internal-storage | window-pane
|
||||
it('should support alias for windowPane shape ', function () {
|
||||
expect(shapes['internal-storage']).toBe(shapes['win-pane']);
|
||||
expect(shapes['window-pane']).toBe(shapes['win-pane']);
|
||||
});
|
||||
|
||||
// fc | junction | filled-circle
|
||||
it('should support alias for filledCircle shape ', function () {
|
||||
expect(shapes.junction).toBe(shapes['f-circ']);
|
||||
expect(shapes['filled-circle']).toBe(shapes['f-circ']);
|
||||
});
|
||||
|
||||
// | lin-doc | lined-document
|
||||
it('should support alias for linedWaveEdgedRectangle shape ', function () {
|
||||
expect(shapes['lin-doc']).toBe(shapes['lined-document']);
|
||||
});
|
||||
|
||||
// notch-pent | loop-limit | notched-pentagon
|
||||
it('should support alias for notchedPentagon shape ', function () {
|
||||
expect(shapes['loop-limit']).toBe(shapes['notch-pent']);
|
||||
expect(shapes['notched-pentagon']).toBe(shapes['notch-pent']);
|
||||
});
|
||||
|
||||
// flip-tri | manual-file | flipped-triangle
|
||||
it('should support alias for flippedTriangle shape ', function () {
|
||||
expect(shapes['manual-file']).toBe(shapes['flip-tri']);
|
||||
expect(shapes['flipped-triangle']).toBe(shapes['flip-tri']);
|
||||
});
|
||||
|
||||
//sl-rect | manual-input | sloped-rectangle
|
||||
it('should support alias for slopedRectangle shape ', function () {
|
||||
expect(shapes['manual-input']).toBe(shapes['sl-rect']);
|
||||
expect(shapes['sloped-rectangle']).toBe(shapes['sl-rect']);
|
||||
});
|
||||
|
||||
// docs | documents | st-doc | stacked-document
|
||||
it('should support alias for multiWaveEdgedRectangle shape ', function () {
|
||||
expect(shapes.docs).toBe(shapes.documents);
|
||||
expect(shapes['st-doc']).toBe(shapes['stacked-document']);
|
||||
});
|
||||
|
||||
// procs | processes | st-rect | stacked-rectangle
|
||||
it('should support alias for multiRect shape ', function () {
|
||||
expect(shapes.procs).toBe(shapes.processes);
|
||||
expect(shapes['st-rect']).toBe(shapes['stacked-rectangle']);
|
||||
});
|
||||
|
||||
// flag | paper-tape
|
||||
it('should support alias for paperTape shape ', function () {
|
||||
expect(shapes['paper-tape']).toBe(shapes.flag);
|
||||
});
|
||||
|
||||
// bow-rect| stored-data | bow-tie-rectangle
|
||||
it('should support alias for bowTieRect shape ', function () {
|
||||
expect(shapes['stored-data']).toBe(shapes['bow-rect']);
|
||||
expect(shapes['bow-tie-rectangle']).toBe(shapes['bow-rect']);
|
||||
});
|
||||
|
||||
// cross-circ | summary | crossed-circle
|
||||
it('should support alias for crossedCircle shape ', function () {
|
||||
expect(shapes.summary).toBe(shapes['cross-circ']);
|
||||
expect(shapes['crossed-circle']).toBe(shapes['cross-circ']);
|
||||
});
|
||||
|
||||
// tag-doc| tag-document
|
||||
it('should support alias for taggedDocument shape ', function () {
|
||||
expect(shapes['tag-doc']).toBe(shapes['tagged-document']);
|
||||
});
|
||||
|
||||
// tag-rect | tag-proc | tagged-rectangle | tagged-process
|
||||
it('should support alias for taggedRect shape ', function () {
|
||||
expect(shapes['tag-proc']).toBe(shapes['tag-rect']);
|
||||
expect(shapes['tagged-rectangle']).toBe(shapes['tag-rect']);
|
||||
expect(shapes['tagged-process']).toBe(shapes['tag-rect']);
|
||||
});
|
||||
});
|
@@ -0,0 +1,44 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const anchor = (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||
const { labelStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const classes = getNodeClasses(node);
|
||||
let cssClasses = classes;
|
||||
if (!classes) {
|
||||
cssClasses = 'anchor';
|
||||
}
|
||||
const shapeSvg = parent
|
||||
// @ts-ignore - SVGElement is not typed
|
||||
.insert('g')
|
||||
.attr('class', cssClasses)
|
||||
.attr('id', node.domId || node.id);
|
||||
|
||||
const radius = 1;
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { fill: 'black', stroke: 'none', fillStyle: 'solid' });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
}
|
||||
const roughNode = rc.circle(0, 0, radius * 2, options);
|
||||
const circleElem = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
circleElem.attr('class', 'anchor').attr('style', cssStyles);
|
||||
|
||||
updateNodeBounds(node, circleElem);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('Circle intersect', node, radius, point);
|
||||
return intersect.circle(node, radius, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,125 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
function generateArcPoints(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
rx: number,
|
||||
ry: number,
|
||||
clockwise: boolean
|
||||
) {
|
||||
const numPoints = 20;
|
||||
// Calculate midpoint
|
||||
const midX = (x1 + x2) / 2;
|
||||
const midY = (y1 + y2) / 2;
|
||||
|
||||
// Calculate the angle of the line connecting the points
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
|
||||
// Calculate transformed coordinates for the ellipse
|
||||
const dx = (x2 - x1) / 2;
|
||||
const dy = (y2 - y1) / 2;
|
||||
|
||||
// Scale to unit circle
|
||||
const transformedX = dx / rx;
|
||||
const transformedY = dy / ry;
|
||||
|
||||
// Calculate the distance between points on the unit circle
|
||||
const distance = Math.sqrt(transformedX ** 2 + transformedY ** 2);
|
||||
|
||||
// Check if the ellipse can be drawn with the given radii
|
||||
if (distance > 1) {
|
||||
throw new Error('The given radii are too small to create an arc between the points.');
|
||||
}
|
||||
|
||||
// Calculate the distance from the midpoint to the center of the ellipse
|
||||
const scaledCenterDistance = Math.sqrt(1 - distance ** 2);
|
||||
|
||||
// Calculate the center of the ellipse
|
||||
const centerX = midX + scaledCenterDistance * ry * Math.sin(angle) * (clockwise ? -1 : 1);
|
||||
const centerY = midY - scaledCenterDistance * rx * Math.cos(angle) * (clockwise ? -1 : 1);
|
||||
|
||||
// Calculate the start and end angles on the ellipse
|
||||
const startAngle = Math.atan2((y1 - centerY) / ry, (x1 - centerX) / rx);
|
||||
const endAngle = Math.atan2((y2 - centerY) / ry, (x2 - centerX) / rx);
|
||||
|
||||
// Adjust angles for clockwise/counterclockwise
|
||||
let angleRange = endAngle - startAngle;
|
||||
if (clockwise && angleRange < 0) {
|
||||
angleRange += 2 * Math.PI;
|
||||
}
|
||||
if (!clockwise && angleRange > 0) {
|
||||
angleRange -= 2 * Math.PI;
|
||||
}
|
||||
|
||||
// Generate points
|
||||
const points = [];
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const t = i / (numPoints - 1);
|
||||
const angle = startAngle + t * angleRange;
|
||||
const x = centerX + rx * Math.cos(angle);
|
||||
const y = centerY + ry * Math.sin(angle);
|
||||
points.push({ x, y });
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
export const bowTieRect = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + node.padding + 20;
|
||||
const h = bbox.height + node.padding;
|
||||
|
||||
const ry = h / 2;
|
||||
const rx = ry / (2.5 + h / 50);
|
||||
|
||||
// let shape: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
const points = [
|
||||
{ x: w / 2, y: -h / 2 },
|
||||
{ x: -w / 2, y: -h / 2 },
|
||||
...generateArcPoints(-w / 2, -h / 2, -w / 2, h / 2, rx, ry, false),
|
||||
{ x: w / 2, y: h / 2 },
|
||||
...generateArcPoints(w / 2, h / 2, w / 2, -h / 2, rx, ry, true),
|
||||
];
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
const bowTieRectPath = createPathFromPoints(points);
|
||||
const bowTieRectShapePath = rc.path(bowTieRectPath, options);
|
||||
const bowTieRectShape = shapeSvg.insert(() => bowTieRectShapePath, ':first-child');
|
||||
|
||||
bowTieRectShape.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
bowTieRectShape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
bowTieRectShape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
bowTieRectShape.attr('transform', `translate(${rx / 2}, 0)`);
|
||||
|
||||
updateNodeBounds(node, bowTieRectShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,68 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
import { insertPolygonShape } from './insertPolygonShape.js';
|
||||
import { createPathFromPoints } from './util.js';
|
||||
|
||||
// const createPathFromPoints = (points: { x: number; y: number }[]): string => {
|
||||
// const pointStrings = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`);
|
||||
// pointStrings.push('Z');
|
||||
// return pointStrings.join(' ');
|
||||
// };
|
||||
|
||||
export async function card(parent: SVGAElement, node: Node): Promise<SVGAElement> {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const h = bbox.height + node.padding;
|
||||
const padding = 12;
|
||||
const w = bbox.width + node.padding + padding;
|
||||
const left = 0;
|
||||
const right = w;
|
||||
const top = -h;
|
||||
const bottom = 0;
|
||||
const points = [
|
||||
{ x: left + padding, y: top },
|
||||
{ x: right, y: top },
|
||||
{ x: right, y: bottom },
|
||||
{ x: left, y: bottom },
|
||||
{ x: left, y: top + padding },
|
||||
{ x: left + padding, y: top },
|
||||
];
|
||||
|
||||
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createPathFromPoints(points);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
polygon = shapeSvg
|
||||
.insert(() => roughNode, ':first-child')
|
||||
.attr('transform', `translate(${-w / 2}, ${h / 2})`);
|
||||
|
||||
if (cssStyles) {
|
||||
polygon.attr('style', cssStyles);
|
||||
}
|
||||
} else {
|
||||
polygon = insertPolygonShape(shapeSvg, w, h, points);
|
||||
}
|
||||
|
||||
if (nodeStyles) {
|
||||
polygon.attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
updateNodeBounds(node, polygon);
|
||||
|
||||
node.intersect = function (point) {
|
||||
return intersect.polygon(node, points, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
@@ -3,20 +3,20 @@ import type { Node } from '../../types.js';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
// @ts-ignore TODO: Fix rough typings
|
||||
import rough from 'roughjs';
|
||||
import { solidStateFill, styles2String } from './handDrawnShapeStyles.js';
|
||||
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { createPathFromPoints, getNodeClasses } from './util.js';
|
||||
|
||||
export const choice = (parent: SVG, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { themeVariables } = getConfig();
|
||||
const { lineColor } = themeVariables;
|
||||
const { nodeStyles } = styles2String(node);
|
||||
node.label = '';
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'node default')
|
||||
.attr('id', node.domId || node.id);
|
||||
.attr('class', getNodeClasses(node))
|
||||
.attr('id', node.domId ?? node.id);
|
||||
const { cssStyles } = node;
|
||||
|
||||
const s = Math.max(28, node.width ?? 0);
|
||||
|
||||
const s = 28;
|
||||
const points = [
|
||||
{ x: 0, y: s / 2 },
|
||||
{ x: s / 2, y: 0 },
|
||||
@@ -24,40 +24,32 @@ export const choice = (parent: SVG, node: Node) => {
|
||||
{ x: -s / 2, y: 0 },
|
||||
];
|
||||
|
||||
let choice;
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore TODO: Fix rough typings
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const pointArr = points.map(function (d) {
|
||||
return [d.x, d.y];
|
||||
});
|
||||
const roughNode = rc.polygon(pointArr, solidStateFill(lineColor));
|
||||
choice = shapeSvg.insert(() => roughNode);
|
||||
} else {
|
||||
choice = shapeSvg.insert('polygon', ':first-child').attr(
|
||||
'points',
|
||||
points
|
||||
.map(function (d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
);
|
||||
// @ts-ignore TODO: Fix rough typings
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
// center the circle around its coordinate
|
||||
choice
|
||||
.attr('class', 'state-start')
|
||||
// @ts-ignore TODO: Fix rough typings
|
||||
.attr('r', 7)
|
||||
.attr('width', 28)
|
||||
.attr('height', 28)
|
||||
.attr('style', nodeStyles);
|
||||
const choicePath = createPathFromPoints(points);
|
||||
const roughNode = rc.path(choicePath, options);
|
||||
const choiceShape = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
choiceShape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
choiceShape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
node.width = 28;
|
||||
node.height = 28;
|
||||
|
||||
node.intersect = function (point) {
|
||||
return intersect.circle(node, 14, point);
|
||||
return intersect.polygon(node, points, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
|
@@ -15,7 +15,6 @@ export const circle = async (parent: SVGAElement, node: Node): Promise<SVGAEleme
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const roughNode = rc.circle(0, 0, radius * 2, options);
|
||||
|
@@ -0,0 +1,67 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { getNodeClasses, updateNodeBounds } from './util.js';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import intersect from '../intersect/index.js';
|
||||
|
||||
function createLine(r: number) {
|
||||
const xAxis45 = Math.cos(Math.PI / 4); // cosine of 45 degrees
|
||||
const yAxis45 = Math.sin(Math.PI / 4); // sine of 45 degrees
|
||||
const lineLength = r * 2;
|
||||
|
||||
const pointQ1 = { x: (lineLength / 2) * xAxis45, y: (lineLength / 2) * yAxis45 }; // Quadrant I
|
||||
const pointQ2 = { x: -(lineLength / 2) * xAxis45, y: (lineLength / 2) * yAxis45 }; // Quadrant II
|
||||
const pointQ3 = { x: -(lineLength / 2) * xAxis45, y: -(lineLength / 2) * yAxis45 }; // Quadrant III
|
||||
const pointQ4 = { x: (lineLength / 2) * xAxis45, y: -(lineLength / 2) * yAxis45 }; // Quadrant IV
|
||||
|
||||
return `M ${pointQ2.x},${pointQ2.y} L ${pointQ4.x},${pointQ4.y}
|
||||
M ${pointQ1.x},${pointQ1.y} L ${pointQ3.x},${pointQ3.y}`;
|
||||
}
|
||||
|
||||
export const crossedCircle = (parent: SVG, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
node.label = '';
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', getNodeClasses(node))
|
||||
.attr('id', node.domId ?? node.id);
|
||||
const radius = Math.max(30, node?.width ?? 0);
|
||||
const { cssStyles } = node;
|
||||
|
||||
// @ts-expect-error shapeSvg d3 class is incorrect?
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const circleNode = rc.circle(0, 0, radius * 2, options);
|
||||
const linePath = createLine(radius);
|
||||
const lineNode = rc.path(linePath, options);
|
||||
|
||||
const crossedCircle = shapeSvg.insert(() => circleNode, ':first-child');
|
||||
crossedCircle.insert(() => lineNode);
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
crossedCircle.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
crossedCircle.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
updateNodeBounds(node, crossedCircle);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('crossedCircle intersect', node, { radius, point });
|
||||
const pos = intersect.circle(node, radius, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,110 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
function generateCirclePoints(
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
radius: number,
|
||||
numPoints = 100,
|
||||
startAngle = 0,
|
||||
endAngle = 180
|
||||
) {
|
||||
const points = [];
|
||||
|
||||
// Convert angles to radians
|
||||
const startAngleRad = (startAngle * Math.PI) / 180;
|
||||
const endAngleRad = (endAngle * Math.PI) / 180;
|
||||
|
||||
// Calculate the angle range in radians
|
||||
const angleRange = endAngleRad - startAngleRad;
|
||||
|
||||
// Calculate the angle step
|
||||
const angleStep = angleRange / (numPoints - 1);
|
||||
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const angle = startAngleRad + i * angleStep;
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
const y = centerY + radius * Math.sin(angle);
|
||||
points.push({ x: -x, y: -y });
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
export const curlyBraceLeft = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + (node.padding ?? 0);
|
||||
const h = bbox.height + (node.padding ?? 0);
|
||||
const radius = Math.max(5, h * 0.1);
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const points = [
|
||||
...generateCirclePoints(w / 2, -h / 2, radius, 30, -90, 0),
|
||||
{ x: -w / 2 - radius, y: radius },
|
||||
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
|
||||
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
|
||||
{ x: -w / 2 - radius, y: -h / 2 },
|
||||
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
|
||||
];
|
||||
|
||||
const rectPoints = [
|
||||
{ x: w / 2, y: -h / 2 - radius },
|
||||
{ x: -w / 2, y: -h / 2 - radius },
|
||||
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
|
||||
{ x: -w / 2 - radius, y: -radius },
|
||||
...generateCirclePoints(w / 2 + w * 0.1, -radius, radius, 20, -180, -270),
|
||||
...generateCirclePoints(w / 2 + w * 0.1, radius, radius, 20, -90, -180),
|
||||
{ x: -w / 2 - radius, y: h / 2 },
|
||||
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
|
||||
{ x: -w / 2, y: h / 2 + radius },
|
||||
{ x: w / 2, y: h / 2 + radius },
|
||||
];
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { fill: 'none' });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
const curlyBraceLeftPath = createPathFromPoints(points);
|
||||
const newCurlyBracePath = curlyBraceLeftPath.replace('Z', '');
|
||||
const curlyBraceLeftNode = rc.path(newCurlyBracePath, options);
|
||||
const rectPath = createPathFromPoints(rectPoints);
|
||||
const rectShape = rc.path(rectPath, { ...options });
|
||||
const curlyBraceLeftShape = shapeSvg.insert('g', ':first-child');
|
||||
curlyBraceLeftShape.insert(() => rectShape, ':first-child').attr('stroke-opacity', 0);
|
||||
curlyBraceLeftShape.insert(() => curlyBraceLeftNode, ':first-child');
|
||||
curlyBraceLeftShape.attr('class', 'text');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
curlyBraceLeftShape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
curlyBraceLeftShape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
curlyBraceLeftShape.attr('transform', `translate(${radius}, 0)`);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-w / 2 + radius - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, curlyBraceLeftShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, rectPoints, point);
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,110 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
function generateCirclePoints(
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
radius: number,
|
||||
numPoints = 100,
|
||||
startAngle = 0,
|
||||
endAngle = 180
|
||||
) {
|
||||
const points = [];
|
||||
|
||||
// Convert angles to radians
|
||||
const startAngleRad = (startAngle * Math.PI) / 180;
|
||||
const endAngleRad = (endAngle * Math.PI) / 180;
|
||||
|
||||
// Calculate the angle range in radians
|
||||
const angleRange = endAngleRad - startAngleRad;
|
||||
|
||||
// Calculate the angle step
|
||||
const angleStep = angleRange / (numPoints - 1);
|
||||
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const angle = startAngleRad + i * angleStep;
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
const y = centerY + radius * Math.sin(angle);
|
||||
points.push({ x, y });
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
export const curlyBraceRight = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + (node.padding ?? 0);
|
||||
const h = bbox.height + (node.padding ?? 0);
|
||||
const radius = Math.max(5, h * 0.1);
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const points = [
|
||||
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
|
||||
{ x: w / 2 + radius, y: -radius },
|
||||
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
|
||||
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
|
||||
{ x: w / 2 + radius, y: h / 2 },
|
||||
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
|
||||
];
|
||||
|
||||
const rectPoints = [
|
||||
{ x: -w / 2, y: -h / 2 - radius },
|
||||
{ x: w / 2, y: -h / 2 - radius },
|
||||
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
|
||||
{ x: w / 2 + radius, y: -radius },
|
||||
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
|
||||
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
|
||||
{ x: w / 2 + radius, y: h / 2 },
|
||||
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
|
||||
{ x: w / 2, y: h / 2 + radius },
|
||||
{ x: -w / 2, y: h / 2 + radius },
|
||||
];
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { fill: 'none' });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
const curlyBraceRightPath = createPathFromPoints(points);
|
||||
const newCurlyBracePath = curlyBraceRightPath.replace('Z', '');
|
||||
const curlyBraceRightNode = rc.path(newCurlyBracePath, options);
|
||||
const rectPath = createPathFromPoints(rectPoints);
|
||||
const rectShape = rc.path(rectPath, { ...options });
|
||||
const curlyBraceRightShape = shapeSvg.insert('g', ':first-child');
|
||||
curlyBraceRightShape.insert(() => rectShape, ':first-child').attr('stroke-opacity', 0);
|
||||
curlyBraceRightShape.insert(() => curlyBraceRightNode, ':first-child');
|
||||
curlyBraceRightShape.attr('class', 'text');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
curlyBraceRightShape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
curlyBraceRightShape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
curlyBraceRightShape.attr('transform', `translate(${-radius}, 0)`);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-w / 2 + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, curlyBraceRightShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, rectPoints, point);
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,129 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
function generateCirclePoints(
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
radius: number,
|
||||
numPoints = 100,
|
||||
startAngle = 0,
|
||||
endAngle = 180
|
||||
) {
|
||||
const points = [];
|
||||
|
||||
// Convert angles to radians
|
||||
const startAngleRad = (startAngle * Math.PI) / 180;
|
||||
const endAngleRad = (endAngle * Math.PI) / 180;
|
||||
|
||||
// Calculate the angle range in radians
|
||||
const angleRange = endAngleRad - startAngleRad;
|
||||
|
||||
// Calculate the angle step
|
||||
const angleStep = angleRange / (numPoints - 1);
|
||||
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const angle = startAngleRad + i * angleStep;
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
const y = centerY + radius * Math.sin(angle);
|
||||
points.push({ x: -x, y: -y });
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
export const curlyBraces = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + (node.padding ?? 0);
|
||||
const h = bbox.height + (node.padding ?? 0);
|
||||
const radius = Math.max(5, h * 0.1);
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const leftCurlyBracePoints = [
|
||||
...generateCirclePoints(w / 2, -h / 2, radius, 30, -90, 0),
|
||||
{ x: -w / 2 - radius, y: radius },
|
||||
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
|
||||
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
|
||||
{ x: -w / 2 - radius, y: -h / 2 },
|
||||
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
|
||||
];
|
||||
|
||||
const rightCurlyBracePoints = [
|
||||
...generateCirclePoints(-w / 2 + radius + radius / 2, -h / 2, radius, 20, -90, -180),
|
||||
{ x: w / 2 - radius / 2, y: radius },
|
||||
...generateCirclePoints(-w / 2 - radius / 2, -radius, radius, 20, 0, 90),
|
||||
...generateCirclePoints(-w / 2 - radius / 2, radius, radius, 20, -90, 0),
|
||||
{ x: w / 2 - radius / 2, y: -radius },
|
||||
...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270),
|
||||
];
|
||||
|
||||
const rectPoints = [
|
||||
{ x: w / 2, y: -h / 2 - radius },
|
||||
{ x: -w / 2, y: -h / 2 - radius },
|
||||
...generateCirclePoints(w / 2, -h / 2, radius, 20, -90, 0),
|
||||
{ x: -w / 2 - radius, y: -radius },
|
||||
...generateCirclePoints(w / 2 + radius * 2, -radius, radius, 20, -180, -270),
|
||||
...generateCirclePoints(w / 2 + radius * 2, radius, radius, 20, -90, -180),
|
||||
{ x: -w / 2 - radius, y: h / 2 },
|
||||
...generateCirclePoints(w / 2, h / 2, radius, 20, 0, 90),
|
||||
{ x: -w / 2, y: h / 2 + radius },
|
||||
{ x: w / 2 - radius - radius / 2, y: h / 2 + radius },
|
||||
...generateCirclePoints(-w / 2 + radius + radius / 2, -h / 2, radius, 20, -90, -180),
|
||||
{ x: w / 2 - radius / 2, y: radius },
|
||||
...generateCirclePoints(-w / 2 - radius / 2, -radius, radius, 20, 0, 90),
|
||||
...generateCirclePoints(-w / 2 - radius / 2, radius, radius, 20, -90, 0),
|
||||
{ x: w / 2 - radius / 2, y: -radius },
|
||||
...generateCirclePoints(-w / 2 + radius + radius / 2, h / 2, radius, 30, -180, -270),
|
||||
];
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { fill: 'none' });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
const leftCurlyBracePath = createPathFromPoints(leftCurlyBracePoints);
|
||||
const newLeftCurlyBracePath = leftCurlyBracePath.replace('Z', '');
|
||||
const leftCurlyBraceNode = rc.path(newLeftCurlyBracePath, options);
|
||||
const rightCurlyBracePath = createPathFromPoints(rightCurlyBracePoints);
|
||||
const newRightCurlyBracePath = rightCurlyBracePath.replace('Z', '');
|
||||
const rightCurlyBraceNode = rc.path(newRightCurlyBracePath, options);
|
||||
const rectPath = createPathFromPoints(rectPoints);
|
||||
const rectShape = rc.path(rectPath, { ...options });
|
||||
const curlyBracesShape = shapeSvg.insert('g', ':first-child');
|
||||
curlyBracesShape.insert(() => rectShape, ':first-child').attr('stroke-opacity', 0);
|
||||
curlyBracesShape.insert(() => leftCurlyBraceNode, ':first-child');
|
||||
curlyBracesShape.insert(() => rightCurlyBraceNode, ':first-child');
|
||||
curlyBracesShape.attr('class', 'text');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
curlyBracesShape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
curlyBracesShape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
curlyBracesShape.attr('transform', `translate(${radius - radius / 4}, 0)`);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-w / 2 + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, curlyBracesShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, rectPoints, point);
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
labelHelper,
|
||||
updateNodeBounds,
|
||||
getNodeClasses,
|
||||
createPathFromPoints,
|
||||
generateCirclePoints,
|
||||
} from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const curvedTrapezoid = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const minWidth = 80,
|
||||
minHeight = 20;
|
||||
const w = Math.max(minWidth, (bbox.width + (node.padding ?? 0) * 2) * 1.25, node?.width ?? 0);
|
||||
const h = Math.max(minHeight, bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
|
||||
const radius = h / 2;
|
||||
|
||||
const { cssStyles } = node;
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const totalWidth = w,
|
||||
totalHeight = h;
|
||||
const rw = totalWidth - radius;
|
||||
const tw = totalHeight / 4;
|
||||
|
||||
const points = [
|
||||
{ x: rw, y: 0 },
|
||||
{ x: tw, y: 0 },
|
||||
{ x: 0, y: totalHeight / 2 },
|
||||
{ x: tw, y: totalHeight },
|
||||
{ x: rw, y: totalHeight },
|
||||
...generateCirclePoints(-rw, -totalHeight / 2, radius, 50, 270, 90),
|
||||
];
|
||||
|
||||
const pathData = createPathFromPoints(points);
|
||||
const shapeNode = rc.path(pathData, options);
|
||||
|
||||
const polygon = shapeSvg.insert(() => shapeNode, ':first-child');
|
||||
polygon.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
polygon.attr('transform', `translate(${-w / 2}, ${-h / 2})`);
|
||||
|
||||
updateNodeBounds(node, polygon);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -51,17 +51,16 @@ export const createInnerCylinderPathD = (
|
||||
export const cylinder = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + node.padding;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = Math.max(bbox.width + node.padding, node.width ?? 0);
|
||||
const rx = w / 2;
|
||||
const ry = rx / (2.5 + w / 50);
|
||||
const h = bbox.height + ry + node.padding;
|
||||
const h = Math.max(bbox.height + ry + node.padding, node.height ?? 0);
|
||||
|
||||
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
|
||||
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
|
||||
@@ -89,6 +88,11 @@ export const cylinder = async (parent: SVGAElement, node: Node) => {
|
||||
|
||||
updateNodeBounds(node, cylinder);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-(bbox.width / 2) - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + (node.padding ?? 0) / 1.5 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.rect(node, point);
|
||||
const x = pos.x - (node.x ?? 0);
|
||||
|
@@ -0,0 +1,66 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const dividedRectangle = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const rectOffset = h * 0.2;
|
||||
|
||||
const x = -w / 2;
|
||||
const y = -h / 2 - rectOffset / 2;
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const pts = [
|
||||
{ x, y: y + rectOffset },
|
||||
{ x: -x, y: y + rectOffset },
|
||||
{ x: -x, y: -y },
|
||||
{ x, y: -y },
|
||||
{ x, y },
|
||||
{ x: -x, y },
|
||||
{ x: -x, y: y + rectOffset },
|
||||
];
|
||||
|
||||
const poly = rc.polygon(
|
||||
pts.map((p) => [p.x, p.y]),
|
||||
options
|
||||
);
|
||||
|
||||
const polygon = shapeSvg.insert(() => poly, ':first-child');
|
||||
polygon.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${x + (node.padding ?? 0) / 2 - (bbox.x - (bbox.left ?? 0))}, ${y + rectOffset + (node.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, polygon);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.rect(node, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,117 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const createCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x},${y + ry}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`a${rx},${ry} 0,0,0 ${-width},0`,
|
||||
`l0,${height}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`l0,${-height}`,
|
||||
].join(' ');
|
||||
};
|
||||
export const createOuterCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x},${y + ry}`,
|
||||
`M${x + width},${y + ry}`,
|
||||
`a${rx},${ry} 0,0,0 ${-width},0`,
|
||||
`l0,${height}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`l0,${-height}`,
|
||||
].join(' ');
|
||||
};
|
||||
export const createInnerCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [`M${x - width / 2},${-height / 2}`, `a${rx},${ry} 0,0,0 ${width},0`].join(' ');
|
||||
};
|
||||
export const cylinder = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = bbox.width + node.padding;
|
||||
const rx = w / 2;
|
||||
const ry = rx / (2.5 + w / 50);
|
||||
const h = bbox.height + ry + node.padding;
|
||||
|
||||
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry);
|
||||
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
|
||||
const outerNode = rc.path(outerPathData, userNodeOverrides(node, {}));
|
||||
const innerLine = rc.path(innerPathData, userNodeOverrides(node, { fill: 'none' }));
|
||||
|
||||
cylinder = shapeSvg.insert(() => innerLine, ':first-child');
|
||||
cylinder = shapeSvg.insert(() => outerNode, ':first-child');
|
||||
cylinder.attr('class', 'basic label-container');
|
||||
if (cssStyles) {
|
||||
cylinder.attr('style', cssStyles);
|
||||
}
|
||||
} else {
|
||||
const pathData = createCylinderPathD(0, 0, w, h, rx, ry);
|
||||
cylinder = shapeSvg
|
||||
.insert('path', ':first-child')
|
||||
.attr('d', pathData)
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', cssStyles)
|
||||
.attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
cylinder.attr('label-offset-y', ry);
|
||||
cylinder.attr('transform', `translate(${-w / 2}, ${-(h / 2 + ry)})`);
|
||||
|
||||
updateNodeBounds(node, cylinder);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.rect(node, point);
|
||||
const x = pos.x - (node.x ?? 0);
|
||||
|
||||
if (
|
||||
rx != 0 &&
|
||||
(Math.abs(x) < (node.width ?? 0) / 2 ||
|
||||
(Math.abs(x) == (node.width ?? 0) / 2 &&
|
||||
Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry))
|
||||
) {
|
||||
let y = ry * ry * (1 - (x * x) / (rx * rx));
|
||||
if (y != 0) {
|
||||
y = Math.sqrt(y);
|
||||
}
|
||||
y = ry - y;
|
||||
if (point.y - (node.y ?? 0) > 0) {
|
||||
y = -y;
|
||||
}
|
||||
|
||||
pos.y += y;
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -17,7 +17,6 @@ export const doublecircle = async (parent: SVGAElement, node: Node): Promise<SVG
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const outerOptions = userNodeOverrides(node, { roughness: 0.2, strokeWidth: 2.5 });
|
||||
|
||||
|
@@ -47,8 +47,6 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', nodeStyles)
|
||||
.attr('rx', rx)
|
||||
.attr('data-id', 'abc')
|
||||
.attr('data-et', 'node')
|
||||
.attr('ry', ry)
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
|
@@ -0,0 +1,56 @@
|
||||
import rough from 'roughjs';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.d.ts';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { getNodeClasses, updateNodeBounds } from './util.js';
|
||||
|
||||
export const filledCircle = (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ config: { themeVariables } }: ShapeRenderOptions
|
||||
) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.label = '';
|
||||
node.labelStyle = labelStyles;
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', getNodeClasses(node))
|
||||
.attr('id', node.domId ?? node.id);
|
||||
const radius = 7;
|
||||
const { cssStyles } = node;
|
||||
|
||||
// @ts-expect-error shapeSvg d3 class is incorrect?
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const { nodeBorder } = themeVariables;
|
||||
const options = userNodeOverrides(node, { fillStyle: 'solid' });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
}
|
||||
|
||||
const circleNode = rc.circle(0, 0, radius * 2, options);
|
||||
|
||||
const filledCircle = shapeSvg.insert(() => circleNode, ':first-child');
|
||||
|
||||
filledCircle.selectAll('path').attr('style', `fill: ${nodeBorder} !important;`);
|
||||
|
||||
if (cssStyles && cssStyles.length > 0 && node.look !== 'handDrawn') {
|
||||
filledCircle.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
filledCircle.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
updateNodeBounds(node, filledCircle);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('filledCircle intersect', node, { radius, point });
|
||||
const pos = intersect.circle(node, radius, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,63 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import { createPathFromPoints } from './util.js';
|
||||
|
||||
export const flippedTriangle = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const w = bbox.width + (node.padding ?? 0);
|
||||
const h = w + bbox.height;
|
||||
|
||||
const tw = w + bbox.height;
|
||||
const points = [
|
||||
{ x: 0, y: -h },
|
||||
{ x: tw, y: -h },
|
||||
{ x: tw / 2, y: 0 },
|
||||
];
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
const pathData = createPathFromPoints(points);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
const flippedTriangle = shapeSvg
|
||||
.insert(() => roughNode, ':first-child')
|
||||
.attr('transform', `translate(${-h / 2}, ${h / 2})`);
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
flippedTriangle.selectChildren('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
flippedTriangle.selectChildren('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
node.width = w;
|
||||
node.height = h;
|
||||
|
||||
updateNodeBounds(node, flippedTriangle);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))}, ${-h / 2 + (node.padding ?? 0) / 2 + (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('Triangle intersect', node, points, point);
|
||||
return intersect.polygon(node, points, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -1,65 +1,66 @@
|
||||
import { updateNodeBounds } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import rough from 'roughjs';
|
||||
import { solidStateFill } from './handDrawnShapeStyles.js';
|
||||
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { getNodeClasses, updateNodeBounds } from './util.js';
|
||||
|
||||
export const forkJoin = (parent: SVG, node: Node, dir: string) => {
|
||||
const { themeVariables } = getConfig();
|
||||
const { lineColor } = themeVariables;
|
||||
export const forkJoin = (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ dir, config: { state, themeVariables } }: ShapeRenderOptions
|
||||
) => {
|
||||
const { nodeStyles } = styles2String(node);
|
||||
node.label = '';
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'node default')
|
||||
.attr('id', node.domId || node.id);
|
||||
.attr('class', getNodeClasses(node))
|
||||
.attr('id', node.domId ?? node.id);
|
||||
|
||||
let width = 70;
|
||||
let height = 10;
|
||||
const { cssStyles } = node;
|
||||
let width = Math.max(70, node?.width ?? 0);
|
||||
let height = Math.max(10, node?.height ?? 0);
|
||||
|
||||
if (dir === 'LR') {
|
||||
width = 10;
|
||||
height = 70;
|
||||
width = Math.max(10, node?.width ?? 0);
|
||||
height = Math.max(70, node?.height ?? 0);
|
||||
}
|
||||
|
||||
const x = (-1 * width) / 2;
|
||||
const y = (-1 * height) / 2;
|
||||
|
||||
let shape;
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore TODO: Fix rough typings
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const roughNode = rc.rectangle(x, y, width, height, solidStateFill(lineColor));
|
||||
shape = shapeSvg.insert(() => roughNode);
|
||||
} else {
|
||||
shape = shapeSvg
|
||||
.append('rect')
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('class', 'fork-join');
|
||||
// @ts-ignore TODO: Fix rough typings
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {
|
||||
stroke: themeVariables.lineColor,
|
||||
fill: themeVariables.lineColor,
|
||||
});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const roughNode = rc.rectangle(x, y, width, height, options);
|
||||
|
||||
const shape = shapeSvg.insert(() => roughNode, ':first-child');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
shape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
shape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
updateNodeBounds(node, shape);
|
||||
let nodeHeight = 0;
|
||||
let nodeWidth = 0;
|
||||
let nodePadding = 10;
|
||||
if (node.height) {
|
||||
nodeHeight = node.height;
|
||||
const padding = state?.padding ?? 0;
|
||||
if (node.width && node.height) {
|
||||
node.width += padding / 2 || 0;
|
||||
node.height += padding / 2 || 0;
|
||||
}
|
||||
if (node.width) {
|
||||
nodeWidth = node.width;
|
||||
}
|
||||
|
||||
if (node.padding) {
|
||||
nodePadding = node.padding;
|
||||
}
|
||||
|
||||
node.height = nodeHeight + nodePadding / 2;
|
||||
node.width = nodeWidth + nodePadding / 2;
|
||||
node.intersect = function (point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
@@ -0,0 +1,67 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import {
|
||||
labelHelper,
|
||||
updateNodeBounds,
|
||||
getNodeClasses,
|
||||
createPathFromPoints,
|
||||
generateCirclePoints,
|
||||
} from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const halfRoundedRectangle = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const minWidth = 80,
|
||||
minHeight = 50;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = Math.max(minWidth, bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
|
||||
const h = Math.max(minHeight, bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
|
||||
const radius = h / 2;
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const points = [
|
||||
{ x: -w / 2, y: -h / 2 },
|
||||
{ x: w / 2 - radius, y: -h / 2 },
|
||||
...generateCirclePoints(-w / 2 + radius, 0, radius, 50, 90, 270),
|
||||
{ x: w / 2 - radius, y: h / 2 },
|
||||
{ x: -w / 2, y: h / 2 },
|
||||
];
|
||||
|
||||
const pathData = createPathFromPoints(points);
|
||||
const shapeNode = rc.path(pathData, options);
|
||||
const polygon = shapeSvg.insert(() => shapeNode, ':first-child');
|
||||
polygon.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
// label.attr(
|
||||
// 'transform',
|
||||
// `translate(${-w / 2 + (node.padding ?? 0) - (bbox.x - (bbox.left ?? 0))}, ${-h / 2 + (node.padding ?? 0) - (bbox.y - (bbox.top ?? 0))})`
|
||||
// );
|
||||
|
||||
updateNodeBounds(node, polygon);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('Pill intersect', node, { radius, point });
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
@@ -97,9 +97,11 @@ export const userNodeOverrides = (node: Node, options: any) => {
|
||||
fill: stylesMap.get('fill') || mainBkg,
|
||||
fillStyle: 'hachure', // solid fill
|
||||
fillWeight: 4,
|
||||
hachureGap: 5.2,
|
||||
stroke: stylesMap.get('stroke') || nodeBorder,
|
||||
seed: handDrawnSeed,
|
||||
strokeWidth: 1.3,
|
||||
strokeWidth: stylesMap.get('stroke-width')?.replace('px', '') || 1.3,
|
||||
fillLineDash: [0, 0],
|
||||
},
|
||||
options
|
||||
);
|
||||
|
@@ -46,7 +46,6 @@ export const hexagon = async (parent: SVGAElement, node: Node): Promise<SVGAElem
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createHexagonPathD(0, 0, w, h, m);
|
||||
|
@@ -0,0 +1,60 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const hourglass = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.label = '';
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const w = Math.max(30, node?.width ?? 0);
|
||||
const h = Math.max(30, node?.height ?? 0);
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const points = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: 0, y: h },
|
||||
{ x: w, y: h },
|
||||
];
|
||||
|
||||
const pathData = createPathFromPoints(points);
|
||||
const shapeNode = rc.path(pathData, options);
|
||||
const polygon = shapeSvg.insert(() => shapeNode, ':first-child');
|
||||
polygon.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
polygon.attr('transform', `translate(${-w / 2}, ${-h / 2})`);
|
||||
|
||||
updateNodeBounds(node, polygon);
|
||||
|
||||
// label.attr('transform', `translate(${-bbox.width / 2}, ${(h/2)})`); // To transform text below hourglass shape
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('Pill intersect', node, { points });
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,137 @@
|
||||
import rough from 'roughjs';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import { getIconSVG } from '../../icons.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.d.ts';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { labelHelper, updateNodeBounds } from './util.js';
|
||||
|
||||
export const icon = async (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ config: { themeVariables, flowchart } }: ShapeRenderOptions
|
||||
) => {
|
||||
const { labelStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const assetHeight = node.assetHeight ?? 48;
|
||||
const assetWidth = node.assetWidth ?? 48;
|
||||
const iconSize = Math.max(assetHeight, assetWidth);
|
||||
const defaultWidth = flowchart?.wrappingWidth;
|
||||
node.width = Math.max(iconSize, defaultWidth ?? 0);
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'icon-shape default');
|
||||
|
||||
const topLabel = node.pos === 't';
|
||||
|
||||
const height = iconSize;
|
||||
const width = iconSize;
|
||||
const { nodeBorder } = themeVariables;
|
||||
const { stylesMap } = compileStyles(node);
|
||||
|
||||
const x = -width / 2;
|
||||
const y = -height / 2;
|
||||
|
||||
const labelPadding = node.label ? 8 : 0;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { stroke: 'none', fill: 'none' });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const iconNode = rc.rectangle(x, y, width, height, options);
|
||||
|
||||
const outerWidth = Math.max(width, bbox.width);
|
||||
const outerHeight = height + bbox.height + labelPadding;
|
||||
|
||||
const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, {
|
||||
...options,
|
||||
fill: 'transparent',
|
||||
stroke: 'none',
|
||||
});
|
||||
|
||||
const iconShape = shapeSvg.insert(() => iconNode, ':first-child');
|
||||
const outerShape = shapeSvg.insert(() => outerNode);
|
||||
|
||||
if (node.icon) {
|
||||
const iconElem = shapeSvg.append('g');
|
||||
iconElem.html(
|
||||
`<g>${await getIconSVG(node.icon, {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fallbackPrefix: '',
|
||||
})}</g>`
|
||||
);
|
||||
const iconBBox = iconElem.node().getBBox();
|
||||
const iconWidth = iconBBox.width;
|
||||
const iconHeight = iconBBox.height;
|
||||
const iconX = iconBBox.x;
|
||||
const iconY = iconBBox.y;
|
||||
iconElem.attr(
|
||||
'transform',
|
||||
`translate(${-iconWidth / 2 - iconX},${
|
||||
topLabel
|
||||
? bbox.height / 2 + labelPadding / 2 - iconHeight / 2 - iconY
|
||||
: -bbox.height / 2 - labelPadding / 2 - iconHeight / 2 - iconY
|
||||
})`
|
||||
);
|
||||
iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder);
|
||||
}
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))},${
|
||||
topLabel ? -outerHeight / 2 : outerHeight / 2 - bbox.height
|
||||
})`
|
||||
);
|
||||
|
||||
iconShape.attr(
|
||||
'transform',
|
||||
`translate(${0},${
|
||||
topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2
|
||||
})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, outerShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('iconSquare intersect', node, point);
|
||||
if (!node.label) {
|
||||
return intersect.rect(node, point);
|
||||
}
|
||||
const dx = node.x ?? 0;
|
||||
const dy = node.y ?? 0;
|
||||
const nodeHeight = node.height ?? 0;
|
||||
let points = [];
|
||||
if (topLabel) {
|
||||
points = [
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
];
|
||||
} else {
|
||||
points = [
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 + height },
|
||||
];
|
||||
}
|
||||
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,101 @@
|
||||
import rough from 'roughjs';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import { getIconSVG } from '../../icons.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.d.ts';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { labelHelper, updateNodeBounds } from './util.js';
|
||||
|
||||
export const iconCircle = async (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ config: { themeVariables, flowchart } }: ShapeRenderOptions
|
||||
) => {
|
||||
const { labelStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const assetHeight = node.assetHeight ?? 48;
|
||||
const assetWidth = node.assetWidth ?? 48;
|
||||
const iconSize = Math.max(assetHeight, assetWidth);
|
||||
const defaultWidth = flowchart?.wrappingWidth;
|
||||
node.width = Math.max(iconSize, defaultWidth ?? 0);
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'icon-shape default');
|
||||
|
||||
const padding = 20;
|
||||
const labelPadding = node.label ? 8 : 0;
|
||||
|
||||
const topLabel = node.pos === 't';
|
||||
|
||||
const { nodeBorder, mainBkg } = themeVariables;
|
||||
const { stylesMap } = compileStyles(node);
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { stroke: stylesMap.get('fill') || mainBkg });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const iconElem = shapeSvg.append('g');
|
||||
if (node.icon) {
|
||||
iconElem.html(
|
||||
`<g>${await getIconSVG(node.icon, {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fallbackPrefix: '',
|
||||
})}</g>`
|
||||
);
|
||||
}
|
||||
const iconBBox = iconElem.node().getBBox();
|
||||
const iconWidth = iconBBox.width;
|
||||
const iconHeight = iconBBox.height;
|
||||
const iconX = iconBBox.x;
|
||||
const iconY = iconBBox.y;
|
||||
|
||||
const diameter = Math.max(iconWidth, iconHeight) * Math.SQRT2 + padding * 2;
|
||||
const iconNode = rc.circle(0, 0, diameter, options);
|
||||
|
||||
const outerWidth = Math.max(diameter, bbox.width);
|
||||
const outerHeight = diameter + bbox.height + labelPadding;
|
||||
|
||||
const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, {
|
||||
...options,
|
||||
fill: 'transparent',
|
||||
stroke: 'none',
|
||||
});
|
||||
|
||||
const iconShape = shapeSvg.insert(() => iconNode, ':first-child');
|
||||
const outerShape = shapeSvg.insert(() => outerNode);
|
||||
iconElem.attr(
|
||||
'transform',
|
||||
`translate(${-iconWidth / 2 - iconX},${
|
||||
topLabel
|
||||
? bbox.height / 2 + labelPadding / 2 - iconHeight / 2 - iconY
|
||||
: -bbox.height / 2 - labelPadding / 2 - iconHeight / 2 - iconY
|
||||
})`
|
||||
);
|
||||
iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder);
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))},${
|
||||
topLabel ? -outerHeight / 2 : outerHeight / 2 - bbox.height
|
||||
})`
|
||||
);
|
||||
|
||||
iconShape.attr(
|
||||
'transform',
|
||||
`translate(${0},${
|
||||
topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2
|
||||
})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, outerShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('iconSquare intersect', node, point);
|
||||
const pos = intersect.rect(node, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,142 @@
|
||||
import rough from 'roughjs';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import { getIconSVG } from '../../icons.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.d.ts';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { createRoundedRectPathD } from './roundedRectPath.js';
|
||||
import { labelHelper, updateNodeBounds } from './util.js';
|
||||
|
||||
export const iconRounded = async (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ config: { themeVariables, flowchart } }: ShapeRenderOptions
|
||||
) => {
|
||||
const { labelStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const assetHeight = node.assetHeight ?? 48;
|
||||
const assetWidth = node.assetWidth ?? 48;
|
||||
const iconSize = Math.max(assetHeight, assetWidth);
|
||||
const defaultWidth = flowchart?.wrappingWidth;
|
||||
node.width = Math.max(iconSize, defaultWidth ?? 0);
|
||||
const { shapeSvg, bbox, halfPadding, label } = await labelHelper(
|
||||
parent,
|
||||
node,
|
||||
'icon-shape default'
|
||||
);
|
||||
|
||||
const topLabel = node.pos === 't';
|
||||
|
||||
const height = iconSize + halfPadding * 2;
|
||||
const width = iconSize + halfPadding * 2;
|
||||
const { nodeBorder, mainBkg } = themeVariables;
|
||||
const { stylesMap } = compileStyles(node);
|
||||
|
||||
const x = -width / 2;
|
||||
const y = -height / 2;
|
||||
|
||||
const labelPadding = node.label ? 8 : 0;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { stroke: stylesMap.get('fill') || mainBkg });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const iconNode = rc.path(createRoundedRectPathD(x, y, width, height, 5), options);
|
||||
|
||||
const outerWidth = Math.max(width, bbox.width);
|
||||
const outerHeight = height + bbox.height + labelPadding;
|
||||
|
||||
const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, {
|
||||
...options,
|
||||
fill: 'transparent',
|
||||
stroke: 'none',
|
||||
});
|
||||
|
||||
const iconShape = shapeSvg.insert(() => iconNode, ':first-child');
|
||||
const outerShape = shapeSvg.insert(() => outerNode);
|
||||
|
||||
if (node.icon) {
|
||||
const iconElem = shapeSvg.append('g');
|
||||
iconElem.html(
|
||||
`<g>${await getIconSVG(node.icon, {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fallbackPrefix: '',
|
||||
})}</g>`
|
||||
);
|
||||
const iconBBox = iconElem.node().getBBox();
|
||||
const iconWidth = iconBBox.width;
|
||||
const iconHeight = iconBBox.height;
|
||||
const iconX = iconBBox.x;
|
||||
const iconY = iconBBox.y;
|
||||
iconElem.attr(
|
||||
'transform',
|
||||
`translate(${-iconWidth / 2 - iconX},${
|
||||
topLabel
|
||||
? bbox.height / 2 + labelPadding / 2 - iconHeight / 2 - iconY
|
||||
: -bbox.height / 2 - labelPadding / 2 - iconHeight / 2 - iconY
|
||||
})`
|
||||
);
|
||||
iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') ?? nodeBorder);
|
||||
}
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))},${
|
||||
topLabel ? -outerHeight / 2 : outerHeight / 2 - bbox.height
|
||||
})`
|
||||
);
|
||||
|
||||
iconShape.attr(
|
||||
'transform',
|
||||
`translate(${0},${
|
||||
topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2
|
||||
})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, outerShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('iconSquare intersect', node, point);
|
||||
if (!node.label) {
|
||||
return intersect.rect(node, point);
|
||||
}
|
||||
const dx = node.x ?? 0;
|
||||
const dy = node.y ?? 0;
|
||||
const nodeHeight = node.height ?? 0;
|
||||
let points = [];
|
||||
if (topLabel) {
|
||||
points = [
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
];
|
||||
} else {
|
||||
points = [
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 + height },
|
||||
];
|
||||
}
|
||||
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,141 @@
|
||||
import rough from 'roughjs';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import { getIconSVG } from '../../icons.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.d.ts';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { labelHelper, updateNodeBounds } from './util.js';
|
||||
|
||||
export const iconSquare = async (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ config: { themeVariables, flowchart } }: ShapeRenderOptions
|
||||
) => {
|
||||
const { labelStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const assetHeight = node.assetHeight ?? 48;
|
||||
const assetWidth = node.assetWidth ?? 48;
|
||||
const iconSize = Math.max(assetHeight, assetWidth);
|
||||
const defaultWidth = flowchart?.wrappingWidth;
|
||||
node.width = Math.max(iconSize, defaultWidth ?? 0);
|
||||
const { shapeSvg, bbox, halfPadding, label } = await labelHelper(
|
||||
parent,
|
||||
node,
|
||||
'icon-shape default'
|
||||
);
|
||||
|
||||
const topLabel = node.pos === 't';
|
||||
|
||||
const height = iconSize + halfPadding * 2;
|
||||
const width = iconSize + halfPadding * 2;
|
||||
const { nodeBorder, mainBkg } = themeVariables;
|
||||
const { stylesMap } = compileStyles(node);
|
||||
|
||||
const x = -width / 2;
|
||||
const y = -height / 2;
|
||||
|
||||
const labelPadding = node.label ? 8 : 0;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, { stroke: stylesMap.get('fill') || mainBkg });
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const iconNode = rc.rectangle(x, y, width, height, options);
|
||||
|
||||
const outerWidth = Math.max(width, bbox.width);
|
||||
const outerHeight = height + bbox.height + labelPadding;
|
||||
|
||||
const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, {
|
||||
...options,
|
||||
fill: 'transparent',
|
||||
stroke: 'none',
|
||||
});
|
||||
|
||||
const iconShape = shapeSvg.insert(() => iconNode, ':first-child');
|
||||
const outerShape = shapeSvg.insert(() => outerNode);
|
||||
|
||||
if (node.icon) {
|
||||
const iconElem = shapeSvg.append('g');
|
||||
iconElem.html(
|
||||
`<g>${await getIconSVG(node.icon, {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fallbackPrefix: '',
|
||||
})}</g>`
|
||||
);
|
||||
const iconBBox = iconElem.node().getBBox();
|
||||
const iconWidth = iconBBox.width;
|
||||
const iconHeight = iconBBox.height;
|
||||
const iconX = iconBBox.x;
|
||||
const iconY = iconBBox.y;
|
||||
iconElem.attr(
|
||||
'transform',
|
||||
`translate(${-iconWidth / 2 - iconX},${
|
||||
topLabel
|
||||
? bbox.height / 2 + labelPadding / 2 - iconHeight / 2 - iconY
|
||||
: -bbox.height / 2 - labelPadding / 2 - iconHeight / 2 - iconY
|
||||
})`
|
||||
);
|
||||
iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') ?? nodeBorder);
|
||||
}
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))},${
|
||||
topLabel ? -outerHeight / 2 : outerHeight / 2 - bbox.height
|
||||
})`
|
||||
);
|
||||
|
||||
iconShape.attr(
|
||||
'transform',
|
||||
`translate(${0},${
|
||||
topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2
|
||||
})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, outerShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('iconSquare intersect', node, point);
|
||||
if (!node.label) {
|
||||
return intersect.rect(node, point);
|
||||
}
|
||||
const dx = node.x ?? 0;
|
||||
const dy = node.y ?? 0;
|
||||
const nodeHeight = node.height ?? 0;
|
||||
let points = [];
|
||||
if (topLabel) {
|
||||
points = [
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
];
|
||||
} else {
|
||||
points = [
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + height },
|
||||
{ x: dx - width / 2, y: dy - nodeHeight / 2 + height },
|
||||
];
|
||||
}
|
||||
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,148 @@
|
||||
import rough from 'roughjs';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import type { Node, ShapeRenderOptions } from '../../types.d.ts';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import { labelHelper, updateNodeBounds } from './util.js';
|
||||
|
||||
export const imageSquare = async (
|
||||
parent: SVG,
|
||||
node: Node,
|
||||
{ config: { flowchart } }: ShapeRenderOptions
|
||||
) => {
|
||||
const img = new Image();
|
||||
img.src = node?.img ?? '';
|
||||
await img.decode();
|
||||
|
||||
const imageNaturalWidth = Number(img.naturalWidth.toString().replace('px', ''));
|
||||
const imageNaturalHeight = Number(img.naturalHeight.toString().replace('px', ''));
|
||||
node.imageAspectRatio = imageNaturalWidth / imageNaturalHeight;
|
||||
|
||||
const { labelStyles } = styles2String(node);
|
||||
|
||||
node.labelStyle = labelStyles;
|
||||
|
||||
const defaultWidth = flowchart?.wrappingWidth;
|
||||
node.defaultWidth = flowchart?.wrappingWidth;
|
||||
|
||||
const imageRawWidth = Math.max(
|
||||
node.label ? (defaultWidth ?? 0) : 0,
|
||||
node?.assetWidth ?? imageNaturalWidth
|
||||
);
|
||||
|
||||
const imageWidth =
|
||||
node.constraint === 'on'
|
||||
? node?.assetHeight
|
||||
? node.assetHeight * node.imageAspectRatio
|
||||
: imageRawWidth
|
||||
: imageRawWidth;
|
||||
|
||||
const imageHeight =
|
||||
node.constraint === 'on'
|
||||
? imageWidth / node.imageAspectRatio
|
||||
: (node?.assetHeight ?? imageNaturalHeight);
|
||||
node.width = Math.max(imageWidth, defaultWidth ?? 0);
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'image-shape default');
|
||||
|
||||
const topLabel = node.pos === 't';
|
||||
|
||||
const x = -imageWidth / 2;
|
||||
const y = -imageHeight / 2;
|
||||
|
||||
const labelPadding = node.label ? 8 : 0;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const imageNode = rc.rectangle(x, y, imageWidth, imageHeight, options);
|
||||
|
||||
const outerWidth = Math.max(imageWidth, bbox.width);
|
||||
const outerHeight = imageHeight + bbox.height + labelPadding;
|
||||
|
||||
const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, {
|
||||
...options,
|
||||
fill: 'none',
|
||||
stroke: 'none',
|
||||
});
|
||||
|
||||
const iconShape = shapeSvg.insert(() => imageNode, ':first-child');
|
||||
const outerShape = shapeSvg.insert(() => outerNode);
|
||||
|
||||
if (node.img) {
|
||||
const image = shapeSvg.append('image');
|
||||
|
||||
// Set the image attributes
|
||||
image.attr('href', node.img);
|
||||
image.attr('width', imageWidth);
|
||||
image.attr('height', imageHeight);
|
||||
image.attr('preserveAspectRatio', 'none');
|
||||
|
||||
image.attr(
|
||||
'transform',
|
||||
`translate(${-imageWidth / 2},${topLabel ? outerHeight / 2 - imageHeight : -outerHeight / 2})`
|
||||
);
|
||||
}
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))},${
|
||||
topLabel
|
||||
? -imageHeight / 2 - bbox.height / 2 - labelPadding / 2
|
||||
: imageHeight / 2 - bbox.height / 2 + labelPadding / 2
|
||||
})`
|
||||
);
|
||||
|
||||
iconShape.attr(
|
||||
'transform',
|
||||
`translate(${0},${
|
||||
topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2
|
||||
})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, outerShape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('iconSquare intersect', node, point);
|
||||
if (!node.label) {
|
||||
return intersect.rect(node, point);
|
||||
}
|
||||
const dx = node.x ?? 0;
|
||||
const dy = node.y ?? 0;
|
||||
const nodeHeight = node.height ?? 0;
|
||||
let points = [];
|
||||
if (topLabel) {
|
||||
points = [
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + imageWidth / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx + imageWidth / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - imageWidth / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - imageWidth / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding },
|
||||
];
|
||||
} else {
|
||||
points = [
|
||||
{ x: dx - imageWidth / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + imageWidth / 2, y: dy - nodeHeight / 2 },
|
||||
{ x: dx + imageWidth / 2, y: dy - nodeHeight / 2 + imageHeight },
|
||||
{ x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + imageHeight },
|
||||
{ x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy + nodeHeight / 2 },
|
||||
{ x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + imageHeight },
|
||||
{ x: dx - imageWidth / 2, y: dy - nodeHeight / 2 + imageHeight },
|
||||
];
|
||||
}
|
||||
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -1,47 +1,48 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import { insertPolygonShape } from './insertPolygonShape.js';
|
||||
|
||||
export const createInvertedTrapezoidPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x + height / 6},${y}`,
|
||||
`L${x + width - height / 6},${y}`,
|
||||
`L${x + width + (2 * height) / 6},${y - height}`,
|
||||
`L${x - (2 * height) / 6},${y - height}`,
|
||||
'Z',
|
||||
].join(' ');
|
||||
};
|
||||
// export const createInvertedTrapezoidPathD = (
|
||||
// x: number,
|
||||
// y: number,
|
||||
// width: number,
|
||||
// height: number
|
||||
// ): string => {
|
||||
// return [
|
||||
// `M${x + height / 6},${y}`,
|
||||
// `L${x + width - height / 6},${y}`,
|
||||
// `L${x + width + (2 * height) / 6},${y - height}`,
|
||||
// `L${x - (2 * height) / 6},${y - height}`,
|
||||
// 'Z',
|
||||
// ].join(' ');
|
||||
// };
|
||||
|
||||
export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
|
||||
const h = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
|
||||
|
||||
const points = [
|
||||
{ x: h / 6, y: 0 },
|
||||
{ x: w - h / 6, y: 0 },
|
||||
{ x: w + (2 * h) / 6, y: -h },
|
||||
{ x: (-2 * h) / 6, y: -h },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w + (3 * h) / 6, y: -h },
|
||||
{ x: (-3 * h) / 6, y: -h },
|
||||
];
|
||||
|
||||
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createInvertedTrapezoidPathD(0, 0, w, h);
|
||||
const pathData = createPathFromPoints(points);
|
||||
// const pathData = createInvertedTrapezoidPathD(0, 0, w, h);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
polygon = shapeSvg
|
||||
|
@@ -16,7 +16,7 @@ export const roundedRect = async (parent: SVGAElement, node: Node) => {
|
||||
};
|
||||
|
||||
export const labelRect = async (parent: SVGElement, node: Node) => {
|
||||
const { shapeSvg } = await labelHelper(parent, node, 'label');
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'label');
|
||||
|
||||
// log.trace('Classes = ', node.class);
|
||||
// add the rect
|
||||
@@ -27,6 +27,10 @@ export const labelRect = async (parent: SVGElement, node: Node) => {
|
||||
const totalHeight = 0.1;
|
||||
rect.attr('width', totalWidth).attr('height', totalHeight);
|
||||
shapeSvg.attr('class', 'label edgeLabel');
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-(bbox.width / 2) - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
// if (node.props) {
|
||||
// const propKeys = new Set(Object.keys(node.props));
|
||||
|
@@ -1,47 +1,31 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import { insertPolygonShape } from './insertPolygonShape.js';
|
||||
|
||||
export const createLeanLeftPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x + (2 * height) / 6},${y}`,
|
||||
`L${x + width + height / 6},${y}`,
|
||||
`L${x + width - (2 * height) / 6},${y - height}`,
|
||||
`L${x - height / 6},${y - height}`,
|
||||
'Z',
|
||||
].join(' ');
|
||||
};
|
||||
|
||||
export const lean_left = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0), node?.width ?? 0);
|
||||
const h = Math.max(bbox.height + (node.padding ?? 0), node?.height ?? 0);
|
||||
const points = [
|
||||
{ x: (2 * h) / 6, y: 0 },
|
||||
{ x: w + h / 6, y: 0 },
|
||||
{ x: w - (2 * h) / 6, y: -h },
|
||||
{ x: -h / 6, y: -h },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w + (3 * h) / 6, y: 0 },
|
||||
{ x: w, y: -h },
|
||||
{ x: -(3 * h) / 6, y: -h },
|
||||
];
|
||||
|
||||
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createLeanLeftPathD(0, 0, w, h);
|
||||
const pathData = createPathFromPoints(points);
|
||||
// const pathData = createLeanLeftPathD(0, 0, w, h);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
polygon = shapeSvg
|
||||
|
@@ -1,47 +1,30 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import { insertPolygonShape } from './insertPolygonShape.js';
|
||||
|
||||
export const createLeanRightPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x - (2 * height) / 6},${y}`,
|
||||
`L${x + width - height / 6},${y}`,
|
||||
`L${x + width + (2 * height) / 6},${y - height}`,
|
||||
`L${x + height / 6},${y - height}`,
|
||||
'Z',
|
||||
].join(' ');
|
||||
};
|
||||
|
||||
export const lean_right = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0), node?.width ?? 0);
|
||||
const h = Math.max(bbox.height + (node.padding ?? 0), node?.height ?? 0);
|
||||
const points = [
|
||||
{ x: (-2 * h) / 6, y: 0 },
|
||||
{ x: w - h / 6, y: 0 },
|
||||
{ x: w + (2 * h) / 6, y: -h },
|
||||
{ x: h / 6, y: -h },
|
||||
{ x: (-3 * h) / 6, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w + (3 * h) / 6, y: -h },
|
||||
{ x: 0, y: -h },
|
||||
];
|
||||
|
||||
let polygon: d3.Selection<SVGPolygonElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
// @ts-ignore - rough is not typed
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createLeanRightPathD(0, 0, w, h);
|
||||
const pathData = createPathFromPoints(points);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
polygon = shapeSvg
|
||||
|
@@ -0,0 +1,66 @@
|
||||
import { log } from '../../../logger.js';
|
||||
import { getNodeClasses, updateNodeBounds } from './util.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import type { SVG } from '../../../diagram-api/types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import intersect from '../intersect/index.js';
|
||||
import { createPathFromPoints } from './util.js';
|
||||
|
||||
export const lightningBolt = (parent: SVG, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.label = '';
|
||||
node.labelStyle = labelStyles;
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', getNodeClasses(node))
|
||||
.attr('id', node.domId ?? node.id);
|
||||
const { cssStyles } = node;
|
||||
const width = Math.max(35, node?.width ?? 0);
|
||||
const height = Math.max(35, node?.height ?? 0);
|
||||
const gap = 7;
|
||||
|
||||
const points = [
|
||||
{ x: width, y: 0 },
|
||||
{ x: 0, y: height + gap / 2 },
|
||||
{ x: width - 2 * gap, y: height + gap / 2 },
|
||||
{ x: 0, y: 2 * height },
|
||||
{ x: width, y: height - gap / 2 },
|
||||
{ x: 2 * gap, y: height - gap / 2 },
|
||||
];
|
||||
|
||||
// @ts-expect-error shapeSvg d3 class is incorrect?
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const linePath = createPathFromPoints(points);
|
||||
const lineNode = rc.path(linePath, options);
|
||||
|
||||
const lightningBolt = shapeSvg.insert(() => lineNode, ':first-child');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
lightningBolt.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
lightningBolt.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
lightningBolt.attr('transform', `translate(-${width / 2},${-height})`);
|
||||
|
||||
updateNodeBounds(node, lightningBolt);
|
||||
|
||||
node.intersect = function (point) {
|
||||
log.info('lightningBolt intersect', node, point);
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,133 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.js';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
|
||||
export const createCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number,
|
||||
outerOffset: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x},${y + ry}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`a${rx},${ry} 0,0,0 ${-width},0`,
|
||||
`l0,${height}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`l0,${-height}`,
|
||||
`M${x},${y + ry + outerOffset}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
].join(' ');
|
||||
};
|
||||
export const createOuterCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number,
|
||||
outerOffset: number
|
||||
): string => {
|
||||
return [
|
||||
`M${x},${y + ry}`,
|
||||
`M${x + width},${y + ry}`,
|
||||
`a${rx},${ry} 0,0,0 ${-width},0`,
|
||||
`l0,${height}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
`l0,${-height}`,
|
||||
`M${x},${y + ry + outerOffset}`,
|
||||
`a${rx},${ry} 0,0,0 ${width},0`,
|
||||
].join(' ');
|
||||
};
|
||||
export const createInnerCylinderPathD = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
rx: number,
|
||||
ry: number
|
||||
): string => {
|
||||
return [`M${x - width / 2},${-height / 2}`, `a${rx},${ry} 0,0,0 ${width},0`].join(' ');
|
||||
};
|
||||
export const linedCylinder = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0), node.width ?? 0);
|
||||
const rx = w / 2;
|
||||
const ry = rx / (2.5 + w / 50);
|
||||
const h = Math.max(bbox.height + ry + (node.padding ?? 0), node.height ?? 0);
|
||||
const outerOffset = h * 0.1; // 10% of height
|
||||
|
||||
let cylinder: d3.Selection<SVGPathElement | SVGGElement, unknown, null, undefined>;
|
||||
const { cssStyles } = node;
|
||||
|
||||
if (node.look === 'handDrawn') {
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const outerPathData = createOuterCylinderPathD(0, 0, w, h, rx, ry, outerOffset);
|
||||
const innerPathData = createInnerCylinderPathD(0, ry, w, h, rx, ry);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
const outerNode = rc.path(outerPathData, options);
|
||||
const innerLine = rc.path(innerPathData, options);
|
||||
|
||||
const innerLineEl = shapeSvg.insert(() => innerLine, ':first-child');
|
||||
innerLineEl.attr('class', 'line');
|
||||
cylinder = shapeSvg.insert(() => outerNode, ':first-child');
|
||||
cylinder.attr('class', 'basic label-container');
|
||||
if (cssStyles) {
|
||||
cylinder.attr('style', cssStyles);
|
||||
}
|
||||
} else {
|
||||
const pathData = createCylinderPathD(0, 0, w, h, rx, ry, outerOffset);
|
||||
cylinder = shapeSvg
|
||||
.insert('path', ':first-child')
|
||||
.attr('d', pathData)
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', cssStyles)
|
||||
.attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
// find label and move it down
|
||||
cylinder.attr('label-offset-y', ry);
|
||||
cylinder.attr('transform', `translate(${-w / 2}, ${-(h / 2 + ry)})`);
|
||||
|
||||
updateNodeBounds(node, cylinder);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-(bbox.width / 2) - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + ry - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.rect(node, point);
|
||||
const x = pos.x - (node.x ?? 0);
|
||||
|
||||
if (
|
||||
rx != 0 &&
|
||||
(Math.abs(x) < (node.width ?? 0) / 2 ||
|
||||
(Math.abs(x) == (node.width ?? 0) / 2 &&
|
||||
Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry))
|
||||
) {
|
||||
let y = ry * ry * (1 - (x * x) / (rx * rx));
|
||||
if (y > 0) {
|
||||
y = Math.sqrt(y);
|
||||
}
|
||||
y = ry - y;
|
||||
if (point.y - (node.y ?? 0) > 0) {
|
||||
y = -y;
|
||||
}
|
||||
|
||||
pos.y += y;
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
labelHelper,
|
||||
updateNodeBounds,
|
||||
getNodeClasses,
|
||||
generateFullSineWavePoints,
|
||||
} from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import rough from 'roughjs';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
|
||||
export const linedWaveEdgedRect = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
|
||||
const h = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
|
||||
const waveAmplitude = h / 4;
|
||||
const finalH = h + waveAmplitude;
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const points = [
|
||||
{ x: -w / 2 - (w / 2) * 0.1, y: -finalH / 2 },
|
||||
{ x: -w / 2 - (w / 2) * 0.1, y: finalH / 2 },
|
||||
...generateFullSineWavePoints(
|
||||
-w / 2 - (w / 2) * 0.1,
|
||||
finalH / 2,
|
||||
w / 2 + (w / 2) * 0.1,
|
||||
finalH / 2,
|
||||
waveAmplitude,
|
||||
0.8
|
||||
),
|
||||
{ x: w / 2 + (w / 2) * 0.1, y: -finalH / 2 },
|
||||
{ x: -w / 2 - (w / 2) * 0.1, y: -finalH / 2 },
|
||||
{ x: -w / 2, y: -finalH / 2 },
|
||||
{ x: -w / 2, y: (finalH / 2) * 1.1 },
|
||||
{ x: -w / 2, y: -finalH / 2 },
|
||||
];
|
||||
|
||||
const poly = rc.polygon(
|
||||
points.map((p) => [p.x, p.y]),
|
||||
options
|
||||
);
|
||||
|
||||
const waveEdgeRect = shapeSvg.insert(() => poly, ':first-child');
|
||||
|
||||
waveEdgeRect.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
waveEdgeRect.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
waveEdgeRect.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
waveEdgeRect.attr('transform', `translate(0,${-waveAmplitude / 2})`);
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-w / 2 + (node.padding ?? 0) + ((w / 2) * 0.1) / 2 - (bbox.x - (bbox.left ?? 0))},${-h / 2 + (node.padding ?? 0) - waveAmplitude / 2 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, waveEdgeRect);
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, points, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,81 @@
|
||||
import { labelHelper, getNodeClasses, updateNodeBounds, createPathFromPoints } from './util.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
import rough from 'roughjs';
|
||||
import intersect from '../intersect/index.js';
|
||||
|
||||
export const multiRect = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
|
||||
const h = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
|
||||
const rectOffset = 5;
|
||||
const x = -w / 2;
|
||||
const y = -h / 2;
|
||||
const { cssStyles } = node;
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
const outerPathPoints = [
|
||||
{ x: x - rectOffset, y: y + rectOffset },
|
||||
{ x: x - rectOffset, y: y + h + rectOffset },
|
||||
{ x: x + w - rectOffset, y: y + h + rectOffset },
|
||||
{ x: x + w - rectOffset, y: y + h },
|
||||
{ x: x + w, y: y + h },
|
||||
{ x: x + w, y: y + h - rectOffset },
|
||||
{ x: x + w + rectOffset, y: y + h - rectOffset },
|
||||
{ x: x + w + rectOffset, y: y - rectOffset },
|
||||
{ x: x + rectOffset, y: y - rectOffset },
|
||||
{ x: x + rectOffset, y: y },
|
||||
{ x, y },
|
||||
{ x, y: y + rectOffset },
|
||||
];
|
||||
|
||||
const innerPathPoints = [
|
||||
{ x, y: y + rectOffset },
|
||||
{ x: x + w - rectOffset, y: y + rectOffset },
|
||||
{ x: x + w - rectOffset, y: y + h },
|
||||
{ x: x + w, y: y + h },
|
||||
{ x: x + w, y },
|
||||
{ x, y },
|
||||
];
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const outerPath = createPathFromPoints(outerPathPoints);
|
||||
const outerNode = rc.path(outerPath, options);
|
||||
const innerPath = createPathFromPoints(innerPathPoints);
|
||||
const innerNode = rc.path(innerPath, { ...options, fill: 'none' });
|
||||
|
||||
const multiRect = shapeSvg.insert(() => innerNode, ':first-child');
|
||||
multiRect.insert(() => outerNode, ':first-child');
|
||||
|
||||
multiRect.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
multiRect.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
multiRect.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-(bbox.width / 2) - rectOffset - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + rectOffset - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, multiRect);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, outerPathPoints, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
labelHelper,
|
||||
updateNodeBounds,
|
||||
getNodeClasses,
|
||||
createPathFromPoints,
|
||||
generateFullSineWavePoints,
|
||||
} from './util.js';
|
||||
import intersect from '../intersect/index.js';
|
||||
import type { Node } from '../../types.d.ts';
|
||||
import rough from 'roughjs';
|
||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||
|
||||
export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) => {
|
||||
const { labelStyles, nodeStyles } = styles2String(node);
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
const w = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0);
|
||||
const h = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0);
|
||||
const waveAmplitude = h / 4;
|
||||
const finalH = h + waveAmplitude;
|
||||
const x = -w / 2;
|
||||
const y = -finalH / 2;
|
||||
const rectOffset = 5;
|
||||
|
||||
const { cssStyles } = node;
|
||||
|
||||
const wavePoints = generateFullSineWavePoints(
|
||||
x - rectOffset,
|
||||
y + finalH + rectOffset,
|
||||
x + w - rectOffset,
|
||||
y + finalH + rectOffset,
|
||||
waveAmplitude,
|
||||
0.8
|
||||
);
|
||||
|
||||
const lastWavePoint = wavePoints?.[wavePoints.length - 1];
|
||||
|
||||
const outerPathPoints = [
|
||||
{ x: x - rectOffset, y: y + rectOffset },
|
||||
{ x: x - rectOffset, y: y + finalH + rectOffset },
|
||||
...wavePoints,
|
||||
{ x: x + w - rectOffset, y: lastWavePoint.y - rectOffset },
|
||||
{ x: x + w, y: lastWavePoint.y - rectOffset },
|
||||
{ x: x + w, y: lastWavePoint.y - 2 * rectOffset },
|
||||
{ x: x + w + rectOffset, y: lastWavePoint.y - 2 * rectOffset },
|
||||
{ x: x + w + rectOffset, y: y - rectOffset },
|
||||
{ x: x + rectOffset, y: y - rectOffset },
|
||||
{ x: x + rectOffset, y: y },
|
||||
{ x, y },
|
||||
{ x, y: y + rectOffset },
|
||||
];
|
||||
|
||||
const innerPathPoints = [
|
||||
{ x, y: y + rectOffset },
|
||||
{ x: x + w - rectOffset, y: y + rectOffset },
|
||||
{ x: x + w - rectOffset, y: lastWavePoint.y - rectOffset },
|
||||
{ x: x + w, y: lastWavePoint.y - rectOffset },
|
||||
{ x: x + w, y },
|
||||
{ x, y },
|
||||
];
|
||||
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
const outerPath = createPathFromPoints(outerPathPoints);
|
||||
const outerNode = rc.path(outerPath, options);
|
||||
const innerPath = createPathFromPoints(innerPathPoints);
|
||||
const innerNode = rc.path(innerPath, options);
|
||||
|
||||
const shape = shapeSvg.insert(() => outerNode, ':first-child');
|
||||
shape.insert(() => innerNode);
|
||||
|
||||
shape.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
shape.selectAll('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
shape.selectAll('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
shape.attr('transform', `translate(0,${-waveAmplitude / 2})`);
|
||||
|
||||
label.attr(
|
||||
'transform',
|
||||
`translate(${-(bbox.width / 2) - rectOffset - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + rectOffset - waveAmplitude / 2 - (bbox.y - (bbox.top ?? 0))})`
|
||||
);
|
||||
|
||||
updateNodeBounds(node, shape);
|
||||
|
||||
node.intersect = function (point) {
|
||||
const pos = intersect.polygon(node, outerPathPoints, point);
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user