Compare commits
598 Commits
8.4.3
...
untagged-5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ac48a431a | ||
|
|
7b15fefcaa | ||
|
|
6d076284e8 | ||
|
|
d7d248fd04 | ||
|
|
a636aa592b | ||
|
|
1fc6603f41 | ||
|
|
d36a233cbf | ||
|
|
2a11f42318 | ||
|
|
e23383be41 | ||
|
|
36a47febc2 | ||
|
|
053a86c0d4 | ||
|
|
3ded1b6c6e | ||
|
|
28ea68fe7f | ||
|
|
6203c27244 | ||
|
|
57fb800c77 | ||
|
|
9a3ec31191 | ||
|
|
8ef3aee7e7 | ||
|
|
a20e6086cc | ||
|
|
66fa8a7595 | ||
|
|
a8f9e3f0b0 | ||
|
|
215905213b | ||
|
|
f8865c1413 | ||
|
|
67aed51742 | ||
|
|
2be2b10e59 | ||
|
|
ed5e194b8e | ||
|
|
a6b4cb24b8 | ||
|
|
4276fc0b0b | ||
|
|
0d10957441 | ||
|
|
368720538c | ||
|
|
d068a460bd | ||
|
|
346ff199c6 | ||
|
|
480a4a9d38 | ||
|
|
77cfd0e1a6 | ||
|
|
950af2e753 | ||
|
|
aa2845a730 | ||
|
|
aedf066337 | ||
|
|
b975cf3ed4 | ||
|
|
f2af5ce7eb | ||
|
|
fa612b731c | ||
|
|
0933268e61 | ||
|
|
3aa5fc0cc2 | ||
|
|
397f57accb | ||
|
|
e3b1944e31 | ||
|
|
507582f40b | ||
|
|
08e015a951 | ||
|
|
ef17f4eacb | ||
|
|
3d185dc622 | ||
|
|
76b4b88e4b | ||
|
|
fd37edc53f | ||
|
|
5662c06a33 | ||
|
|
0aede618ec | ||
|
|
e52d4ce033 | ||
|
|
b646672d3e | ||
|
|
a1a7d44fd7 | ||
|
|
25ea221a6a | ||
|
|
38ee4f556e | ||
|
|
862f20ef20 | ||
|
|
5c902001b9 | ||
|
|
5f6887b316 | ||
|
|
45b96bd568 | ||
|
|
197d006860 | ||
|
|
49f8aac6a9 | ||
|
|
22dd50a047 | ||
|
|
81d9b5afd6 | ||
|
|
59f3d2a11e | ||
|
|
134a170a61 | ||
|
|
681f006ae1 | ||
|
|
36abfcc79b | ||
|
|
249a14f84a | ||
|
|
10fdc45dea | ||
|
|
e8e7b419b5 | ||
|
|
955fc58773 | ||
|
|
2da3d83420 | ||
|
|
c3f2e8dde1 | ||
|
|
4e4c80b7fa | ||
|
|
3e776c8c41 | ||
|
|
7c82e4b6c1 | ||
|
|
12d26d5a3d | ||
|
|
d228769b1b | ||
|
|
433f88b459 | ||
|
|
704d56d193 | ||
|
|
5f4da6e0bc | ||
|
|
0ce89d53d7 | ||
|
|
5bdffd7d20 | ||
|
|
5996920249 | ||
|
|
f0119085e4 | ||
|
|
22e17172dd | ||
|
|
6ec731e263 | ||
|
|
7cd8b6f938 | ||
|
|
a4a19845d4 | ||
|
|
fffb0eafde | ||
|
|
10f60e2807 | ||
|
|
3db27e6017 | ||
|
|
91b64798bb | ||
|
|
8455db6fae | ||
|
|
530648e937 | ||
|
|
7f67435cb9 | ||
|
|
d8eba411ec | ||
|
|
dcfa903176 | ||
|
|
fc70a8e145 | ||
|
|
e4a2d7dfb7 | ||
|
|
f081527731 | ||
|
|
9f8a0234aa | ||
|
|
857c860952 | ||
|
|
cff68fc062 | ||
|
|
365c741864 | ||
|
|
366f9db8a8 | ||
|
|
9d5303c59d | ||
|
|
c527392e21 | ||
|
|
933cc333cc | ||
|
|
e72c3b2c86 | ||
|
|
231c849451 | ||
|
|
a5af248715 | ||
|
|
8fede48e7b | ||
|
|
d0492ec7bd | ||
|
|
b5399202b6 | ||
|
|
240077ffe8 | ||
|
|
eee9a1661f | ||
|
|
9199546dca | ||
|
|
e0d97d44aa | ||
|
|
5fbb69e7c5 | ||
|
|
3a6aa8f820 | ||
|
|
44ae5c86a7 | ||
|
|
85f47e1693 | ||
|
|
9a96f8b155 | ||
|
|
391dc06013 | ||
|
|
598b392878 | ||
|
|
9e75993658 | ||
|
|
159a3a3706 | ||
|
|
cbdb2e6e6f | ||
|
|
d409da2201 | ||
|
|
c7aa67b07a | ||
|
|
5b302ae2e6 | ||
|
|
df2925e51c | ||
|
|
02b19fed27 | ||
|
|
17f06564a2 | ||
|
|
6ddd394e33 | ||
|
|
d3f78299e7 | ||
|
|
3e76b2374c | ||
|
|
a3b97f7c24 | ||
|
|
4f50e36e5b | ||
|
|
a0bbbe1404 | ||
|
|
50f983871b | ||
|
|
2d3b02df6a | ||
|
|
4010c89899 | ||
|
|
215e930da4 | ||
|
|
5b74d75011 | ||
|
|
9266eaef16 | ||
|
|
963a0ce6ef | ||
|
|
5f5e453fb3 | ||
|
|
0cacd111df | ||
|
|
d86a7ccf3e | ||
|
|
35ea4d1a00 | ||
|
|
10583610bd | ||
|
|
55816a09fe | ||
|
|
ad288be2ee | ||
|
|
c9071ff724 | ||
|
|
96b8dce982 | ||
|
|
1e498eccb6 | ||
|
|
da048cbc7b | ||
|
|
7bd5529bb7 | ||
|
|
1b64af143e | ||
|
|
d2685f2544 | ||
|
|
83c6aef078 | ||
|
|
a9570f7298 | ||
|
|
71e0a788d1 | ||
|
|
e6910d3c52 | ||
|
|
9a6e4ba77f | ||
|
|
bab4649a1e | ||
|
|
5a763230e5 | ||
|
|
09e6804761 | ||
|
|
a414aa27b6 | ||
|
|
c598fd9ec4 | ||
|
|
a8eeb7f76a | ||
|
|
d28e3374c7 | ||
|
|
c3e9ae0ca0 | ||
|
|
71cfec91af | ||
|
|
b085222253 | ||
|
|
29b6e00071 | ||
|
|
9aacc85a16 | ||
|
|
aa32d454c9 | ||
|
|
d3b69abd87 | ||
|
|
df88300e6e | ||
|
|
e90ed7a853 | ||
|
|
fdaf13bdc4 | ||
|
|
d96d40e0b0 | ||
|
|
5dae0c14d9 | ||
|
|
5cd54c4831 | ||
|
|
4caa02474c | ||
|
|
005defbb5c | ||
|
|
061045016f | ||
|
|
997353ab71 | ||
|
|
1570ed4610 | ||
|
|
25e2d78311 | ||
|
|
1e2d014ac9 | ||
|
|
a0b1d16e79 | ||
|
|
6985391437 | ||
|
|
e90f58a0a7 | ||
|
|
0555fca5d8 | ||
|
|
2a55e74e11 | ||
|
|
94ace2348f | ||
|
|
2b58a2a176 | ||
|
|
7bc82365c6 | ||
|
|
de11dc43ab | ||
|
|
756927b6f8 | ||
|
|
5693c69457 | ||
|
|
cda41a1506 | ||
|
|
890298457a | ||
|
|
ccfc7351e7 | ||
|
|
df6c5dd22e | ||
|
|
60f23c838b | ||
|
|
dcd807c49b | ||
|
|
575b921a15 | ||
|
|
67473f07b1 | ||
|
|
3aa368b630 | ||
|
|
4e044d66b4 | ||
|
|
065a5d2a47 | ||
|
|
5103da09c5 | ||
|
|
33ea1d44fb | ||
|
|
e62027b73c | ||
|
|
26c43667aa | ||
|
|
92fbc26852 | ||
|
|
a4e488a1eb | ||
|
|
0ee9c69ddf | ||
|
|
dd5e6c18d5 | ||
|
|
737e95c3f9 | ||
|
|
4b064dc19f | ||
|
|
f2e5c913d6 | ||
|
|
8758bd57ca | ||
|
|
73e31373a8 | ||
|
|
990e620dc7 | ||
|
|
9103e58314 | ||
|
|
a26fe71171 | ||
|
|
7a44e46d6d | ||
|
|
b376e4da1f | ||
|
|
f8481f8d4c | ||
|
|
54f55cc021 | ||
|
|
a7a4f38d58 | ||
|
|
d67e49400f | ||
|
|
6789f2c1e3 | ||
|
|
e8a3ffcb41 | ||
|
|
9607c743a0 | ||
|
|
553b964ab8 | ||
|
|
72ac408429 | ||
|
|
8456905960 | ||
|
|
13261c974a | ||
|
|
eafabdce36 | ||
|
|
d02a57744c | ||
|
|
58374add90 | ||
|
|
a52d501ba2 | ||
|
|
702abbad6d | ||
|
|
2d0edb5270 | ||
|
|
7aab34b540 | ||
|
|
d637a13f59 | ||
|
|
17f99aeed8 | ||
|
|
8729414da9 | ||
|
|
cd8b7e5528 | ||
|
|
6d74c5663f | ||
|
|
5ccca5e329 | ||
|
|
6c7b7b796e | ||
|
|
4ee3cea6ce | ||
|
|
1c07e550bb | ||
|
|
50c71ebb01 | ||
|
|
348d086c11 | ||
|
|
89c1853977 | ||
|
|
3acff54837 | ||
|
|
c7466f7a1a | ||
|
|
6420ad9f69 | ||
|
|
b30823d5ac | ||
|
|
864472d648 | ||
|
|
36b02ec008 | ||
|
|
aad080aa1b | ||
|
|
7b7a6e10f8 | ||
|
|
aeccf92375 | ||
|
|
f90d271739 | ||
|
|
9c2fde940d | ||
|
|
2b57cc6be8 | ||
|
|
b62819e3fb | ||
|
|
3eb5f2c65b | ||
|
|
41be7f951c | ||
|
|
7812fdb6ed | ||
|
|
4eeb2a0583 | ||
|
|
3dd0c537ee | ||
|
|
e0aa42f695 | ||
|
|
5dddf2f757 | ||
|
|
f8137ea7c1 | ||
|
|
cf6f1a083e | ||
|
|
f9cfeacc67 | ||
|
|
98229a47b2 | ||
|
|
530ace1507 | ||
|
|
792c657ff2 | ||
|
|
5aa07439a8 | ||
|
|
bf2862f164 | ||
|
|
d9ef227435 | ||
|
|
324c845dc4 | ||
|
|
5d0c44e84b | ||
|
|
01a71c3dbb | ||
|
|
b018f026e3 | ||
|
|
0a38e3557d | ||
|
|
6f450272b8 | ||
|
|
7e01f9be6e | ||
|
|
00d2a1fe9f | ||
|
|
18d0d435c6 | ||
|
|
f298c154e2 | ||
|
|
822df37f8c | ||
|
|
73cac95255 | ||
|
|
a0e7789d50 | ||
|
|
c825b37cc6 | ||
|
|
2dd4aa31e5 | ||
|
|
c95adfaf74 | ||
|
|
9da8ea5967 | ||
|
|
4d5ecc5518 | ||
|
|
963a0fcd77 | ||
|
|
fbd8dfa5be | ||
|
|
c43f791b29 | ||
|
|
39b873cd5f | ||
|
|
ec90537181 | ||
|
|
4709195a1d | ||
|
|
0082c23a90 | ||
|
|
c7b559f449 | ||
|
|
d571c7e08e | ||
|
|
b6f0b25f24 | ||
|
|
5ea6bd5980 | ||
|
|
ea29b67c5c | ||
|
|
1f851bc04c | ||
|
|
0921007d92 | ||
|
|
4a2e0bfa00 | ||
|
|
8ebc587fb5 | ||
|
|
c7b46f4fba | ||
|
|
4ad354a561 | ||
|
|
587592449a | ||
|
|
425b071a50 | ||
|
|
4dd72e6793 | ||
|
|
d2d4a24229 | ||
|
|
4805394e61 | ||
|
|
5cbd723f8d | ||
|
|
28153656d7 | ||
|
|
d2eaf16751 | ||
|
|
e0d98b2e14 | ||
|
|
c38f053294 | ||
|
|
921d274579 | ||
|
|
c9f95a1866 | ||
|
|
99469f8404 | ||
|
|
417d2c0336 | ||
|
|
2a3de1a090 | ||
|
|
d9318c5af3 | ||
|
|
0e02cf5c86 | ||
|
|
45277affe8 | ||
|
|
c564a843fa | ||
|
|
efa810da91 | ||
|
|
c0e1c90497 | ||
|
|
b1bfdec473 | ||
|
|
244f423baf | ||
|
|
8897b32cd3 | ||
|
|
5493fadc9e | ||
|
|
eade3d0a2d | ||
|
|
36f9eca33e | ||
|
|
4dd90e56b1 | ||
|
|
5e7484a12d | ||
|
|
fdbc44e41b | ||
|
|
e0d16dcb22 | ||
|
|
0a5ee0634f | ||
|
|
fa1331ffd5 | ||
|
|
57b5b9a7a6 | ||
|
|
58fbfc3c38 | ||
|
|
0af5e0b795 | ||
|
|
d7771eb4b6 | ||
|
|
fc0902c290 | ||
|
|
75890f88fa | ||
|
|
9a6b07e1e1 | ||
|
|
ce02d1dc98 | ||
|
|
7760e63b47 | ||
|
|
a483f2466f | ||
|
|
48c345a403 | ||
|
|
82b79aa332 | ||
|
|
ffbf9f265f | ||
|
|
144f65c459 | ||
|
|
18204586e1 | ||
|
|
1f3c970e7a | ||
|
|
1cfd93640c | ||
|
|
553ea4971f | ||
|
|
ac8b01a94c | ||
|
|
b3934c9788 | ||
|
|
37ae863443 | ||
|
|
f57356006d | ||
|
|
591a104c80 | ||
|
|
38097c9095 | ||
|
|
7f31e624ca | ||
|
|
e6fbfcb1e8 | ||
|
|
00687f21cd | ||
|
|
96fc0d043e | ||
|
|
190353785e | ||
|
|
f54adb4a3e | ||
|
|
f2a5d92398 | ||
|
|
b14c768fa2 | ||
|
|
851d2f2d91 | ||
|
|
4e6aad5115 | ||
|
|
7bd1408de0 | ||
|
|
d63eb396e1 | ||
|
|
465e99ab5d | ||
|
|
33916cfbd3 | ||
|
|
a1626927cc | ||
|
|
75e2abe242 | ||
|
|
2db8075e3f | ||
|
|
413876644c | ||
|
|
d5e9f2e23e | ||
|
|
bc59d015e6 | ||
|
|
2decf94ad0 | ||
|
|
b52744ae18 | ||
|
|
6cbbf0af2f | ||
|
|
5d7ef149aa | ||
|
|
7ce0974767 | ||
|
|
7b12c7a07d | ||
|
|
728e3fd2f0 | ||
|
|
4eedeebd46 | ||
|
|
85fb12155b | ||
|
|
f99daa696e | ||
|
|
b4f9dd46a9 | ||
|
|
ffe16f89bb | ||
|
|
7604d92d5c | ||
|
|
3abd77a1e6 | ||
|
|
d96b79a9ba | ||
|
|
14bb888c21 | ||
|
|
c8f59ea1a7 | ||
|
|
233520b797 | ||
|
|
061d31af33 | ||
|
|
ec742d3180 | ||
|
|
1bc62cfba4 | ||
|
|
028d9a2667 | ||
|
|
d53e00ffe0 | ||
|
|
b90fcc1b71 | ||
|
|
a4e7305d52 | ||
|
|
a3378f8d03 | ||
|
|
5716d163ec | ||
|
|
42ef035241 | ||
|
|
b6157195b3 | ||
|
|
b8ee68034d | ||
|
|
6598b1b10d | ||
|
|
4ff4058244 | ||
|
|
dddd5af830 | ||
|
|
26fac9507e | ||
|
|
1f1c9698ce | ||
|
|
56f7459617 | ||
|
|
86604b5de1 | ||
|
|
0544dbe891 | ||
|
|
6365cea0b2 | ||
|
|
7fef13346e | ||
|
|
81216e6ece | ||
|
|
1d0b305332 | ||
|
|
c10ccc011f | ||
|
|
3fe7995060 | ||
|
|
c441d04e8a | ||
|
|
52d84a99ac | ||
|
|
fcf20215a6 | ||
|
|
aa2f9622f8 | ||
|
|
1811318dea | ||
|
|
5eb50cb2b6 | ||
|
|
f6ef6ff7db | ||
|
|
2f33a80e1e | ||
|
|
363d49b655 | ||
|
|
de8c6d5572 | ||
|
|
9406bda93d | ||
|
|
d17a447a5f | ||
|
|
5b4e95484e | ||
|
|
bebea41e19 | ||
|
|
442474fc44 | ||
|
|
0d9112a4c1 | ||
|
|
d504e00e18 | ||
|
|
74c8e7fad9 | ||
|
|
4b781d3827 | ||
|
|
9fbcc5c32d | ||
|
|
c7ec8190fb | ||
|
|
b73a6d84ee | ||
|
|
2eaa7f1ab6 | ||
|
|
608445e64f | ||
|
|
1f4be77662 | ||
|
|
0deae4abf9 | ||
|
|
5b2f9351c7 | ||
|
|
6fdf30357c | ||
|
|
cf5d7478fc | ||
|
|
9a0df5afb0 | ||
|
|
813b2fcb38 | ||
|
|
08cbc0f187 | ||
|
|
d8251c8f79 | ||
|
|
5ea70baa6f | ||
|
|
d23ce9fb63 | ||
|
|
ddf8016a0c | ||
|
|
ab191abd5a | ||
|
|
c2e5e94b37 | ||
|
|
6a9b251be1 | ||
|
|
6b5185abfb | ||
|
|
2a41280076 | ||
|
|
91d986970b | ||
|
|
b4192bba7a | ||
|
|
9fe0aa0604 | ||
|
|
fc528749f8 | ||
|
|
5c71a3c85b | ||
|
|
94e768dd01 | ||
|
|
0fb91d6bcc | ||
|
|
02854881b4 | ||
|
|
4254781391 | ||
|
|
31f4f4096e | ||
|
|
e95e016378 | ||
|
|
c337c9128c | ||
|
|
5a38562bfc | ||
|
|
a3dd0e5f7d | ||
|
|
fcd1e106a5 | ||
|
|
346328156a | ||
|
|
3239f99ea8 | ||
|
|
93c32d3f29 | ||
|
|
98449dac3f | ||
|
|
32b60edda7 | ||
|
|
823c95bd9c | ||
|
|
e99d872f2b | ||
|
|
de8f8b02dc | ||
|
|
eec45dfff9 | ||
|
|
cce86c8e96 | ||
|
|
aabdc47c38 | ||
|
|
df10f7fbe7 | ||
|
|
71c531240f | ||
|
|
1e83207dac | ||
|
|
3311fcdc8e | ||
|
|
f368be925f | ||
|
|
075c57ca06 | ||
|
|
49fc80d506 | ||
|
|
74fc2fcfa9 | ||
|
|
7cc427e28d | ||
|
|
87c571412c | ||
|
|
ab093b2cde | ||
|
|
bbc4ede768 | ||
|
|
64c20dc528 | ||
|
|
50ea9bda89 | ||
|
|
2cf8c4e37a | ||
|
|
fe4719f656 | ||
|
|
1d43b7b316 | ||
|
|
78e4fead49 | ||
|
|
bea2e73b82 | ||
|
|
481e55e8da | ||
|
|
d26a67297a | ||
|
|
753bd7e1d9 | ||
|
|
b57492c1c6 | ||
|
|
4ff5c3b455 | ||
|
|
f62c47a757 | ||
|
|
a85bb0d86f | ||
|
|
5736d523dd | ||
|
|
5004b5723d | ||
|
|
ad2802d8e8 | ||
|
|
3fedd452a5 | ||
|
|
292bc3c4e5 | ||
|
|
512ba8e733 | ||
|
|
9a87ff684d | ||
|
|
f79fc21bfc | ||
|
|
ffddd58b6b | ||
|
|
087e5eaa32 | ||
|
|
cbd27831df | ||
|
|
f6028b63b6 | ||
|
|
a60e01db97 | ||
|
|
68c2ea38c9 | ||
|
|
3469cfca2f | ||
|
|
afd189d24c | ||
|
|
06cb09c267 | ||
|
|
15a37a5062 | ||
|
|
3f8f9eb92c | ||
|
|
33de2bda9e | ||
|
|
94d913fbab | ||
|
|
beb1fcc176 | ||
|
|
1e1a6e3a2d | ||
|
|
6e7c21e439 | ||
|
|
cfe9aaf639 | ||
|
|
69e701befb | ||
|
|
5174a085b7 | ||
|
|
15fab69eca | ||
|
|
20b103a0fb | ||
|
|
8ebe7ee81a | ||
|
|
1d747f664b | ||
|
|
dbf5988c28 | ||
|
|
31ab0e4b7d | ||
|
|
35ea7083bb | ||
|
|
bf90e8bf44 | ||
|
|
b1305644f4 | ||
|
|
34707a057b | ||
|
|
f5e90252f1 | ||
|
|
e75acf69aa | ||
|
|
3ad3fc2622 | ||
|
|
a59468d6c1 | ||
|
|
86e63b1614 | ||
|
|
3f8f9f6711 | ||
|
|
d01f494277 | ||
|
|
4db525c6a9 | ||
|
|
e313625ccb | ||
|
|
5b5be40dd5 | ||
|
|
aac915b285 | ||
|
|
7208f045c1 | ||
|
|
f7a3c42da1 | ||
|
|
4d1c53eb1e | ||
|
|
7bcc9b19ac | ||
|
|
7cfc729679 | ||
|
|
ec3b68ad28 |
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [knsv]
|
||||
#patreon: # Replace with a single Patreon username
|
||||
#open_collective: # Replace with a single Open Collective username
|
||||
#ko_fi: # Replace with a single Ko-fi username
|
||||
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
#liberapay: # Replace with a single Liberapay username
|
||||
#issuehunt: # Replace with a single IssueHunt username
|
||||
#otechie: # Replace with a single Otechie username
|
||||
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
3
.github/pr-labeler.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'Type: Bug / Error': 'bug/*'
|
||||
'Type: Enhancement': 'feature/*'
|
||||
'Type: Other': 'other/*'
|
||||
13
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
## :bookmark_tabs: Summary
|
||||
Brief description about the content of your PR.
|
||||
|
||||
Resolves #<your issue id here>
|
||||
|
||||
## :straight_ruler: Design Decisions
|
||||
Describe the way your implementation works or what design decisions you made if applicable.
|
||||
|
||||
### :clipboard: Tasks
|
||||
Make sure you
|
||||
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] :computer: have added unit/e2e tests (if appropriate)
|
||||
- [ ] :bookmark: targeted `develop` branch
|
||||
25
.github/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name-template: '$NEXT_PATCH_VERSION'
|
||||
tag-template: '$NEXT_PATCH_VERSION'
|
||||
categories:
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'Type: Enhancement'
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'Type: Bug / Error'
|
||||
- title: '🧰 Maintenance'
|
||||
label: 'Type: Other'
|
||||
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
|
||||
sort-by: title
|
||||
sort-direction: ascending
|
||||
branches:
|
||||
- develop
|
||||
exclude-labels:
|
||||
- 'Skip changelog'
|
||||
no-changes-template: 'This release contains minor changes and bugfixes.'
|
||||
template: |
|
||||
# Release Notes
|
||||
|
||||
$CHANGES
|
||||
|
||||
🎉 **Thanks to all contributors helping with this release!** 🎉
|
||||
64
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install Yarn
|
||||
run: npm i yarn --global
|
||||
|
||||
- name: Cache Node Modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .cache
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
yarn config set cache-folder $GITHUB_WORKSPACE/.cache/yarn
|
||||
yarn install --frozen-lockfile
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
- name: Run Build
|
||||
run: yarn build
|
||||
|
||||
- name: Upload Build as Artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
yarn test --coverage
|
||||
|
||||
#- name: Upload Test Results
|
||||
# uses: coverallsapp/github-action@v1.0.1
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# parallel: true
|
||||
|
||||
# - name: Run E2E Tests
|
||||
# run: yarn e2e
|
||||
# env:
|
||||
# PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
||||
# CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
#- name: Post Upload Test Results
|
||||
# uses: coverallsapp/github-action@master
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# parallel-finished: true
|
||||
19
.github/workflows/checks
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
on: [push]
|
||||
|
||||
name: Static analysis
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: check tests
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: testomatio/check-tests@stable
|
||||
with:
|
||||
framework: cypress
|
||||
tests: "./cypress/integration/**/**.spec.js"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
has-tests-label: true
|
||||
|
||||
13
.github/workflows/pr-labeler.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Apply labels to PR
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
pr-labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label PR
|
||||
uses: TimonVS/pr-labeler-action@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
15
.github/workflows/release-draft.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Draft Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Draft Release
|
||||
uses: toolmantim/release-drafter@v5.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -8,15 +8,12 @@ on:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: 10.x
|
||||
- name: Install Yarn
|
||||
run: npm i yarn --global
|
||||
|
||||
@@ -24,7 +21,7 @@ jobs:
|
||||
run: npm i json --global
|
||||
|
||||
- name: Install Packages
|
||||
run: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
|
||||
46
.github/workflows/release-publish.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: fregante/setup-git-token@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10.x
|
||||
- name: Install Yarn
|
||||
run: npm i yarn --global
|
||||
|
||||
- name: Install Json
|
||||
run: npm i json --global
|
||||
|
||||
- name: Install Packages
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Prepare release
|
||||
run: |
|
||||
VERSION=${GITHUB_REF:10}
|
||||
echo "Preparing release $VERSION"
|
||||
git checkout -t origin/release/$VERSION
|
||||
npm version --no-git-tag-version --allow-same-version $VERSION
|
||||
git add package.json
|
||||
git commit -m "Bump version $VERSION"
|
||||
git checkout -t origin/master
|
||||
git merge -m "Release $VERSION" --no-ff release/$VERSION
|
||||
git push --no-verify
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
npm set //registry.npmjs.org/:_authToken $NPM_TOKEN
|
||||
npm publish
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
8
.gitignore
vendored
@@ -12,3 +12,11 @@ yarn-error.log
|
||||
token
|
||||
|
||||
package-lock.json
|
||||
|
||||
dist/classTest.html
|
||||
|
||||
dist/sequenceTest.html
|
||||
|
||||
.vscode/
|
||||
cypress/platform/current.html
|
||||
cypress/platform/experimental.html
|
||||
8
.vscode/settings.json
vendored
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"typescript.format.enable": false,
|
||||
"typescript.reportStyleChecksAsWarnings": false,
|
||||
"typescript.validate.enable": false,
|
||||
"javascript.validate.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.snippetSuggestions": "top"
|
||||
}
|
||||
72
README.md
@@ -5,13 +5,50 @@
|
||||
<!-- </Remove this in the future> -->
|
||||
|
||||
# mermaid [](https://travis-ci.org/mermaid-js/mermaid) [](https://www.npmjs.com/package/mermaid) [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [](https://percy.io/Mermaid/mermaid)
|
||||
|
||||
<!-- <Main description> -->
|
||||
__Generate diagrams, charts, graphs or flows from markdown-like text via javascript.__
|
||||
See our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play in our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage).
|
||||
__mermaid is a Javascript based diagramming and charting tool. It generates diagrams flowcharts and more, using markdown-inspired text for ease and speed.__
|
||||
|
||||
Check out the list of [Integrations and Usages of Mermaid](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md)
|
||||
|
||||
For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Alternatively, you can also play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/).
|
||||
<!-- </Main description> -->
|
||||
|
||||
:trophy: _"The most exciting use of technology"_ - [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees)
|
||||
:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/#nominees) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.**
|
||||
|
||||
## New diagram
|
||||
|
||||
This version comes with a new diagram type, user journey diagrams.
|
||||
|
||||
## New diagrams in 8.4
|
||||
|
||||
With version 8.4 class diagrams have got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram type, state diagrams.
|
||||
|
||||

|
||||
|
||||
## Special note regarding version 8.2
|
||||
|
||||
In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams.
|
||||
|
||||
- **true**: (default) tags in text are encoded, click functionality is disabled
|
||||
- false: tags in text are allowed, click functionality is enabled
|
||||
|
||||
Closed issues:
|
||||
|
||||
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
|
||||
|
||||
If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed.
|
||||
|
||||
```javascript
|
||||
mermaidAPI.initialize({
|
||||
securityLevel: 'loose'
|
||||
});
|
||||
```
|
||||
|
||||
For more information and help in getting started, please view our [documentation](http://mermaid-js.github.io/mermaid/) and start simplifying yours. Play with our [live editor](https://mermaidjs.github.io/mermaid-live-editor/) or jump straight to the [installation and usage](http://mermaid-js.github.io/mermaid/#/usage).
|
||||
<!-- </Main description> -->
|
||||
|
||||
__The following are some examples of the diagrams, charts and graphs that can be made using mermaid and the Markdown-inspired text specific to it. Click here jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).__
|
||||
<table>
|
||||
<!-- <Flowchart> -->
|
||||
<tr><td colspan=2 align="center">
|
||||
@@ -133,7 +170,7 @@ Crash --> [*]
|
||||
pie
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
"Rats" : 15
|
||||
</pre></td>
|
||||
<td align="center">
|
||||
<img src="https://raw.githubusercontent.com/mermaid-js/mermaid/master/img/gray-pie.png" />
|
||||
@@ -149,12 +186,37 @@ pie
|
||||
<td colspan="2" align="center"><i>Coming soon!</i></td>
|
||||
</tr>
|
||||
<!-- </Git> -->
|
||||
<!-- <Journey> -->
|
||||
<tr><td colspan=2 align="center">
|
||||
<b>User Journey</b><br />
|
||||
[<a href="http://mermaid-js.github.io/mermaid/#/user-journey">docs</a> - <a href="https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic3RhdGVEaWFncmFtXG4gICAgWypdIC0tPiBTdGlsbFxuICAgIFN0aWxsIC0tPiBbKl1cbiAgICBTdGlsbCAtLT4gTW92aW5nXG4gICAgTW92aW5nIC0tPiBTdGlsbFxuICAgIE1vdmluZyAtLT4gQ3Jhc2hcbiAgICBDcmFzaCAtLT4gWypdIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifX0">live editor</a>]
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td>
|
||||
<pre>
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 3: Me
|
||||
</pre></td>
|
||||
<td align="center">
|
||||
<img alt="User Journey Diagram" src="img/gray-user-journey.png" />
|
||||
</td>
|
||||
</tr>
|
||||
<!-- </Journey> -->
|
||||
|
||||
</table>
|
||||
|
||||
## Related projects
|
||||
## Related projects
|
||||
|
||||
- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli)
|
||||
- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
||||
- [HTTP Server](https://github.com/TomWright/mermaid-server)
|
||||
|
||||
# Contributors [](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Help+wanted%21%22) [](https://github.com/mermaid-js/mermaid/graphs/contributors) [](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ module.exports = {
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
node: 'current'
|
||||
}
|
||||
targets: "defaults, ie >= 11, current node"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -26,3 +26,9 @@ export const imgSnapshotTest = (graphStr, options, api) => {
|
||||
cy.get('svg');
|
||||
cy.percySnapshot();
|
||||
};
|
||||
|
||||
export const renderGraph = (graphStr, options, api) => {
|
||||
const url = mermaidUrl(graphStr, options, api);
|
||||
|
||||
cy.visit(url);
|
||||
};
|
||||
|
||||
100
cypress/integration/other/configuration.spec.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { renderGraph } from '../../helpers/util';
|
||||
/* eslint-env jest */
|
||||
describe('Configuration', () => {
|
||||
describe('arrowMarkerAbsolute', () => {
|
||||
it('should handle default value false of arrowMarkerAbsolute', () => {
|
||||
renderGraph(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ }
|
||||
);
|
||||
|
||||
// Check the marker-end property to make sure it is properly set to
|
||||
// start with #
|
||||
cy.get('.edgePath path').first().should('have.attr', 'marker-end')
|
||||
.should('exist')
|
||||
.and('include', 'url(#');
|
||||
});
|
||||
it('should handle default value false of arrowMarkerAbsolute', () => {
|
||||
renderGraph(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ }
|
||||
);
|
||||
|
||||
// Check the marker-end property to make sure it is properly set to
|
||||
// start with #
|
||||
cy.get('.edgePath path').first().should('have.attr', 'marker-end')
|
||||
.should('exist')
|
||||
.and('include', 'url(#');
|
||||
});
|
||||
it('should handle arrowMarkerAbsolute excplicitly set to false', () => {
|
||||
renderGraph(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{
|
||||
arrowMarkerAbsolute: false
|
||||
}
|
||||
);
|
||||
|
||||
// Check the marker-end property to make sure it is properly set to
|
||||
// start with #
|
||||
cy.get('.edgePath path').first().should('have.attr', 'marker-end')
|
||||
.should('exist')
|
||||
.and('include', 'url(#');
|
||||
});
|
||||
it('should handle arrowMarkerAbsolute excplicitly set to "false" as false', () => {
|
||||
renderGraph(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{
|
||||
arrowMarkerAbsolute: "false"
|
||||
}
|
||||
);
|
||||
|
||||
// Check the marker-end property to make sure it is properly set to
|
||||
// start with #
|
||||
cy.get('.edgePath path').first().should('have.attr', 'marker-end')
|
||||
.should('exist')
|
||||
.and('include', 'url(#');
|
||||
});
|
||||
it('should handle arrowMarkerAbsolute set to true', () => {
|
||||
renderGraph(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{
|
||||
arrowMarkerAbsolute: true
|
||||
}
|
||||
);
|
||||
|
||||
cy.get('.edgePath path').first().should('have.attr', 'marker-end')
|
||||
.should('exist')
|
||||
.and('include', 'url(http://localhost');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@ describe('Interaction', () => {
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('g#mermaid-dom-id-1Function')
|
||||
.find('g[id="1Function"]')
|
||||
.click();
|
||||
|
||||
cy.get('.created-by-click').should('have.text', 'Clicked By Flow');
|
||||
@@ -38,7 +38,7 @@ describe('Interaction', () => {
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('g#mermaid-dom-id-2URL')
|
||||
.find('g[id="2URL"]')
|
||||
.click();
|
||||
|
||||
cy.location().should(location => {
|
||||
@@ -70,7 +70,7 @@ describe('Interaction', () => {
|
||||
expect(location.href).to.eq('http://localhost:9000/webpackUsage.html');
|
||||
});
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
it('should handle a click on a task with a bound function without args', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
@@ -78,9 +78,20 @@ describe('Interaction', () => {
|
||||
.find('rect#cl2')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
it('should handle a click on a task with a bound function with args', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('rect#cl3')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant test1 test2 test3');
|
||||
});
|
||||
|
||||
it('should handle a click on a task with a bound function without args', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
@@ -88,8 +99,19 @@ describe('Interaction', () => {
|
||||
.find('text#cl2-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function with args ', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('text#cl3-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant test1 test2 test3');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Interaction - security level tight', () => {
|
||||
@@ -108,7 +130,7 @@ describe('Interaction', () => {
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('g#mermaid-dom-id-1Function')
|
||||
.find('g[id="1Function"]')
|
||||
.click();
|
||||
|
||||
cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow');
|
||||
@@ -130,7 +152,7 @@ describe('Interaction', () => {
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('g#mermaid-dom-id-2URL')
|
||||
.find('g[id="2URL"]')
|
||||
.click();
|
||||
|
||||
cy.location().should(location => {
|
||||
@@ -170,7 +192,7 @@ describe('Interaction', () => {
|
||||
.find('rect#cl2')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
const url = 'http://localhost:9000/click_security_strict.html';
|
||||
@@ -180,7 +202,7 @@ describe('Interaction', () => {
|
||||
.find('text#cl2-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -200,7 +222,7 @@ describe('Interaction', () => {
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('g#mermaid-dom-id-1Function')
|
||||
.find('g[id="1Function"]')
|
||||
.click();
|
||||
|
||||
cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow');
|
||||
@@ -226,7 +248,7 @@ describe('Interaction', () => {
|
||||
.find('rect#cl2')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
const url = 'http://localhost:9000/click_security_strict.html';
|
||||
@@ -236,7 +258,7 @@ describe('Interaction', () => {
|
||||
.find('text#cl2-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,8 +9,27 @@ describe('XSS', () => {
|
||||
const url = mermaidUrl(str,{}, true);
|
||||
|
||||
cy.visit(url);
|
||||
cy.wait(1000).then(()=>{
|
||||
cy.get('.mermaid').should('exist');
|
||||
});
|
||||
cy.get('svg')
|
||||
cy.percySnapshot()
|
||||
// cy.percySnapshot()
|
||||
|
||||
})
|
||||
it('should handle xss in tags in non-html mode', () => {
|
||||
const str = 'eyJjb2RlIjoiXG5ncmFwaCBMUlxuICAgICAgQi0tPkQoPGltZyBvbmVycm9yPWxvY2F0aW9uPWBqYXZhc2NyaXB0XFx1MDAzYXhzc0F0dGFja1xcdTAwMjhkb2N1bWVudC5kb21haW5cXHUwMDI5YCBzcmM9eD4pOyIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0IiwiZmxvd2NoYXJ0Ijp7Imh0bWxMYWJlbHMiOmZhbHNlfX19';
|
||||
|
||||
const url = mermaidUrl(str,{
|
||||
"theme": "default",
|
||||
"flowchart": {
|
||||
"htmlMode": false
|
||||
}
|
||||
}, true);
|
||||
|
||||
cy.visit(url);
|
||||
// cy.get('svg')
|
||||
// cy.percySnapshot()
|
||||
cy.get('.malware').should('not.exist');
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('Class diagram', () => {
|
||||
it('should render a simple class diagram', () => {
|
||||
it('1: should render a simple class diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
@@ -14,11 +14,16 @@ describe('Class diagram', () => {
|
||||
Class09 --> C2 : Where am i?
|
||||
Class09 --* C3
|
||||
Class09 --|> Class07
|
||||
Class12 <|.. Class08
|
||||
Class11 ..>Class12
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class01 : -int privateChimp
|
||||
Class01 : +int publicGorilla
|
||||
Class01 : #int protectedMarmoset
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
@@ -26,11 +31,12 @@ describe('Class diagram', () => {
|
||||
test()
|
||||
}
|
||||
`,
|
||||
{}
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should render a simple class diagrams with cardinality', () => {
|
||||
|
||||
it('2: should render a simple class diagrams with cardinality', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
@@ -58,4 +64,245 @@ describe('Class diagram', () => {
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a simple class diagram with different visibilities', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class01 : -int privateMethod()
|
||||
Class01 : +int publicMethod()
|
||||
Class01 : #int protectedMethod()
|
||||
Class01 : -int privateChimp
|
||||
Class01 : +int publicGorilla
|
||||
Class01 : #int protectedMarmoset
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render multiple class diagrams', () => {
|
||||
imgSnapshotTest(
|
||||
[
|
||||
`
|
||||
classDiagram
|
||||
Class01 "1" <|--|> "*" AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03 "1" *-- "*" Class04
|
||||
Class05 "1" o-- "many" Class06
|
||||
Class07 "1" .. "*" Class08
|
||||
Class09 "1" --> "*" C2 : Where am i?
|
||||
Class09 "*" --* "*" C3
|
||||
Class09 "1" --|> "1" Class07
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 "1" <--> "*" C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
`,
|
||||
`
|
||||
classDiagram
|
||||
Class01 "1" <|--|> "*" AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03 "1" *-- "*" Class04
|
||||
Class05 "1" o-- "many" Class06
|
||||
Class07 "1" .. "*" Class08
|
||||
Class09 "1" --> "*" C2 : Where am i?
|
||||
Class09 "*" --* "*" C3
|
||||
Class09 "1" --|> "1" Class07
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 "1" <--> "*" C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
`,
|
||||
],
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('4: should render a simple class diagram with comments', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
%% this is a comment
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03 *-- Class04
|
||||
Class05 o-- Class06
|
||||
Class07 .. Class08
|
||||
Class09 --> C2 : Where am i?
|
||||
Class09 --* C3
|
||||
Class09 --|> Class07
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('5: should render a simple class diagram with abstract method', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
Class01 : someMethod()*
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('6: should render a simple class diagram with static method', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
Class01 : someMethod()$
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('7: should render a simple class diagram with Generic class', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
class Class01~T~
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('8: should render a simple class diagram with Generic class and relations', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01~T~ <|-- AveryLongClass : Cool
|
||||
Class03~T~ *-- Class04~T~
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('9: should render a simple class diagram with clickable link', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01~T~ <|-- AveryLongClass : Cool
|
||||
Class03~T~ *-- Class04~T~
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
link class01 "google.com" "A Tooltip"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('10: should render a simple class diagram with clickable callback', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01~T~ <|-- AveryLongClass : Cool
|
||||
Class03~T~ *-- Class04~T~
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
callback class01 "functionCall" "A Tooltip"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('11: should render a simple class diagram with return type on method', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
class Class10~T~ {
|
||||
int[] id
|
||||
test(int[] ids) bool
|
||||
testArray() bool[]
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('12: should render a simple class diagram with generic types', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
class Class10~T~ {
|
||||
int[] id
|
||||
List~int~ ids
|
||||
test(List~int~ ids) List~bool~
|
||||
testArray() bool[]
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('State diagram', () => {
|
||||
it('should render a flowchart full of circles', () => {
|
||||
it('should render a state with states in it', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You\ncan write
|
||||
notes with multiple lines...
|
||||
Here is another line...
|
||||
And another line...
|
||||
end note
|
||||
stateDiagram
|
||||
state PersonalizedCockpit {
|
||||
Other
|
||||
state Parent {
|
||||
C
|
||||
}
|
||||
}
|
||||
`,
|
||||
{}
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
91
cypress/integration/rendering/erDiagram.spec.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('Entity Relationship Diagram', () => {
|
||||
it('should render a simple ER diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
`,
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render an ER diagram with a recursive relationship', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
CUSTOMER ||..o{ CUSTOMER : refers
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
`,
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render an ER diagram with multiple relationships between the same two entities', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
CUSTOMER ||--|{ ADDRESS : "invoiced at"
|
||||
CUSTOMER ||--|{ ADDRESS : "receives goods at"
|
||||
`,
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a cyclical ER diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
A ||--|{ B : likes
|
||||
B ||--|{ C : likes
|
||||
C ||--|{ A : likes
|
||||
`,
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a not-so-simple ER diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER ||--o{ INVOICE : "liable for"
|
||||
DELIVERY-ADDRESS ||--o{ ORDER : receives
|
||||
INVOICE ||--|{ ORDER : covers
|
||||
ORDER ||--|{ ORDER-ITEM : includes
|
||||
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
|
||||
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
|
||||
`,
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render multiple ER diagrams', () => {
|
||||
imgSnapshotTest(
|
||||
[
|
||||
`
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
`,
|
||||
`
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
`
|
||||
],
|
||||
{logLevel : 1}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('Flowcart', () => {
|
||||
describe('Flowchart', () => {
|
||||
it('1: should render a simple flowchart no htmlLabels', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
@@ -14,6 +14,7 @@ describe('Flowcart', () => {
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('2: should render a simple flowchart with htmlLabels', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
@@ -26,6 +27,7 @@ describe('Flowcart', () => {
|
||||
{ flowchart: { htmlLabels: true } }
|
||||
);
|
||||
});
|
||||
|
||||
it('3: should render a simple flowchart with line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -55,7 +57,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('4: should style nodes via a class.', () => {
|
||||
it('5: should style nodes via a class.', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
@@ -71,7 +73,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('5: should render a flowchart full of circles', () => {
|
||||
it('6: should render a flowchart full of circles', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph LR
|
||||
@@ -99,7 +101,8 @@ describe('Flowcart', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('6: should render a flowchart full of icons', () => {
|
||||
|
||||
it('7: should render a flowchart full of icons', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
@@ -170,7 +173,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('7: should render labels with numbers at the start', () => {
|
||||
it('8: should render labels with numbers at the start', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB;subgraph "number as labels";1;end;
|
||||
@@ -178,7 +181,8 @@ describe('Flowcart', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('8: should render subgraphs', () => {
|
||||
|
||||
it('9: should render subgraphs', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB
|
||||
@@ -190,7 +194,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('9: should render subgraphs with a title startign with a digit', () => {
|
||||
it('10: should render subgraphs with a title starting with a digit', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB
|
||||
@@ -202,7 +206,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('10: should render styled subgraphs', () => {
|
||||
it('11: should render styled subgraphs', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB
|
||||
@@ -237,7 +241,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('11: should render a flowchart with ling sames and class definitoins', () => {
|
||||
it('12: should render a flowchart with long names and class definitions', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
|
||||
@@ -339,7 +343,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('12: should render color of styled nodes', () => {
|
||||
it('13: should render color of styled nodes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph LR
|
||||
@@ -356,7 +360,8 @@ describe('Flowcart', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
it('13: should render hexagons', () => {
|
||||
|
||||
it('14: should render hexagons', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
@@ -369,6 +374,7 @@ describe('Flowcart', () => {
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
`,
|
||||
{
|
||||
listUrl: false,
|
||||
@@ -377,4 +383,292 @@ describe('Flowcart', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('15: should render a simple flowchart with comments', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
%% this is a comment
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('16: Render Stadium shape', () => {
|
||||
imgSnapshotTest(
|
||||
` graph TD
|
||||
A([stadium shape test])
|
||||
A -->|Get money| B([Go shopping])
|
||||
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
|
||||
C -->|One| D([Laptop])
|
||||
C -->|Two| E([iPhone])
|
||||
C -->|Three| F([Car<br/>wroom wroom])
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('17: Render multiline texts', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A1[Multi<br>Line] -->|Multi<br>Line| B1(Multi<br>Line)
|
||||
C1[Multi<br/>Line] -->|Multi<br/>Line| D1(Multi<br/>Line)
|
||||
E1[Multi<br />Line] -->|Multi<br />Line| F1(Multi<br />Line)
|
||||
A2[Multi<br>Line] -->|Multi<br>Line| B2(Multi<br>Line)
|
||||
C2[Multi<br/>Line] -->|Multi<br/>Line| D2(Multi<br/>Line)
|
||||
E2[Multi<br />Line] -->|Multi<br />Line| F2(Multi<br />Line)
|
||||
linkStyle 0 stroke:DarkGray,stroke-width:2px
|
||||
linkStyle 1 stroke:DarkGray,stroke-width:2px
|
||||
linkStyle 2 stroke:DarkGray,stroke-width:2px
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('18: Chaining of nodes', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
a --> b --> c
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('19: Multiple nodes and chaining in one statement', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
a --> b & c--> d
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('20: Multiple nodes and chaining in one statement', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[ h ] -- hello --> B[" test "]:::exClass & C --> D;
|
||||
classDef exClass background:#bbb,border:1px solid red;
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('21: Render cylindrical shape', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A[(cylindrical<br />shape<br />test)]
|
||||
A -->|Get money| B1[(Go shopping 1)]
|
||||
A -->|Get money| B2[(Go shopping 2)]
|
||||
A -->|Get money| B3[(Go shopping 3)]
|
||||
C[(Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?)]
|
||||
B1 --> C
|
||||
B2 --> C
|
||||
B3 --> C
|
||||
C -->|One| D[(Laptop)]
|
||||
C -->|Two| E[(iPhone)]
|
||||
C -->|Three| F[(Car)]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('22: Render a simple flowchart with nodeSpacing set to 100', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
%% this is a comment
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ flowchart: { nodeSpacing: 50 } }
|
||||
);
|
||||
});
|
||||
|
||||
it('23: Render a simple flowchart with rankSpacing set to 100', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
%% this is a comment
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ flowchart: { rankSpacing: '100' } }
|
||||
);
|
||||
});
|
||||
|
||||
it('24: Keep node label text (if already defined) when a style is applied', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A(( )) -->|step 1| B(( ))
|
||||
B(( )) -->|step 2| C(( ))
|
||||
C(( )) -->|step 3| D(( ))
|
||||
linkStyle 1 stroke:greenyellow,stroke-width:2px
|
||||
style C fill:greenyellow,stroke:green,stroke-width:4px
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('25: Handle link click events (link, anchor, mailto, other protocol, script)', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TB
|
||||
TITLE["Link Click Events<br>(click the nodes below)"]
|
||||
A[link test]
|
||||
B[anchor test]
|
||||
C[mailto test]
|
||||
D[other protocol test]
|
||||
E[script test]
|
||||
TITLE --> A & B & C & D & E
|
||||
click A "https://mermaid-js.github.io/mermaid/#/" "link test"
|
||||
click B "#link-clicked" "anchor test"
|
||||
click C "mailto:user@user.user" "mailto test"
|
||||
click D "notes://do-your-thing/id" "other protocol test"
|
||||
click E "javascript:alert('test')" "script test"
|
||||
`,
|
||||
{ securityLevel: 'loose' }
|
||||
);
|
||||
});
|
||||
|
||||
it('26: Set text color of nodes and links according to styles when html labels are enabled', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A[red<br>text] -->|red<br>text| B(blue<br>text)
|
||||
C[/red<br/>text/] -->|blue<br/>text| D{blue<br/>text}
|
||||
E{{default<br />style}} -->|default<br />style| F([default<br />style])
|
||||
linkStyle default color:Sienna;
|
||||
linkStyle 0 color:red;
|
||||
linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
|
||||
style A color:red;
|
||||
style B color:blue;
|
||||
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
|
||||
style D stroke:#0000ff,fill:#ccccff,color:#0000ff
|
||||
click B "index.html#link-clicked" "link test"
|
||||
click D testClick "click test"
|
||||
`,
|
||||
{ flowchart: { htmlLabels: true } }
|
||||
);
|
||||
});
|
||||
|
||||
it('27: Set text color of nodes and links according to styles when html labels are disabled', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A[red<br>text] -->|red<br>text| B(blue<br>text)
|
||||
C[/red<br/>text/] -->|blue<br/>text| D{blue<br/>text}
|
||||
E{{default<br />style}} -->|default<br />style| F([default<br />style])
|
||||
linkStyle default color:Sienna;
|
||||
linkStyle 0 color:red;
|
||||
linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
|
||||
style A color:red;
|
||||
style B color:blue;
|
||||
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
|
||||
style D stroke:#0000ff,fill:#ccccff,color:#0000ff
|
||||
click B "index.html#link-clicked" "link test"
|
||||
click D testClick "click test"
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('28: Apply default class to all nodes which do not have another class assigned (htmlLabels enabled)', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[myClass1] --> B[default] & C[default]
|
||||
B[default] & C[default] --> D[myClass2]
|
||||
classDef default stroke-width:2px,fill:none,stroke:silver
|
||||
classDef node color:red
|
||||
classDef myClass1 color:#0000ff
|
||||
classDef myClass2 stroke:#0000ff,fill:#ccccff
|
||||
class A myClass1
|
||||
class D myClass2
|
||||
`,
|
||||
{ flowchart: { htmlLabels: true } }
|
||||
);
|
||||
});
|
||||
|
||||
it('29: Apply default class to all nodes which do not have another class assigned (htmlLabels disabled)', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[myClass1] --> B[default] & C[default]
|
||||
B[default] & C[default] --> D[myClass2]
|
||||
classDef default stroke-width:2px,fill:none,stroke:silver
|
||||
classDef node color:red
|
||||
classDef myClass1 color:#0000ff
|
||||
classDef myClass2 stroke:#0000ff,fill:#ccccff
|
||||
class A myClass1
|
||||
class D myClass2
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('30: Possibility to style text color of nodes and subgraphs as well as apply classes to subgraphs', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
subgraph id1 [title is set]
|
||||
A-->B
|
||||
end
|
||||
subgraph id2 [title]
|
||||
E
|
||||
end
|
||||
|
||||
B-->C
|
||||
|
||||
subgraph id3
|
||||
C-->D
|
||||
end
|
||||
class id3,id2,A redBg;
|
||||
class id3,A whiteTxt;
|
||||
classDef redBg fill:#622;
|
||||
classDef whiteTxt color: white;
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('31: should not slice off edges that are to the left of the left-most vertex', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
work --> sleep
|
||||
sleep --> work
|
||||
eat --> sleep
|
||||
work --> eat
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('32: Render Subroutine shape', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A[[subroutine shape test]]
|
||||
A -->|Get money| B[[Go shopping]]
|
||||
B --> C[[Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?]]
|
||||
C -->|One| D[[Laptop]]
|
||||
C -->|Two| E[[iPhone]]
|
||||
C -->|Three| F[[Car<br/>wroom wroom]]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
|
||||
describe('Sequencediagram', () => {
|
||||
describe('Gantt diagram', () => {
|
||||
it('should render a gantt chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -38,4 +38,126 @@ describe('Sequencediagram', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('Handle multiline section titles with different line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d/%m
|
||||
title GANTT diagram with multiline section titles
|
||||
excludes weekdays 2014-01-10
|
||||
|
||||
section A section<br>multiline
|
||||
Completed task : done, des1, 2014-01-06,2014-01-08
|
||||
Active task : active, des2, 2014-01-09, 3d
|
||||
Future task : des3, after des2, 5d
|
||||
Future task2 : des4, after des3, 5d
|
||||
|
||||
section Critical tasks<br/>multiline
|
||||
Completed task in the critical line : crit, done, 2014-01-06, 24h
|
||||
Implement parser and jison : crit, done, after des1, 2d
|
||||
Create tests for parser : crit, active, 3d
|
||||
Future task in critical line : crit, 5d
|
||||
Create tests for renderer : 2d
|
||||
Add to mermaid : 1d
|
||||
|
||||
section Documentation<br />multiline
|
||||
Describe gantt syntax : active, a1, after des1, 3d
|
||||
Add gantt diagram to demo page : after a1, 20h
|
||||
Add another diagram to demo page : doc1, after a1, 48h
|
||||
|
||||
section Last section<br />multiline
|
||||
Describe gantt syntax : after doc1, 3d
|
||||
Add gantt diagram to demo page : 20h
|
||||
Add another diagram to demo page : 48h
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('Multiple dependencies syntax', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d/%m
|
||||
title Adding GANTT diagram to mermaid
|
||||
excludes weekdays 2014-01-10
|
||||
|
||||
apple :a, 2017-07-20, 1w
|
||||
banana :crit, b, 2017-07-23, 1d
|
||||
cherry :active, c, after b a, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render a gantt chart for issue #1060', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
excludes weekdays 2017-01-10
|
||||
title Projects Timeline
|
||||
|
||||
section asdf
|
||||
specs :done, :ps, 2019-05-10, 50d
|
||||
Plasma :pc, 2019-06-20, 60d
|
||||
Rollup :or, 2019-08-20, 50d
|
||||
|
||||
section CEL
|
||||
|
||||
plasma-chamber :done, :pc, 2019-05-20, 60d
|
||||
Plasma Implementation (Rust) :por, 2019-06-20, 120d
|
||||
Predicates (Atomic Swap) :pred, 2019-07-20, 60d
|
||||
|
||||
section DEX
|
||||
|
||||
History zkSNARK :hs, 2019-08-10, 40d
|
||||
Exit :vs, after hs, 60d
|
||||
PredicateSpec :ps, 2019-09-1, 20d
|
||||
PlasmaIntegration :pi, after ps,40d
|
||||
|
||||
|
||||
section Events
|
||||
|
||||
ETHBoston :done, :eb, 2019-09-08, 3d
|
||||
DevCon :active, :dc, 2019-10-08, 3d
|
||||
|
||||
section Plasma Calls & updates
|
||||
OVM :ovm, 2019-07-12, 120d
|
||||
Plasma call 26 :pc26, 2019-08-21, 1d
|
||||
Plasma call 27 :pc27, 2019-09-03, 1d
|
||||
Plasma call 28 :pc28, 2019-09-17, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should hide today marker', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title Hide today marker (vertical line should not be visible)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d
|
||||
todayMarker off
|
||||
section Section1
|
||||
Today: 1, -1h
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should style today marker', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title Style today marker (vertical line should be 5px wide and half-transparent blue)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d
|
||||
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
|
||||
section Section1
|
||||
Today: 1, -1h
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
|
||||
describe('Sequencediagram', () => {
|
||||
it('should render a simple git graph', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gitGraph:
|
||||
commit
|
||||
branch newbranch
|
||||
checkout newbranch
|
||||
commit
|
||||
commit
|
||||
checkout master
|
||||
commit
|
||||
commit
|
||||
merge newbranch`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
});
|
||||
// it('should render a simple git graph', () => {
|
||||
// imgSnapshotTest(
|
||||
// `
|
||||
// gitGraph:
|
||||
// commit
|
||||
// branch newbranch
|
||||
// checkout newbranch
|
||||
// commit
|
||||
// commit
|
||||
// checkout master
|
||||
// commit
|
||||
// commit
|
||||
// merge newbranch`,
|
||||
// { logLevel: 0 }
|
||||
// );
|
||||
// });
|
||||
});
|
||||
|
||||
31
cypress/integration/rendering/journey.spec.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
|
||||
describe('User journey diagram', () => {
|
||||
it('Simple test', () => {
|
||||
imgSnapshotTest(
|
||||
`journey
|
||||
title Adding journey diagram functionality to mermaid
|
||||
section Order from website
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a user journey chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 3: Me
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
context('Aliasing', () => {
|
||||
it('should render a simple sequence diagrams', () => {
|
||||
context('Sequence diagram', () => {
|
||||
it('should render a simple sequence diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
@@ -32,6 +32,158 @@ context('Aliasing', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should handle different line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant 1 as multiline<br>using #lt;br#gt;
|
||||
participant 2 as multiline<br/>using #lt;br/#gt;
|
||||
participant 3 as multiline<br />using #lt;br /#gt;
|
||||
participant 4 as multiline<br \t/>using #lt;br \t/#gt;
|
||||
1->>2: multiline<br>using #lt;br#gt;
|
||||
note right of 2: multiline<br>using #lt;br#gt;
|
||||
2->>3: multiline<br/>using #lt;br/#gt;
|
||||
note right of 3: multiline<br/>using #lt;br/#gt;
|
||||
3->>4: multiline<br />using #lt;br /#gt;
|
||||
note right of 4: multiline<br />using #lt;br /#gt;
|
||||
4->>1: multiline<br />using #lt;br /#gt;
|
||||
note right of 1: multiline<br \t/>using #lt;br \t/#gt;
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render loops with a slight margin', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
loop Loopy
|
||||
Bob->>Alice: Pasten
|
||||
end `,
|
||||
{}
|
||||
);
|
||||
});
|
||||
context('font settings', () => {
|
||||
it('should render different note fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: I should have bigger fonts
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { noteFontSize: 18, noteFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should render different message fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { messageFontSize: 18, messageFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should render different actor fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { actorFontSize: 18, actorFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should render notes aligned to the left when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: I am left aligned
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { noteAlign: 'left' } }
|
||||
);
|
||||
});
|
||||
it('should render notes aligned to the right when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: I am right aligned
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { noteAlign: 'right' } }
|
||||
);
|
||||
});
|
||||
});
|
||||
context('auth width scaling', () => {
|
||||
it('should render long actor descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
A->>Bob: Hola
|
||||
Bob-->A: Pasten !
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes left of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note left of Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes right of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note right of Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes over actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note over Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages from an actor to the left to one to the right', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages from an actor to the right to one to the left', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
context('background rects', () => {
|
||||
it('should render a single and nested rects', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -129,5 +281,51 @@ context('Aliasing', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render autonumber when configured with such', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts!
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
`,
|
||||
{ sequence: { actorMargin: 50, showSequenceNumbers: true } }
|
||||
);
|
||||
});
|
||||
it('should render autonumber when autonumber keyword is used', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts!
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render autonumber with different line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
Alice->>John: Hello John,<br>how are you?
|
||||
Alice->>John: John,<br/>can you hear me?
|
||||
John-->>Alice: Hi Alice,<br />I can hear you!
|
||||
John-->>Alice: I feel great!
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
358
cypress/integration/rendering/stateDiagram-v2.spec.js
Normal file
@@ -0,0 +1,358 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('State diagram', () => {
|
||||
it('v2 should render a simple info', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
info
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a simple state diagrams', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
|
||||
[*] --> State1
|
||||
State1 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a long descriptions instead of id when available', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
|
||||
[*] --> S1
|
||||
state "Some long name" as S1
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a long descriptions with additional descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
|
||||
[*] --> S1
|
||||
state "Some long name" as S1: The description
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a single state with short descr', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
state "A long long name" as long1
|
||||
state "A" as longlonglongid
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a transition descrions with new lines', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
|
||||
[*] --> S1
|
||||
S1 --> S2: long line using<br/>should work
|
||||
S1 --> S3: long line using <br>should work
|
||||
S1 --> S4: long line using \\nshould work
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a state with a note', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You can write
|
||||
notes.
|
||||
end note
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a state with on the left side when so specified', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
State1: The state with a note with minus - and plus + in it
|
||||
note left of State1
|
||||
Important information! You can write
|
||||
notes with . and in them.
|
||||
end note
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a state with a note together with another state', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
State1: The state with a note +,-
|
||||
note right of State1
|
||||
Important information! You can write +,-
|
||||
notes.
|
||||
end note
|
||||
State1 --> State2 : With +,-
|
||||
note left of State2 : This is the note +,-<br/>
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a note with multiple lines in it', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You\ncan write
|
||||
notes with multiple lines...
|
||||
Here is another line...
|
||||
And another line...
|
||||
end note
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('v2 should handle multiline notes with different line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
State1
|
||||
note right of State1
|
||||
Line1<br>Line2<br/>Line3<br />Line4<br />Line5
|
||||
end note
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('v2 should render a states with descriptions including multi-line descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
State1: This a a single line description
|
||||
State2: This a a multi line description
|
||||
State2: here comes the multi part
|
||||
[*] --> State1
|
||||
State1 --> State2
|
||||
State2 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a simple state diagrams', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
[*] --> State1
|
||||
State1 --> State2
|
||||
State1 --> State3
|
||||
State1 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a simple state diagrams with labels', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
[*] --> State1
|
||||
State1 --> State2 : Transition 1
|
||||
State1 --> State3 : Transition 2
|
||||
State1 --> State4 : Transition 3
|
||||
State1 --> State5 : Transition 4
|
||||
State2 --> State3 : Transition 5
|
||||
State1 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render state descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
state "Long state description" as XState1
|
||||
state "Another Long state description" as XState2
|
||||
XState2 : New line
|
||||
XState1 --> XState2
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render composit states', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
[*] --> NotShooting: Pacifist
|
||||
NotShooting --> A
|
||||
NotShooting --> B
|
||||
NotShooting --> C
|
||||
|
||||
state NotShooting {
|
||||
[*] --> Idle: Yet another long long öong öong öong label
|
||||
Idle --> Configuring : EvConfig
|
||||
Configuring --> Idle : EvConfig EvConfig EvConfig EvConfig EvConfig
|
||||
}
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render multiple composit states', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
[*]-->TV
|
||||
|
||||
state TV {
|
||||
[*] --> Off: Off to start with
|
||||
On --> Off : Turn off
|
||||
Off --> On : Turn on
|
||||
}
|
||||
|
||||
TV--> Console
|
||||
|
||||
state Console {
|
||||
[*] --> Off2: Off to start with
|
||||
On2--> Off2 : Turn off
|
||||
Off2 --> On2 : Turn on
|
||||
On2-->Playing
|
||||
|
||||
state Playing {
|
||||
Alive --> Dead
|
||||
Dead-->Alive
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
});
|
||||
it('v2 should render forks in composit states', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
[*]-->TV
|
||||
|
||||
state TV {
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
}
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
});
|
||||
it('v2 should render forks and joins', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render concurrency states', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
|
||||
state Active {
|
||||
[*] --> NumLockOff
|
||||
NumLockOff --> NumLockOn : EvNumLockPressed
|
||||
NumLockOn --> NumLockOff : EvNumLockPressed
|
||||
--
|
||||
[*] --> CapsLockOff
|
||||
CapsLockOff --> CapsLockOn : EvCapsLockPressed
|
||||
CapsLockOn --> CapsLockOff : EvCapsLockPressed
|
||||
--
|
||||
[*] --> ScrollLockOff
|
||||
ScrollLockOff --> ScrollLockOn : EvCapsLockPressed
|
||||
ScrollLockOn --> ScrollLockOff : EvCapsLockPressed
|
||||
}
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('v2 should render a state with states in it', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
state PilotCockpit {
|
||||
state Parent {
|
||||
C
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('v2 Simplest composit state', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
state Parent {
|
||||
C
|
||||
}
|
||||
`,
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('v2 should handle multiple arrows from one node to another', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram-v2
|
||||
a --> b: Start
|
||||
a --> b: Stop
|
||||
`,
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -106,6 +106,34 @@ describe('State diagram', () => {
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should render a note with multiple lines in it', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You\ncan write
|
||||
notes with multiple lines...
|
||||
Here is another line...
|
||||
And another line...
|
||||
end note
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should handle multiline notes with different line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
State1
|
||||
note right of State1
|
||||
Line1<br>Line2<br/>Line3<br />Line4<br />Line5
|
||||
end note
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a states with descriptions including multi-line descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -276,4 +304,45 @@ describe('State diagram', () => {
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should render a state with states in it', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
state PilotCockpit {
|
||||
state Parent {
|
||||
C
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('Simplest composit state', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
state Parent {
|
||||
C
|
||||
}
|
||||
`,
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should handle multiple arrows from one node to another', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
a --> b: Start
|
||||
a --> b: Stop
|
||||
`,
|
||||
{
|
||||
logLevel: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -49,9 +49,11 @@
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10
|
||||
Calling a Callback (look at the console log) :cl2, after cl1, 3d
|
||||
Calling a Callback with args :cl3, after cl1, 3d
|
||||
|
||||
click cl1 href "http://localhost:9000/webpackUsage.html"
|
||||
click cl2 call clickByGantt("test", test, test)
|
||||
click cl2 call clickByGantt()
|
||||
click cl3 call clickByGantt("test1", test2, test3)
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
@@ -69,11 +71,14 @@
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
function clickByGantt(elemName) {
|
||||
function clickByGantt(arg1, arg2, arg3) {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'created-by-gant-click'
|
||||
div.style = 'padding: 20px; background: green; color: white;'
|
||||
div.innerText = 'Clicked By Gant'
|
||||
if (arg1) div.innerText += ' ' + arg1;
|
||||
if (arg2) div.innerText += ' ' + arg2;
|
||||
if (arg3) div.innerText += ' ' + arg3;
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
|
||||
@@ -49,9 +49,11 @@
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10
|
||||
Calling a Callback (look at the console log) :cl2, after cl1, 3d
|
||||
Calling a Callback with args :cl3, after cl1, 3d
|
||||
|
||||
click cl1 href "http://localhost:9000/webpackUsage.html"
|
||||
click cl2 call clickByGantt("test", test, test)
|
||||
click cl2 call clickByGantt()
|
||||
click cl3 call clickByGantt("test1", test2, test3)
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
@@ -69,11 +71,14 @@
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
function clickByGantt(elemName) {
|
||||
function clickByGantt(arg1, arg2, arg3) {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'created-by-gant-click'
|
||||
div.style = 'padding: 20px; background: green; color: white;'
|
||||
div.innerText = 'Clicked By Gant'
|
||||
if (arg1) div.innerText += ' ' + arg1;
|
||||
if (arg2) div.innerText += ' ' + arg2;
|
||||
if (arg3) div.innerText += ' ' + arg3;
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
|
||||
@@ -4,28 +4,275 @@
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background: rgb(221, 208, 208);
|
||||
font-family: 'Arial';
|
||||
}
|
||||
h1 { color: white;}
|
||||
.mermaid2 {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>info below</h1>
|
||||
<div class="mermaid">graph TB
|
||||
a --> b
|
||||
a --> c
|
||||
a --> d
|
||||
<div class="mermaid2" style="width: 100%; height: 20%;">
|
||||
flowchart LR
|
||||
a --> b
|
||||
|
||||
subgraph b [Test]
|
||||
c --> d -->e
|
||||
end
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 20%;">
|
||||
flowchart LR
|
||||
a --> b
|
||||
|
||||
subgraph id1 [Test]
|
||||
a --apa--> c
|
||||
b
|
||||
c-->b
|
||||
b-->H
|
||||
end
|
||||
G-->H
|
||||
G-->c
|
||||
</div>
|
||||
<div class="mermaid" style="width: 50%; height: 20%;">
|
||||
flowchart LR
|
||||
A{{A}}-->B{{B}};
|
||||
click A callback "Tooltip"
|
||||
click B "http://www.github.com" "This is a link"
|
||||
|
||||
</div>
|
||||
<div class="mermaid" style="width: 50%; height: 20%;">
|
||||
flowchart LR
|
||||
A{{A}}-->B{{B}};
|
||||
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||
stateDiagram-v2
|
||||
[*]-->TV
|
||||
|
||||
state TV {
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
}
|
||||
</div>
|
||||
<div class="mermaid2 mermaid-apa" style="width: 100%; height: 20%;">
|
||||
stateDiagram
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
First --> Third
|
||||
First --> sec
|
||||
|
||||
state First {
|
||||
[*] --> fir
|
||||
fir --> [*]
|
||||
}
|
||||
state Second {
|
||||
[*] --> sec
|
||||
sec --> [*]
|
||||
}
|
||||
state Third {
|
||||
[*] --> thi
|
||||
thi --> [*]
|
||||
}
|
||||
thi --> sec
|
||||
</div>
|
||||
<script src="./mermaid.js"></script>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
flowchart TD
|
||||
subgraph A
|
||||
a
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
subgraph C
|
||||
subgraph D
|
||||
d
|
||||
end
|
||||
end
|
||||
A -- oAo --o B
|
||||
A --> C
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
flowchart TD
|
||||
subgraph A
|
||||
a
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
c-->A
|
||||
c-->B
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
First --> Second
|
||||
First --> Third
|
||||
|
||||
state First {
|
||||
[*] --> fir
|
||||
fir --> [*]
|
||||
}
|
||||
state Second {
|
||||
[*] --> sec
|
||||
sec --> [*]
|
||||
}
|
||||
state Third {
|
||||
[*] --> thi
|
||||
thi --> [*]
|
||||
}
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
stateDiagram-v2
|
||||
[*]-->TV
|
||||
|
||||
state TV {
|
||||
[*] --> Off: Off to start with
|
||||
On --> Off : Turn off
|
||||
Off --> On : Turn on
|
||||
}
|
||||
|
||||
TV--> Console
|
||||
|
||||
state Console {
|
||||
[*] --> Off2: Off to start with
|
||||
On2--> Off2 : Turn off
|
||||
Off2 --> On2 : Turn on
|
||||
On2-->Playing
|
||||
|
||||
state Playing {
|
||||
Alive --> Dead
|
||||
Dead-->Alive
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div style="display: flex;flex-direction:column;width: 100%; height: 100%">
|
||||
<div class="mermaid2" style="width: 100%; height: 100%;">
|
||||
stateDiagram-v2
|
||||
state apa {
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart TB
|
||||
a --> b
|
||||
|
||||
subgraph id1 [Test]
|
||||
a --apa--> c
|
||||
b
|
||||
c-->b
|
||||
b-->H
|
||||
end
|
||||
G-->H
|
||||
G-->id1
|
||||
id1 --> I
|
||||
I --> G
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart RL
|
||||
a --> b
|
||||
|
||||
subgraph id1 [Test]
|
||||
a --apa--> c
|
||||
b
|
||||
c-->b
|
||||
b-->H
|
||||
end
|
||||
G-->H
|
||||
G-->id1
|
||||
id1 --> I
|
||||
I --> G
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart RL
|
||||
|
||||
subgraph id1 [Test]
|
||||
a
|
||||
end
|
||||
b-->id1
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart RL
|
||||
|
||||
subgraph id1 [Test1]
|
||||
a
|
||||
end
|
||||
subgraph id2 [Test2]
|
||||
b
|
||||
end
|
||||
a --> id2
|
||||
a --> b
|
||||
b-->id1
|
||||
id1 --> id2
|
||||
</div>
|
||||
new:
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
flowchart LR
|
||||
a <--> b
|
||||
b o--o c
|
||||
c x--x d
|
||||
a21([In the box]) --> b2
|
||||
b2((b2)) --o c2
|
||||
c2(c2) --x d2 --> id1{{This is the text in the box}} --> A[(cylindrical<br />shape<br />test)]
|
||||
</div>
|
||||
old:
|
||||
<div class="mermaid2" style="width: 100%; height: 100%">
|
||||
graph LR
|
||||
a((a)) --> b --> id1{{This is the text in the box}}
|
||||
A[(cylindrical<br />shape<br />test)]
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="./mermaid.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// theme: 'dark',
|
||||
// arrowMarkerAbsolute: true,
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
logLevel: 3,
|
||||
flowchart: { curve: 'linear' },
|
||||
gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50 },
|
||||
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
|
||||
logLevel: 0,
|
||||
flowchart: { curve: 'linear', "htmlLabels": false },
|
||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
fontFamily: '"arial", sans-serif',
|
||||
curve: 'linear',
|
||||
securityLevel: 'loose'
|
||||
});
|
||||
</script>
|
||||
function callback(){alert('It worked');}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<head>
|
||||
<script src="/e2e.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Mansalva&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
/* font-family: 'Mansalva', cursive;
|
||||
font-family: 'Mansalva', cursive; */
|
||||
font-family: 'times';
|
||||
/* font-family: 'Mansalva', cursive;*/
|
||||
font-family: 'Mansalva', cursive;
|
||||
}
|
||||
/* .mermaid-main-font {
|
||||
font-family: "trebuchet ms", verdana, arial;
|
||||
@@ -33,7 +33,8 @@
|
||||
// "themeCSS": ":root { --mermaid-font-family: \"trebuchet ms\", verdana, arial;}",
|
||||
// fontFamily: '\"trebuchet ms\", verdana, arial;'
|
||||
// fontFamily: '"Comic Sans MS", "Comic Sans", cursive'
|
||||
fontFamily: '"Mansalva", cursive'
|
||||
// fontFamily: '"Mansalva", cursive',
|
||||
fontFamily: '"Noto Sans SC", sans-serif'
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
39
cypress/platform/huge.html
Normal file
41
cypress/platform/user-journey.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>User Journey</h1>
|
||||
<div class="mermaid">
|
||||
journey
|
||||
title Go shopping
|
||||
|
||||
section Get to the shops
|
||||
Get car keys:5: Dad
|
||||
Get into car:5: Dad, Mum, Child 1, Child 2
|
||||
Really drive to supermarket:3: Dad
|
||||
|
||||
section Do shopping
|
||||
Do actual shop:3: Mum
|
||||
Get in the way:2: Dad, Child 1, Child 2
|
||||
Pay: 2: Dad
|
||||
|
||||
section Go home
|
||||
Lose keys:3: Dad
|
||||
Get cross:1: Dad, Child 1
|
||||
Find keys:4: Mum
|
||||
Get into car:4: Dad, Mum, Child 1, Child 2
|
||||
Drive home:3: Dad
|
||||
</div>
|
||||
<script src="./mermaid.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
logLevel: 3,
|
||||
journey: { taskMargin: 30 },
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,13 +14,23 @@ const contentLoaded = function() {
|
||||
const graphObj = JSON.parse(Base64.decode(graphBase64));
|
||||
// const graph = 'hello'
|
||||
console.log(graphObj);
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
div.innerHTML = graphObj.code;
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
if (Array.isArray(graphObj.code)) {
|
||||
const numCodes = graphObj.code.length;
|
||||
for (let i = 0; i < numCodes; i++) {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block' + i;
|
||||
div.className = 'mermaid';
|
||||
div.innerHTML = graphObj.code[i];
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
} else {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
div.innerHTML = graphObj.code;
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
global.mermaid.initialize(graphObj.mermaid);
|
||||
// console.log('graphObj.mermaid', graphObj.mermaid)
|
||||
global.mermaid.init();
|
||||
}
|
||||
};
|
||||
@@ -31,23 +41,53 @@ const contentLoadedApi = function() {
|
||||
const graphBase64 = document.location.href.substr(pos);
|
||||
const graphObj = JSON.parse(Base64.decode(graphBase64));
|
||||
// const graph = 'hello'
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
// div.innerHTML = graphObj.code
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
global.mermaid.initialize(graphObj.mermaid);
|
||||
if (Array.isArray(graphObj.code)) {
|
||||
const numCodes = graphObj.code.length;
|
||||
const divs = [];
|
||||
let div;
|
||||
for (let i = 0; i < numCodes; i++) {
|
||||
div = document.createElement('div');
|
||||
div.id = 'block' + i;
|
||||
div.className = 'mermaid';
|
||||
// div.innerHTML = graphObj.code
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
divs[i] = div;
|
||||
}
|
||||
|
||||
mermaid2.render(
|
||||
'newid',
|
||||
graphObj.code,
|
||||
(svgCode, bindFunctions) => {
|
||||
div.innerHTML = svgCode;
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
|
||||
bindFunctions(div);
|
||||
},
|
||||
div
|
||||
);
|
||||
for (let i = 0; i < numCodes; i++) {
|
||||
mermaid2.render(
|
||||
'newid' + i,
|
||||
graphObj.code[i],
|
||||
(svgCode, bindFunctions) => {
|
||||
div.innerHTML = svgCode;
|
||||
|
||||
bindFunctions(div);
|
||||
},
|
||||
divs[i]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
// div.innerHTML = graphObj.code
|
||||
console.warn('graphObj.mermaid', graphObj.mermaid);
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
|
||||
mermaid2.render(
|
||||
'newid',
|
||||
graphObj.code,
|
||||
(svgCode, bindFunctions) => {
|
||||
div.innerHTML = svgCode;
|
||||
|
||||
if (bindFunctions) bindFunctions(div);
|
||||
},
|
||||
div
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@
|
||||
div.id = 'the-malware'
|
||||
div.className = 'malware'
|
||||
div.innerHTML = 'XSS Succeeded'
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
// const el = document.querySelector('.mermaid');
|
||||
// el.parentNode.removeChild(el);
|
||||
throw new Error('XSS Succeded');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
283
dist/index.html
vendored
@@ -16,6 +16,9 @@
|
||||
<div class="mermaid">
|
||||
info
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
title Exclusive end dates (Manual date should end on 3d)
|
||||
@@ -25,7 +28,6 @@
|
||||
2 Days: 1, 2019-01-01,2d
|
||||
Manual Date: 2, 2019-01-01,2019-01-03
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
title Inclusive end dates (Manual date should end on 4th)
|
||||
@@ -36,6 +38,27 @@
|
||||
2 Days: 1, 2019-01-01,2d
|
||||
Manual Date: 2, 2019-01-01,2019-01-03
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
title Hide today marker (vertical line should not be visible)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d
|
||||
todayMarker off
|
||||
section Section1
|
||||
Today: 1, -1h
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
title Style today marker (vertical line should be 5px wide and half-transparent blue)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d
|
||||
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
|
||||
section Section1
|
||||
Today: 1, -1h
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
|
||||
@@ -289,16 +312,125 @@ graph TB
|
||||
style 456ac9b0d15a8b7f1e71073221059886 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{{Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?}}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[Car]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{{Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?}}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[Car]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph TD
|
||||
A([stadium shape test])
|
||||
A -->|Get money| B([Go shopping])
|
||||
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
|
||||
C -->|One| D([Laptop])
|
||||
C -->|Two| E([iPhone])
|
||||
C -->|Three| F([Car<br/>wroom wroom])
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
A[[subroutine shape test]]
|
||||
A -->|Get money| B[[Go shopping]]
|
||||
B --> C[[Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?]]
|
||||
C -->|One| D[[Laptop]]
|
||||
C -->|Two| E[[iPhone]]
|
||||
C -->|Three| F[[Car<br/>wroom wroom]]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
A[(cylindrical<br />shape<br />test)]
|
||||
A -->|Get money| B1[(Go shopping 1)]
|
||||
A -->|Get money| B2[(Go shopping 2)]
|
||||
A -->|Get money| B3[(Go shopping 3)]
|
||||
C[(Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?)]
|
||||
B1 --> C
|
||||
B2 --> C
|
||||
B3 --> C
|
||||
C -->|One| D[(Laptop)]
|
||||
C -->|Two| E[(iPhone)]
|
||||
C -->|Three| F[(Car)]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
A1[Multi<br>Line] -->|Multi<br>Line| B1(Multi<br>Line)
|
||||
C1[Multi<br/>Line] -->|Multi<br/>Line| D1(Multi<br/>Line)
|
||||
E1[Multi<br />Line] -->|Multi<br />Line| F1(Multi<br />Line)
|
||||
A2[Multi<br>Line] -->|Multi<br>Line| B2(Multi<br>Line)
|
||||
C2[Multi<br/>Line] -->|Multi<br/>Line| D2(Multi<br/>Line)
|
||||
E2[Multi<br />Line] -->|Multi<br />Line| F2(Multi<br />Line)
|
||||
linkStyle 0 stroke:DarkGray,stroke-width:2px
|
||||
linkStyle 1 stroke:DarkGray,stroke-width:2px
|
||||
linkStyle 2 stroke:DarkGray,stroke-width:2px
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
A(( )) -->|step 1| B(( ))
|
||||
B(( )) -->|step 2| C(( ))
|
||||
C(( )) -->|step 3| D(( ))
|
||||
linkStyle 1 stroke:greenyellow,stroke-width:2px
|
||||
style C fill:greenyellow,stroke:green,stroke-width:4px
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph TB
|
||||
TITLE["Link Click Events<br>(click the nodes below)"]
|
||||
A[link test]
|
||||
B[anchor test]
|
||||
C[mailto test]
|
||||
D[other protocol test]
|
||||
E[script test]
|
||||
TITLE --> A & B & C & D & E
|
||||
click A "https://mermaid-js.github.io/mermaid/#/" "link test"
|
||||
click B "#link-clicked" "anchor test"
|
||||
click C "mailto:user@user.user" "mailto test"
|
||||
click D "notes://do-your-thing/id" "other protocol test"
|
||||
click E "javascript:alert('test')" "script test"
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
A[red<br>text] -->|red<br>text| B(blue<br>text)
|
||||
C[/red<br/>text/] -->|blue<br/>text| D{blue<br/>text}
|
||||
E{{default<br />style}} -->|default<br />style| F([default<br />style])
|
||||
linkStyle default color:Sienna;
|
||||
linkStyle 0 color:red;
|
||||
linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
|
||||
style A color:red;
|
||||
style B color:blue;
|
||||
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
|
||||
style D stroke:#0000ff,fill:#ccccff,color:#0000ff
|
||||
click B "index.html#link-clicked" "link test"
|
||||
click D testClick "click test"
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph TD
|
||||
A[myClass1] --> B[default] & C[default]
|
||||
B[default] & C[default] --> D[myClass2]
|
||||
classDef default stroke-width:2px,fill:none,stroke:silver
|
||||
classDef node color:red
|
||||
classDef myClass1 color:#0000ff
|
||||
classDef myClass2 stroke:#0000ff,fill:#ccccff
|
||||
class A myClass1
|
||||
class D myClass2
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
@@ -331,6 +463,29 @@ and
|
||||
Alice -->> John: Parallel message 2
|
||||
end
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
sequenceDiagram
|
||||
participant 1 as multiline<br>using #lt;br#gt;
|
||||
participant 2 as multiline<br/>using #lt;br/#gt;
|
||||
participant 3 as multiline<br />using #lt;br /#gt;
|
||||
participant 4 as multiline<br />using #lt;br /#gt;
|
||||
1->>2: multiline<br>using #lt;br#gt;
|
||||
note right of 2: multiline<br>using #lt;br#gt;
|
||||
2->>3: multiline<br/>using #lt;br/#gt;
|
||||
note right of 3: multiline<br/>using #lt;br/#gt;
|
||||
3->>4: multiline<br />using #lt;br /#gt;
|
||||
note right of 4: multiline<br />using #lt;br /#gt;
|
||||
4->>1: multiline<br />using #lt;br /#gt;
|
||||
note right of 1: multiline<br />using #lt;br /#gt;
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
Alice->>John: Hello John,<br>how are you?
|
||||
Alice->>John: John,<br/>can you hear me?
|
||||
John-->>Alice: Hi Alice,<br />I can hear you!
|
||||
John-->>Alice: I feel great!
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
@@ -372,6 +527,37 @@ Describe gantt syntax :after doc1, 3d
|
||||
Add gantt diagram to demo page : 20h
|
||||
Add another diagram to demo page : 48h
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d/%m
|
||||
title GANTT diagram with multiline section titles
|
||||
excludes weekdays 2014-01-10
|
||||
|
||||
section A section<br>multiline
|
||||
Completed task : done, des1, 2014-01-06,2014-01-08
|
||||
Active task : active, des2, 2014-01-09, 3d
|
||||
Future task : des3, after des2, 5d
|
||||
Future task2 : des4, after des3, 5d
|
||||
|
||||
section Critical tasks<br/>multiline
|
||||
Completed task in the critical line : crit, done, 2014-01-06, 24h
|
||||
Implement parser and jison : crit, done, after des1, 2d
|
||||
Create tests for parser : crit, active, 3d
|
||||
Future task in critical line : crit, 5d
|
||||
Create tests for renderer : 2d
|
||||
Add to mermaid : 1d
|
||||
|
||||
section Documentation<br />multiline
|
||||
Describe gantt syntax : active, a1, after des1, 3d
|
||||
Add gantt diagram to demo page : after a1, 20h
|
||||
Add another diagram to demo page : doc1, after a1, 48h
|
||||
|
||||
section Last section<br />multiline
|
||||
Describe gantt syntax : after doc1, 3d
|
||||
Add gantt diagram to demo page : 20h
|
||||
Add another diagram to demo page : 48h
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
@@ -408,9 +594,9 @@ Class09 "0" --* "1..n" C3
|
||||
Class09 --|> Class07
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class01 : #size()
|
||||
Class01 : -int chimp
|
||||
Class01 : +int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
@@ -418,20 +604,58 @@ class Class10 {
|
||||
size()
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
classDiagram
|
||||
class Class01~T~
|
||||
Class01 : #size()
|
||||
Class01 : -int chimp
|
||||
Class01 : +int gorilla
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
size()
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
classDiagram
|
||||
Class01~T~ <|-- AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03~T~ "0" *-- "0..n" Class04
|
||||
Class05 "1" o-- "many" Class06
|
||||
Class07~T~ .. Class08
|
||||
Class09 "many" --> "1" C2 : Where am i?
|
||||
Class09 "0" --* "1..n" C3
|
||||
Class09 --|> Class07
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : #size()
|
||||
Class01 : -int chimp
|
||||
Class01 : +int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
size()
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mermaid">
|
||||
stateDiagram
|
||||
State1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="mermaid">
|
||||
stateDiagram
|
||||
[*] --> First
|
||||
state First {
|
||||
[*] --> second
|
||||
second --> [*]
|
||||
}
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="mermaid">
|
||||
stateDiagram
|
||||
[*] --> First
|
||||
state First {
|
||||
[*] --> second
|
||||
second --> [*]
|
||||
}
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
stateDiagram
|
||||
State1: The state with a note
|
||||
@@ -441,8 +665,16 @@ class Class10 {
|
||||
end note
|
||||
State1 --> State2
|
||||
note left of State2 : This is the note to the left.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
stateDiagram
|
||||
State1
|
||||
note right of State1
|
||||
Line1<br>Line2<br/>Line3<br />Line4<br />Line5
|
||||
end note
|
||||
</div>
|
||||
|
||||
<h1 id="link-clicked">Anchor for "link-clicked" test</h1>
|
||||
|
||||
<script src="./mermaid.js"></script>
|
||||
<script>
|
||||
@@ -450,6 +682,7 @@ class Class10 {
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
flowchart: { curve: 'basis' },
|
||||
gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50 },
|
||||
|
||||
145
docs/README.md
@@ -1,38 +1,9 @@
|
||||
[](https://travis-ci.org/knsv/mermaid)
|
||||
# Mermaid
|
||||
|
||||
[](https://travis-ci.org/mermaid-js/mermaid)
|
||||
[](https://coveralls.io/github/knsv/mermaid?branch=master)
|
||||
[](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
# mermaid
|
||||
|
||||
## New diagrams in 8.4
|
||||
|
||||
With version 8.4 class diagrams has got some new features, bug fixes and documentation. Another new feature in 8.4 is the new diagram
|
||||
type, state diagrams.
|
||||
|
||||

|
||||
|
||||
|
||||
## Special note regarding version 8.2
|
||||
|
||||
In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams.
|
||||
|
||||
* **true**: (default) tags in text are encoded, click functionality is disabled
|
||||
* false: tags in text are allowed, click functionality is enabled
|
||||
|
||||
Closed issues:
|
||||
|
||||
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
|
||||
|
||||
If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed.
|
||||
|
||||
```javascript
|
||||
mermaidAPI.initialize({
|
||||
securityLevel: 'loose'
|
||||
});
|
||||
```
|
||||
|
||||
**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/knsv/mermaid/issues/866)**
|
||||
|
||||

|
||||
|
||||
Generation of diagrams and flowcharts from text in a similar manner as markdown.
|
||||
@@ -41,8 +12,39 @@ Ever wanted to simplify documentation and avoid heavy tools like Visio when expl
|
||||
|
||||
This is why mermaid was born, a simple markdown-like script language for generating charts from text via javascript.
|
||||
|
||||
Check out the list of [Integrations and Usages of Mermaid](./integrations.md)
|
||||
|
||||
**Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project.**
|
||||
|
||||
## New diagrams in 8.5
|
||||
|
||||
With version 8.5 there are some bug fixes and enhancements, plus a new diagram type, entity relationship diagrams.
|
||||
|
||||

|
||||
|
||||
## Special note regarding version 8.2
|
||||
|
||||
In version 8.2 a security improvement was introduced. A securityLevel configuration was introduced which sets the level of trust to be used on the parsed diagrams.
|
||||
|
||||
- **true**: (default) tags in text are encoded, click functionality is disabled
|
||||
- false: tags in text are allowed, click functionality is enabled
|
||||
|
||||
Closed issues:
|
||||
|
||||
⚠️ **Note** : This changes the default behaviour of mermaid so that after upgrade to 8.2, if the securityLevel is not configured, tags in flowcharts are encoded as tags and clicking is prohibited.
|
||||
|
||||
If your application is taking resposibility for the diagram source security you can set the securityLevel accordingly. By doing this clicks and tags are again allowed.
|
||||
|
||||
```javascript
|
||||
mermaidAPI.initialize({
|
||||
securityLevel: 'loose'
|
||||
});
|
||||
```
|
||||
|
||||
**🖖 Keep a steady pulse: mermaid needs more Collaborators [#866](https://github.com/knsv/mermaid/issues/866)**
|
||||
|
||||
## Diagrams
|
||||
|
||||
### Flowchart
|
||||
|
||||
```
|
||||
@@ -52,8 +54,8 @@ graph TD;
|
||||
B-->D;
|
||||
C-->D;
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
### Sequence diagram
|
||||
|
||||
@@ -70,8 +72,8 @@ sequenceDiagram
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
### Gantt diagram
|
||||
|
||||
@@ -87,8 +89,8 @@ Active task :active, des2, 2014-01-09, 3d
|
||||
Future task : des3, after des2, 5d
|
||||
Future task2 : des4, after des3, 5d
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
### Class diagram - :exclamation: experimental
|
||||
|
||||
@@ -108,8 +110,8 @@ Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
### Git graph - :exclamation: experimental
|
||||
|
||||
@@ -135,6 +137,17 @@ merge newbranch
|
||||
|
||||

|
||||
|
||||
### Entity Relationship Diagram - :exclamation: experimental
|
||||
|
||||
```
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
@@ -154,12 +167,10 @@ Example: https://unpkg.com/mermaid@7.1.0/dist/
|
||||
yarn add mermaid
|
||||
```
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
https://mermaidjs.github.io
|
||||
|
||||
|
||||
## Sibling projects
|
||||
|
||||
- [mermaid CLI](https://github.com/mermaidjs/mermaid.cli)
|
||||
@@ -167,8 +178,7 @@ https://mermaidjs.github.io
|
||||
- [mermaid webpack demo](https://github.com/mermaidjs/mermaid-webpack-demo)
|
||||
- [mermaid Parcel demo](https://github.com/mermaidjs/mermaid-parcel-demo)
|
||||
|
||||
|
||||
# Request for assistance
|
||||
## Request for assistance
|
||||
|
||||
Things are piling up and I have a hard time keeping up. To remedy this
|
||||
it would be great if we could form a core team of developers to cooperate
|
||||
@@ -178,61 +188,62 @@ As part of this team you would get write access to the repository and would
|
||||
represent the project when answering questions and issues.
|
||||
|
||||
Together we could continue the work with things like:
|
||||
* adding more types of diagrams like mindmaps, ert diagrams, etc.
|
||||
* improving existing diagrams
|
||||
|
||||
- Adding more types of diagrams like mindmaps, ert diagrams, etc.
|
||||
- Improving existing diagrams
|
||||
|
||||
Don't hesitate to contact me if you want to get involved.
|
||||
|
||||
## For contributors
|
||||
|
||||
# For contributors
|
||||
### Setup
|
||||
|
||||
## Setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
yarn install
|
||||
### Build
|
||||
|
||||
```
|
||||
yarn build:watch
|
||||
```
|
||||
|
||||
## Build
|
||||
### Lint
|
||||
|
||||
yarn build:watch
|
||||
|
||||
|
||||
## Lint
|
||||
|
||||
yarn lint
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
We use [eslint](https://eslint.org/).
|
||||
We recommend you installing [editor plugins](https://eslint.org/docs/user-guide/integrations) so you can get real time lint result.
|
||||
|
||||
### Test
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
Manual test in browser: open `dist/index.html`
|
||||
|
||||
## Test
|
||||
|
||||
yarn test
|
||||
|
||||
Manual test in browser:
|
||||
|
||||
open dist/index.html
|
||||
|
||||
|
||||
## Release
|
||||
### Release
|
||||
|
||||
For those who have the permission to do so:
|
||||
|
||||
Update version number in `package.json`.
|
||||
|
||||
npm publish
|
||||
```
|
||||
npm publish
|
||||
```
|
||||
|
||||
Command above generates files into the `dist` folder and publishes them to npmjs.org.
|
||||
|
||||
|
||||
# Credits
|
||||
## Credits
|
||||
|
||||
Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!
|
||||
|
||||
Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering.
|
||||
|
||||
*Mermaid was created by Knut Sveidqvist for easier documentation.*
|
||||
_Mermaid was created by Knut Sveidqvist for easier documentation._
|
||||
|
||||
*[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017.*
|
||||
_[Tyler Long](https://github.com/tylerlong) has became a collaborator since April 2017._
|
||||
|
||||
Here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors).
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
- [Sequence diagram](sequenceDiagram.md)
|
||||
- [Class Diagram](classDiagram.md)
|
||||
- [State Diagram](stateDiagram.md)
|
||||
- [Entity Relationship Diagram](entityRelationshipDiagram.md)
|
||||
- [User Journey](user-journey.md)
|
||||
- [Gantt](gantt.md)
|
||||
- [Pie Chart](pie.md)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Mermaid can render class diagrams.
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
```mermaid
|
||||
classDiagram
|
||||
@@ -52,8 +52,8 @@ Mermaid can render class diagrams.
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Syntax
|
||||
@@ -105,55 +105,120 @@ Naming convention: a class name should be composed of alphanumeric (unicode allo
|
||||
|
||||
UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.
|
||||
|
||||
#### Visibility
|
||||
To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but is it optional:
|
||||
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes.
|
||||
|
||||
- `+` Public
|
||||
- `-` Private
|
||||
- `#` Protected
|
||||
- `~` Package
|
||||
|
||||
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The one with `()` are treated as functions/methods, and others as attributes.
|
||||
|
||||
There are two ways to define the members of a class, and regardless of the whichever syntax is used to define the members, the output will still be same. The two different ways are :
|
||||
- Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example:
|
||||
There are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are :
|
||||
- Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example:
|
||||
|
||||
```
|
||||
class BankAccount
|
||||
BankAccount : +String owner
|
||||
BankAccount : +BigDecimal balance
|
||||
BankAccount : +deposit(amount)
|
||||
BankAccount : +withdrawl(amount)
|
||||
BankAccount : +withdrawal(amount)
|
||||
```
|
||||
```mermaid
|
||||
|
||||
``` mermaid
|
||||
classDiagram
|
||||
class BankAccount
|
||||
BankAccount : +String owner
|
||||
BankAccount : +BigDecimal balance
|
||||
BankAccount : +deposit(amount)
|
||||
BankAccount : +withdrawl(amount)
|
||||
```
|
||||
```
|
||||
|
||||
- Associate members of a class using **{}** brackets, where members are grouped within curly brackets. Suitable for defining multiple members at once. For example:
|
||||
```
|
||||
class BankAccount{
|
||||
+String owner
|
||||
+BigDecimal balance
|
||||
+deposit(amount)
|
||||
+deposit(amount) bool
|
||||
+withdrawl(amount)
|
||||
}
|
||||
```
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BankAccount{
|
||||
+String owner
|
||||
+BigDecimal balance
|
||||
+deposit(amount) bool
|
||||
+withdrawl(amount) int
|
||||
}
|
||||
```
|
||||
|
||||
#### Return Type
|
||||
Optionally you can end the method/function definition with the data type that will be returned (note: there must be a space between the final `)` of the method definition and return type
|
||||
example:
|
||||
```
|
||||
class BankAccount{
|
||||
+String owner
|
||||
+BigDecimal balance
|
||||
+deposit(amount) bool
|
||||
+withdrawl(amount) int
|
||||
}
|
||||
```
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BankAccount{
|
||||
+String owner
|
||||
+BigDecimal balance
|
||||
+deposit(amount)
|
||||
+withdrawl(amount)
|
||||
}```
|
||||
+deposit(amount) bool
|
||||
+withdrawl(amount) int
|
||||
}
|
||||
```
|
||||
|
||||
#### Generic Types
|
||||
Members can be defined using generic types, such as `List<int>`, for fields, parameters and return types by enclosing the type within `~` (**tilde**). Note: **nested** type declarations (such as `List<List<int>>`) are not currently supported
|
||||
|
||||
This can be done as part of either class definition method:
|
||||
|
||||
```
|
||||
classDiagram
|
||||
class Square~Shape~{
|
||||
int id
|
||||
List~int~ position
|
||||
setPoints(List~int~ points)
|
||||
getPoints() List~int~
|
||||
}
|
||||
|
||||
Square : -List~string~ messages
|
||||
Square : +setMessages(List~string~ messages)
|
||||
Square : +getMessages() List~string~
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Square~Shape~{
|
||||
int id
|
||||
List~int~ position
|
||||
setPoints(List~int~ points)
|
||||
getPoints() List~int~
|
||||
}
|
||||
|
||||
Square : -List~string~ messages
|
||||
Square : +setMessages(List~string~ messages)
|
||||
Square : +getMessages() List~string~
|
||||
```
|
||||
|
||||
#### Return Type
|
||||
Optionally you can end the method/function definition with the data type that will be returned
|
||||
|
||||
#### Visibility
|
||||
To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but it is optional:
|
||||
|
||||
- `+` Public
|
||||
- `-` Private
|
||||
- `#` Protected
|
||||
- `~` Package/Internal
|
||||
|
||||
>_note_ you can also include additional _classifers_ to a method definition by adding the following notations to the end of the method, i.e.: after the `()`:
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$`
|
||||
|
||||
|
||||
## Defining Relationship
|
||||
A relationship is a general term covering the specific types of logical connections found on class and object diagrams.
|
||||
A relationship is a general term covering the specific types of logical connections found on class and object diagrams.
|
||||
```
|
||||
[classA][Arrow][ClassB]:LabelText
|
||||
```
|
||||
@@ -161,22 +226,26 @@ A relationship is a general term covering the specific types of logical connecti
|
||||
There are different types of relations defined for classes under UML which are currently supported:
|
||||
|
||||
Type | Description
|
||||
--- | ---
|
||||
<\|--| Inheritance
|
||||
*-- | Composition
|
||||
o-- | Aggregation
|
||||
--> | Association
|
||||
-- | Link
|
||||
--- | ---
|
||||
<\|-- | Inheritance
|
||||
*-- | Composition
|
||||
o-- | Aggregation
|
||||
--> | Association
|
||||
-- | Link (Solid)
|
||||
..> | Dependency
|
||||
..\|> | Realization
|
||||
.. | Link (Dashed)
|
||||
|
||||
<!--- TODO ..> Dependency--->
|
||||
```
|
||||
classDiagram
|
||||
classA <|-- classB
|
||||
classC *-- classD
|
||||
classE o-- classF
|
||||
classG <-- classH
|
||||
classI <.. classJ
|
||||
classK .. classL
|
||||
classI -- classJ
|
||||
classK <.. classL
|
||||
classM <|.. classN
|
||||
classO .. classP
|
||||
|
||||
```
|
||||
|
||||
@@ -186,34 +255,40 @@ classA <|-- classB
|
||||
classC *-- classD
|
||||
classE o-- classF
|
||||
classG <-- classH
|
||||
classI <.. classJ
|
||||
classK .. classL
|
||||
classI -- classJ
|
||||
classK <.. classL
|
||||
classM <|.. classN
|
||||
classO .. classP
|
||||
|
||||
```
|
||||
We can use the arrowheads in opposite directions as well :
|
||||
We can use the labels to describe nature of relation between two classes. Also, arrowheads can be used in opposite directions as well :
|
||||
```
|
||||
classDiagram
|
||||
classA --|> classB
|
||||
classC --* classD
|
||||
classE --o classF
|
||||
classG <--> classH
|
||||
classI ..> classJ
|
||||
classK .. classL
|
||||
classA --|> classB : Inheritance
|
||||
classC --* classD : Composition
|
||||
classE --o classF : Aggregation
|
||||
classG --> classH : Association
|
||||
classI -- classJ : Link(Solid)
|
||||
classK ..> classL : Dependency
|
||||
classM ..|> classN : Realization
|
||||
classO .. classP : Link(Dashed)
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
classA --|> classB
|
||||
classC --* classD
|
||||
classE --o classF
|
||||
classG <--> classH
|
||||
classI ..> classJ
|
||||
classK .. classL
|
||||
classA --|> classB : Inheritance
|
||||
classC --* classD : Composition
|
||||
classE --o classF : Aggregation
|
||||
classG --> classH : Association
|
||||
classI -- classJ : Link(Solid)
|
||||
classK ..> classL : Dependency
|
||||
classM ..|> classN : Realization
|
||||
classO .. classP : Link(Dashed)
|
||||
|
||||
|
||||
```
|
||||
## Labels on Relations
|
||||
## Labels on Relations
|
||||
|
||||
It is possible to add a label text to a relation:
|
||||
```
|
||||
@@ -234,7 +309,7 @@ classE o-- classF : association
|
||||
## Cardinality / Multiplicity on relations
|
||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class linked to one instance of the other class. For example, one company will have one or more employees, but each employee works for just one company.
|
||||
|
||||
Multiplicity notations are placed near the ends of an association.
|
||||
Multiplicity notations are placed near the ends of an association.
|
||||
|
||||
The different cardinality options are :
|
||||
- `0..1` Zero or one
|
||||
@@ -243,7 +318,7 @@ The different cardinality options are :
|
||||
- `1..*` One or more
|
||||
- `*` Many
|
||||
- `n` n {where n>1}
|
||||
- `0..n` zeor to n {where n>1}
|
||||
- `0..n` zero to n {where n>1}
|
||||
- `1..n` one to n {where n>1}
|
||||
|
||||
Cardinality can be easily defined by placing cardinality text within qoutes `"` before(optional) and after(optional) a given arrow.
|
||||
@@ -262,7 +337,7 @@ classDiagram
|
||||
Student "1" --> "1..*" Course
|
||||
Galaxy --> "many" Star : Contains
|
||||
```
|
||||
## Annotations on classes
|
||||
## Annotations on classes
|
||||
|
||||
It is possible to annotate classes with a specific marker text which is like meta-data for the class, giving a clear indication about its nature. Some common annotations examples could be:
|
||||
- `<<Interface>>` To represent an Interface class
|
||||
@@ -322,7 +397,115 @@ class Color{
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a class diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any class diagram syntax
|
||||
|
||||
```
|
||||
classDiagram
|
||||
%% This whole line is a comment classDiagram class Shape <<interface>>
|
||||
class Shape{
|
||||
<<interface>>
|
||||
noOfVertices
|
||||
draw()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Interaction
|
||||
|
||||
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||
|
||||
You would define these actions on a separate line after all classes have been declared.
|
||||
|
||||
```
|
||||
action className "reference" "tooltip"
|
||||
```
|
||||
|
||||
* _action_ is either `link` or `callback`, depending on which type of interaction you want to have called
|
||||
* _className_ is the id of the node that the action will be associated with
|
||||
* _reference_ is either the url link, or the function name for callback. (note: callback function will be called with the nodeId as parameter).
|
||||
* (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
|
||||
|
||||
### Examples:
|
||||
|
||||
*URL Link:*
|
||||
|
||||
```
|
||||
classDiagram
|
||||
class Shape
|
||||
link Shape "http://www.github.com" "This is a tooltip for a link"
|
||||
```
|
||||
|
||||
*Callback:*
|
||||
|
||||
```
|
||||
classDiagram
|
||||
class Shape
|
||||
callback Shape "callbackFunction" "This is a tooltip for a callback"
|
||||
```
|
||||
|
||||
```
|
||||
<script>
|
||||
var callbackFunction = function(){
|
||||
alert('A callback was triggered');
|
||||
}
|
||||
<script>
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Class01
|
||||
class Class02
|
||||
callback Class01 "callbackFunction" "Callback tooltip"
|
||||
link Class02 "http://www.github.com" "This is a link"
|
||||
```
|
||||
|
||||
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
|
||||
|
||||
Beginners tip, a full example using interactive links in an html context:
|
||||
```
|
||||
<body>
|
||||
<div class="mermaid">
|
||||
classDiagram
|
||||
Animal <|-- Duck
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
Animal : +String gender
|
||||
Animal: +isMammal()
|
||||
Animal: +mate()
|
||||
class Duck{
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
class Fish{
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
|
||||
callback Duck callback "Tooltip"
|
||||
click Zebra "http://www.github.com" "This is a link"
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var callback = function(){
|
||||
alert('A callback was triggered');
|
||||
}
|
||||
var config = {
|
||||
startOnLoad:true,
|
||||
securityLevel:'loose',
|
||||
};
|
||||
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
@@ -334,7 +517,7 @@ Class | Description
|
||||
--- | ---
|
||||
g.classGroup text | Styles for general class text
|
||||
classGroup .title | Styles for general class title
|
||||
g.classGroup rect | Styles for class diagram rectangle
|
||||
g.classGroup rect | Styles for class diagram rectangle
|
||||
g.classGroup line | Styles for class diagram line
|
||||
.classLabel .box | Styles for class label box
|
||||
.classLabel .label | Styles for class label text
|
||||
|
||||
@@ -3,9 +3,31 @@
|
||||
|
||||
## Updating the documentation
|
||||
|
||||
We write documention with GitBook.
|
||||
Please continue writing documentation at [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs).
|
||||
|
||||
Please continue with the [mermaid-gitbook](https://github.com/mermaidjs/mermaid-gitbook) project.
|
||||
We publish documentation using GitHub Pages.
|
||||
|
||||
|
||||
### Questions and/or suggestions ?
|
||||
After logging in at [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22).
|
||||
|
||||
### How to contribute a suggestion
|
||||
Markdown is used to format the text, for more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).
|
||||
|
||||
If you want to use an editor on your own computer, you may follow these steps:
|
||||
* Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs) directory on the develop branch.
|
||||
* Create a fork of the develop branch.
|
||||
* Make changes or add new documentation.
|
||||
* Commit changes to your fork and push it to GitHub.
|
||||
* Create a pull request of your fork.
|
||||
|
||||
If you don't have such editor on your computer, you may follow these steps:
|
||||
* Login at [GitHub.com](https://www.github.com).
|
||||
* Navigate to [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs).
|
||||
* To edit a file, click the pencil icon at the top-right of the file contents panel.
|
||||
* Describe what you changed in the "Propose file change" section, located at the bottom of the page.
|
||||
* Submit your changes by clicking the button "Propose file change" at the bottom (by automatic creation of a fork and a new branch).
|
||||
* Create a pull request of your newly forked branch, by clicking the green "Create pull request" button.
|
||||
|
||||
|
||||
## How to add a new diagram type
|
||||
|
||||
80
docs/entityRelationshipDiagram.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Entity Relationship Diagrams
|
||||
|
||||
> An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia.
|
||||
|
||||
Note that practitioners of ER modelling almost always refer to *entity types* simply as *entities*. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract *instance* of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
|
||||
|
||||
Mermaid can render ER diagrams
|
||||
```
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
```mermaid
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
|
||||
Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
|
||||
|
||||
Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.
|
||||
|
||||
## Status
|
||||
|
||||
ER diagrams are a new feature in Mermaid and are **experimental**. There are likely to be a few bugs and constraints, and enhancements will be made in due course. Currently you can only define entities and relationships, but not attributes.
|
||||
|
||||
## Syntax
|
||||
|
||||
### Entities and Relationships
|
||||
|
||||
Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts, all of which are mandatory:
|
||||
```
|
||||
<first-entity> <relationship> <second-entity> : <relationship-label>
|
||||
```
|
||||
Where:
|
||||
|
||||
- `first-entity` is the name of an entity. Names must begin with an alphabetic character and may also contain digits and hyphens
|
||||
- `relationship` describes the way that both entities inter-relate. See below.
|
||||
- `second-entity` is the name of the other entity
|
||||
- `relationship-label` describes the relationship from the perspective of the first entity.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
PROPERTY ||--|{ ROOM : contains
|
||||
```
|
||||
|
||||
This statement can be read as *a property contains one or more rooms, and a room is part of one and only one property*. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
|
||||
|
||||
### Relationship Syntax
|
||||
|
||||
The `relationship` part of each statement can be broken down into three sub-components:
|
||||
|
||||
- the cardinality of the first entity with respect to the second,
|
||||
- whether the relationship confers identity on a 'child' entity
|
||||
- the cardinality of the second entity with respect to the first
|
||||
|
||||
Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a `PROPERTY` can have one or more `ROOM` instances associated to it, whereas a `ROOM` can only be associated with one `PROPERTY`. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities.
|
||||
|
||||
| Value (left) | Value (right) | Meaning |
|
||||
|:------------:|:-------------:|--------------------------------------------------------|
|
||||
| `\|o` | `o\|` | Zero or one |
|
||||
| `\|\|` | `\|\|` | Exactly one |
|
||||
| `}o` | `o{` | Zero or more (no upper limit) |
|
||||
| `}\|` | `\|{` | One or more (no upper limit) |
|
||||
|
||||
### Identification
|
||||
|
||||
Relationships may be classified as either *identifying* or *non-identifying* and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on `NAMED-DRIVER`s. In modelling this we might start out by observing that a `CAR` can be driven by many `PERSON` instances, and a `PERSON` can drive many `CAR`s - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: `PERSON }|..|{ CAR : "driver"`. Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a `NAMED-DRIVER` cannot exist without both a `PERSON` and a `CAR` - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
|
||||
|
||||
```
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
```
|
||||
### Other Things
|
||||
|
||||
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
||||
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
||||
@@ -5,7 +5,7 @@ pie title NETFLIX
|
||||
"Time spent looking for movie" : 90
|
||||
"Time spent watching it" : 10
|
||||
```
|
||||
``` mermaid
|
||||
```mermaid
|
||||
pie title NETFLIX
|
||||
"Time spent looking for movie" : 90
|
||||
"Time spent watching it" : 10
|
||||
@@ -21,7 +21,7 @@ pie title What Voldemort doesn't have?
|
||||
"FRIENDS" : 2
|
||||
"FAMILY" : 3
|
||||
"NOSE" : 45
|
||||
```
|
||||
```
|
||||
## Basic sequence diagram
|
||||
|
||||
```
|
||||
@@ -35,8 +35,6 @@ sequenceDiagram
|
||||
Bob-->Alice: Checking with John...
|
||||
Alice->John: Yes... John, how are you?
|
||||
```
|
||||
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice ->> Bob: Hello Bob, how are you?
|
||||
@@ -49,9 +47,15 @@ sequenceDiagram
|
||||
Alice->John: Yes... John, how are you?
|
||||
```
|
||||
|
||||
|
||||
## Basic flowchart
|
||||
|
||||
```
|
||||
graph LR
|
||||
A[Square Rect] -- Link text --> B((Circle))
|
||||
A --> C(Round Rect)
|
||||
B --> D{Rhombus}
|
||||
C --> D
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Square Rect] -- Link text --> B((Circle))
|
||||
@@ -63,6 +67,29 @@ graph LR
|
||||
|
||||
## Larger flowchart with some styling
|
||||
|
||||
```
|
||||
graph TB
|
||||
sq[Square shape] --> ci((Circle shape))
|
||||
|
||||
subgraph A
|
||||
od>Odd shape]-- Two line<br/>edge comment --> ro
|
||||
di{Diamond with <br/> line break} -.-> ro(Rounded<br>square<br>shape)
|
||||
di==>ro2(Rounded square shape)
|
||||
end
|
||||
|
||||
%% Notice that no text in shape are added here instead that is appended further down
|
||||
e --> od3>Really long text with linebreak<br>in an Odd shape]
|
||||
|
||||
%% Comments after double percent signs
|
||||
e((Inner / circle<br>and some odd <br>special characters)) --> f(,.?!+-*ز)
|
||||
|
||||
cyr[Cyrillic]-->cyr2((Circle shape Начало));
|
||||
|
||||
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
|
||||
classDef orange fill:#f96,stroke:#333,stroke-width:4px;
|
||||
class sq,e green
|
||||
class di orange
|
||||
```
|
||||
```mermaid
|
||||
graph TB
|
||||
sq[Square shape] --> ci((Circle shape))
|
||||
@@ -90,6 +117,21 @@ graph TB
|
||||
|
||||
## Loops, alt and opt
|
||||
|
||||
```
|
||||
sequenceDiagram
|
||||
loop Daily query
|
||||
Alice->>Bob: Hello Bob, how are you?
|
||||
alt is sick
|
||||
Bob->>Alice: Not so good :(
|
||||
else is well
|
||||
Bob->>Alice: Feeling fresh like a daisy
|
||||
end
|
||||
|
||||
opt Extra response
|
||||
Bob->>Alice: Thanks for asking
|
||||
end
|
||||
end
|
||||
```
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
loop Daily query
|
||||
@@ -109,6 +151,19 @@ sequenceDiagram
|
||||
|
||||
## Message to self in loop
|
||||
|
||||
```
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts<br/>prevail...
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
|
||||
@@ -35,6 +35,9 @@ Possible directions are:
|
||||
|
||||
* TD - same as TB
|
||||
|
||||
## flowchart
|
||||
|
||||
This renders a flowchart in the same way as graph but with a new rendering method opening up for long requested features as more arrow types, multi directional arrows and link to and from subgraphs. Apart from the graph type flowchart/graph the syntax is the same. This is currently experimental but when the beta persioid is over both the graph and flowchart keyswords will render in the new way. This means it is ok to start betat testing flowcharts.
|
||||
|
||||
## Nodes & shapes
|
||||
|
||||
@@ -62,7 +65,7 @@ graph LR
|
||||
id1[This is the text in the box]
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1[This is the text in the box]
|
||||
```
|
||||
|
||||
@@ -70,33 +73,66 @@ graph LR
|
||||
### A node with round edges
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1(This is the text in the box)
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1(This is the text in the box)
|
||||
```
|
||||
|
||||
### A stadium-shaped node
|
||||
|
||||
```
|
||||
flowchart LR
|
||||
id1([This is the text in the box])
|
||||
```
|
||||
```mermaid
|
||||
flowchart LR
|
||||
id1([This is the text in the box])
|
||||
```
|
||||
|
||||
### A node in a subroutine shape
|
||||
|
||||
```
|
||||
flowchart LR
|
||||
id1[[This is the text in the box]]
|
||||
```
|
||||
```mermaid
|
||||
flowchart LR
|
||||
id1[[This is the text in the box]]
|
||||
```
|
||||
|
||||
### A node in a cylindrical shape
|
||||
|
||||
```
|
||||
flowchart LR
|
||||
id1[(Database)]
|
||||
```
|
||||
```mermaid
|
||||
flowchart LR
|
||||
id1[(Database)]
|
||||
```
|
||||
|
||||
### A node in the form of a circle
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1((This is the text in the circle))
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1((This is the text in the circle))
|
||||
```
|
||||
|
||||
### A node in an asymetric shape
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1>This is the text in the box]
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1>This is the text in the box]
|
||||
```
|
||||
Currently only the shape above is possible and not its mirror. *This might change with future releases.*
|
||||
@@ -104,25 +140,47 @@ Currently only the shape above is possible and not its mirror. *This might chang
|
||||
### A node (rhombus)
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1{This is the text in the box}
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1{This is the text in the box}
|
||||
```
|
||||
|
||||
### A hexagon node
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1{{This is the text in the box}}
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1{{This is the text in the box}}
|
||||
```
|
||||
|
||||
### Parallelogram
|
||||
|
||||
```
|
||||
graph TD
|
||||
id1[/This is the text in the box/]
|
||||
```
|
||||
```mermaid
|
||||
graph TD
|
||||
id1[/This is the text in the box/]
|
||||
```
|
||||
|
||||
### Parallelogram alt
|
||||
|
||||
```
|
||||
graph TD
|
||||
id1[\This is the text in the box\]
|
||||
```
|
||||
```mermaid
|
||||
graph TD
|
||||
id1[\This is the text in the box\]
|
||||
```
|
||||
|
||||
### Trapezoid
|
||||
|
||||
```
|
||||
@@ -151,22 +209,22 @@ Nodes can be connected with links/edges. It is possible to have different types
|
||||
### A link with arrow head
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-->B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-->B
|
||||
```
|
||||
|
||||
### An open link
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A --- B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A --- B
|
||||
```
|
||||
|
||||
@@ -174,87 +232,87 @@ graph LR
|
||||
|
||||
```
|
||||
graph LR
|
||||
A-- This is the text ---B
|
||||
A-- This is the text! ---B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-- This is the text ---B
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A---|This is the text|B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A---|This is the text|B
|
||||
```
|
||||
|
||||
### A link with arrow head and text
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-->|text|B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-->|text|B
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-- text -->B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-- text -->B
|
||||
```
|
||||
|
||||
### Dotted link
|
||||
|
||||
```
|
||||
graph LR;
|
||||
flowchart LR;
|
||||
A-.->B;
|
||||
```
|
||||
```mermaid
|
||||
graph LR;
|
||||
flowchart LR;
|
||||
A-.->B;
|
||||
```
|
||||
|
||||
### Dotted link with text
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-. text .-> B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A-. text .-> B
|
||||
```
|
||||
|
||||
### Thick link
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A ==> B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A ==> B
|
||||
```
|
||||
|
||||
### Thick link with text
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A == text ==> B
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A == text ==> B
|
||||
```
|
||||
|
||||
@@ -262,25 +320,90 @@ graph LR
|
||||
|
||||
It is possible declare many links in the same line as per below:
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A -- text --> B -- text2 --> C
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A -- text --> B -- text2 --> C
|
||||
```
|
||||
|
||||
It is also possible to declare multiple nodes links in the same line as per below:
|
||||
```
|
||||
flowchart LR
|
||||
a --> b & c--> d
|
||||
```
|
||||
```mermaid
|
||||
flowchart LR
|
||||
a --> b & c--> d
|
||||
```
|
||||
|
||||
You can then describe dependencies in a very expressive way. Like the onliner below:
|
||||
```
|
||||
graph TB
|
||||
A & B--> C & D
|
||||
```
|
||||
```mermaid
|
||||
graph TB
|
||||
A & B--> C & D
|
||||
```
|
||||
If you describe the same diagram using the the basic syntax, it will take four lines. A
|
||||
word of warning, one could go overboard with this making the graph harder to read in
|
||||
markdown form. The Swedish word `lagom` comes to mind. It means, not to much and not to little.
|
||||
This goes for expressive syntaxes as well.
|
||||
```
|
||||
graph TB
|
||||
A --> C
|
||||
A --> D
|
||||
B --> C
|
||||
B --> D
|
||||
```
|
||||
|
||||
## Beta: New arrow types
|
||||
|
||||
When using flowchart instead of graph there is the are new types of arrows supported as per below:
|
||||
|
||||
```
|
||||
flowchart LR
|
||||
A --o B
|
||||
B --x C
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A --o B
|
||||
B --x C
|
||||
```
|
||||
|
||||
|
||||
## Beta: multi directional arrows
|
||||
|
||||
When using flowchart instead of graph there is the possibility to use multidirectional arrows.
|
||||
|
||||
```
|
||||
flowchart LR
|
||||
A o--o B
|
||||
B <--> C
|
||||
C x--x D
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A o--o B
|
||||
B <--> C
|
||||
C x--x D
|
||||
```
|
||||
|
||||
## Special characters that break syntax
|
||||
|
||||
It is possible to put text within quotes in order to render more troublesome characters. As in the example below:
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1["This is the (text) in the box"]
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
id1["This is the (text) in the box"]
|
||||
```
|
||||
|
||||
@@ -289,11 +412,11 @@ graph LR
|
||||
It is possible to escape characters using the syntax examplified here.
|
||||
|
||||
```
|
||||
graph LR
|
||||
flowchart LR
|
||||
A["A double quote:#quot;"] -->B["A dec char:#9829;"]
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
flowchart LR
|
||||
A["A double quote:#quot;"] -->B["A dec char:#9829;"]
|
||||
```
|
||||
|
||||
@@ -334,6 +457,60 @@ graph TB
|
||||
end
|
||||
```
|
||||
|
||||
You can also set an excplicit id for the subgraph.
|
||||
|
||||
```
|
||||
graph TB
|
||||
c1-->a2
|
||||
subgraph ide1 [one]
|
||||
a1-->a2
|
||||
end
|
||||
```
|
||||
```mermaid
|
||||
graph TB
|
||||
c1-->a2
|
||||
subgraph id1 [one]
|
||||
a1-->a2
|
||||
end
|
||||
```
|
||||
|
||||
## Beta: flowcharts
|
||||
|
||||
With the graphtype flowcharts it is also possible to set edges to and from subgraphs as in the graph below.
|
||||
|
||||
```
|
||||
flowchart TB
|
||||
c1-->a2
|
||||
subgraph one
|
||||
a1-->a2
|
||||
end
|
||||
subgraph two
|
||||
b1-->b2
|
||||
end
|
||||
subgraph three
|
||||
c1-->c2
|
||||
end
|
||||
one --> two
|
||||
three --> two
|
||||
two --> c2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
c1-->a2
|
||||
subgraph one
|
||||
a1-->a2
|
||||
end
|
||||
subgraph two
|
||||
b1-->b2
|
||||
end
|
||||
subgraph three
|
||||
c1-->c2
|
||||
end
|
||||
one --> two
|
||||
three --> two
|
||||
two --> c2
|
||||
```
|
||||
|
||||
## Interaction
|
||||
|
||||
@@ -350,14 +527,14 @@ Examples of tooltip usage below:
|
||||
|
||||
```
|
||||
<script>
|
||||
var callback = function(){
|
||||
alert('A callback was triggered');
|
||||
}
|
||||
<script>
|
||||
var callback = function(){
|
||||
alert('A callback was triggered');
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
```
|
||||
graph LR;
|
||||
flowchart LR;
|
||||
A-->B;
|
||||
click A callback "Tooltip for a callback"
|
||||
click B "http://www.github.com" "This is a tooltip for a link"
|
||||
@@ -366,35 +543,37 @@ graph LR;
|
||||
The tooltip text is surrounded in double quotes. The styles of the tooltip are set by the class .mermaidTooltip.
|
||||
|
||||
```mermaid
|
||||
graph LR;
|
||||
graph LR
|
||||
A-->B;
|
||||
click A callback "Tooltip"
|
||||
click B "http://www.github.com" "This is a link"
|
||||
```
|
||||
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
|
||||
|
||||
?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/).
|
||||
|
||||
Beginners tip, a full example using interactive links in a html context:
|
||||
```
|
||||
<body>
|
||||
<div class="mermaid">
|
||||
graph LR;
|
||||
A-->B;
|
||||
click A callback "Tooltip"
|
||||
click B "http://www.github.com" "This is a link"
|
||||
A-->B;
|
||||
click A callback "Tooltip"
|
||||
click B "http://www.github.com" "This is a link"
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var callback = function(){
|
||||
var callback = function(){
|
||||
alert('A callback was triggered');
|
||||
}
|
||||
var config = {
|
||||
startOnLoad:true,
|
||||
flowchart:{
|
||||
useMaxWidth:true,
|
||||
htmlLabels:true,
|
||||
curve:'cardinal',
|
||||
},
|
||||
securityLevel:'loose',
|
||||
startOnLoad:true,
|
||||
flowchart:{
|
||||
useMaxWidth:true,
|
||||
htmlLabels:true,
|
||||
curve:'cardinal',
|
||||
},
|
||||
securityLevel:'loose',
|
||||
};
|
||||
|
||||
mermaid.initialize(config);
|
||||
@@ -402,6 +581,16 @@ Beginners tip, a full example using interactive links in a html context:
|
||||
</body>
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
Comments can be entered within a flow diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any flow syntax
|
||||
|
||||
```
|
||||
graph LR
|
||||
%% this is a comment A -- text --> B{node}
|
||||
A -- text --> B -- text2 --> C
|
||||
```
|
||||
|
||||
## Styling and classes
|
||||
|
||||
### Styling links
|
||||
@@ -412,7 +601,7 @@ Instead of ids, the order number of when the link was defined in the graph is us
|
||||
defined in the linkStyle statement will belong to the fourth link in the graph:
|
||||
|
||||
```
|
||||
linkStyle 3 stroke:#ff3,stroke-width:4px;
|
||||
linkStyle 3 stroke:#ff3,stroke-width:4px,color:red;
|
||||
```
|
||||
|
||||
|
||||
@@ -424,13 +613,13 @@ It is possible to apply specific styles such as a thicker border or a different
|
||||
graph LR
|
||||
id1(Start)-->id2(Stop)
|
||||
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#ccf,stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5, 5
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
id1(Start)-->id2(Stop)
|
||||
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#ccf,stroke:#f66,stroke-width:2px,stroke-dasharray: 5, 5
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5, 5
|
||||
```
|
||||
|
||||
|
||||
@@ -458,6 +647,19 @@ It is also possible to attach a class to a list of nodes in one statement:
|
||||
class nodeId1,nodeId2 className;
|
||||
```
|
||||
|
||||
A shorter form of adding a class is to attach the classname to the node using the `:::`operator as per below:
|
||||
|
||||
```
|
||||
graph LR
|
||||
A:::someclass --> B
|
||||
classDef someclass fill:#f96;
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
A:::someclass --> B
|
||||
classDef someclass fill:#f96;
|
||||
```
|
||||
|
||||
|
||||
### Css classes
|
||||
|
||||
|
||||
125
docs/gantt.md
@@ -1,8 +1,19 @@
|
||||
# Gantt diagrams
|
||||
|
||||
> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule. Gantt charts illustrate the start and finish dates of the terminal elements and summary elements of a project.
|
||||
|
||||
Mermaid can render Gantt diagrams.
|
||||
> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule and the amount of time it would take for any one project to finish. Gantt charts illustrate number of days between the start and finish dates of the terminal elements and summary elements of a project.
|
||||
|
||||
## A note to users
|
||||
Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed.
|
||||
|
||||
It is important to remember that when a date, day or collection of dates specific to a task are "excluded", the Gannt Chart will accomodate those changes by extending an equal number of day, towards the right, not by creating a gap inside the task.
|
||||
As shown here 
|
||||
|
||||
However, if the excluded date/s is between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded date/s.
|
||||
As shown here 
|
||||
|
||||
A Gantt chart is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days, with a few tweaks.
|
||||
|
||||
Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pasted into docs.
|
||||
|
||||
```
|
||||
gantt
|
||||
@@ -30,9 +41,12 @@ gantt
|
||||
|
||||
```
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
title Adding GANTT diagram functionality to mermaid
|
||||
|
||||
|
||||
dateFormat :YYYY-MM-DD
|
||||
title :Adding GANTT diagram functionality to mermaid
|
||||
excludes :excludes the named dates/days from being included in a charted task..
|
||||
(Accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
|
||||
section A section
|
||||
Completed task :done, des1, 2014-01-06,2014-01-08
|
||||
Active task :active, des2, 2014-01-09, 3d
|
||||
@@ -87,6 +101,20 @@ gantt
|
||||
Add another diagram to demo page :48h
|
||||
```
|
||||
|
||||
It is possible to set multiple depenendenies separated by space:
|
||||
```
|
||||
gantt
|
||||
apple :a, 2017-07-20, 1w
|
||||
banana :crit, b, 2017-07-23, 1d
|
||||
cherry :active, c, after b a, 1d
|
||||
```
|
||||
```
|
||||
gantt
|
||||
apple :a, 2017-07-20, 1w
|
||||
banana :crit, b, 2017-07-23, 1d
|
||||
cherry :active, c, after b a, 1d
|
||||
```
|
||||
|
||||
### Title
|
||||
|
||||
Tbd
|
||||
@@ -99,23 +127,21 @@ Tbd
|
||||
|
||||
## Setting dates
|
||||
|
||||
Tbd
|
||||
`dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`.
|
||||
|
||||
|
||||
### Date format
|
||||
### Input date format
|
||||
|
||||
The default date format is YYYY-MM-DD. You can define your ``dateFormat``. For example:
|
||||
The default input date format is `YYYY-MM-DD`. You can define your custom ``dateFormat``.
|
||||
|
||||
```
|
||||
dateFormat YYYY MM DD
|
||||
dateFormat YYYY-MM-DD
|
||||
```
|
||||
|
||||
The following formating options are supported:
|
||||
|
||||
### Diagram definition
|
||||
|
||||
```
|
||||
Input Example Description:
|
||||
|
||||
```
|
||||
YYYY 2014 4 digit year
|
||||
YY 14 2 digit year
|
||||
Q 1..4 Quarter of year. Sets month to first month in quarter.
|
||||
@@ -126,8 +152,6 @@ Do 1st..31st Day of month with ordinal
|
||||
DDD DDDD 1..365 Day of year
|
||||
X 1410715640.579 Unix timestamp
|
||||
x 1410715640579 Unix ms timestamp
|
||||
|
||||
Input Example Description
|
||||
H HH 0..23 24 hour time
|
||||
h hh 1..12 12 hour time used with a A.
|
||||
a A am pm Post or ante meridiem
|
||||
@@ -141,8 +165,15 @@ Z ZZ +12:00 Offset from UTC as +-HH:mm, +-HHmm, or Z
|
||||
|
||||
More info in: http://momentjs.com/docs/#/parsing/string-format/
|
||||
|
||||
### Output date format on the axis
|
||||
|
||||
### Scale
|
||||
The default output date format is YYYY-MM-DD. You can define your custom ``axisFormat``, like `2020-Q1` for the first quarter of the year 2020.
|
||||
|
||||
```
|
||||
axisFormat %Y-%m-%d
|
||||
```
|
||||
|
||||
The following formating strings are supported:
|
||||
|
||||
```
|
||||
%a - abbreviated weekday name.
|
||||
@@ -173,6 +204,23 @@ More info in: http://momentjs.com/docs/#/parsing/string-format/
|
||||
|
||||
More info in: https://github.com/mbostock/d3/wiki/Time-Formatting
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
|
||||
```
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
%% this is a comment
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :after a1 , 20d
|
||||
section Another
|
||||
Task in sec :2014-01-12 , 12d
|
||||
another task : 24d
|
||||
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
@@ -271,3 +319,48 @@ Param | Descriotion | Default value
|
||||
--- | --- | ---
|
||||
mirrorActor|Turns on/off the rendering of actors below the diagram as well as above it|false
|
||||
bottomMarginAdj|Adjusts how far down the graph ended. Wide borders styles with css could generate unwantewd clipping which is why this config param exists.|1
|
||||
|
||||
## Interaction
|
||||
|
||||
It is possible to bind a click event to a task, the click can lead to either a javascript callback or to a link which will be opened in the current browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||
|
||||
```
|
||||
click taskId call callback(arguments)
|
||||
click taskId href URL
|
||||
```
|
||||
|
||||
* taskId is the id of the task
|
||||
* callback is the name of a javascript function defined on the page displaying the graph, the function will be called with the taskId as the parameter if no other arguments are specified..
|
||||
|
||||
Beginners tip, a full example using interactive links in an html context:
|
||||
```
|
||||
<body>
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07, 3d
|
||||
Print arguments :cl2, after cl1, 3d
|
||||
Print task :cl3, after cl2, 3d
|
||||
|
||||
click cl1 href "https://mermaidjs.github.io/"
|
||||
click cl2 call printArguments("test1", "test2", test3)
|
||||
click cl3 call printTask()
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var printArguments = function(arg1, arg2, arg3) {
|
||||
alert('printArguments called with arguments: ' + arg1 + ', ' + arg2 + ', ' + arg3);
|
||||
}
|
||||
var printTask = function(taskId) {
|
||||
alert('taskId: ' + taskId);
|
||||
}
|
||||
var config = {
|
||||
startOnLoad:true,
|
||||
securityLevel:'loose',
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
BIN
docs/img/Gantt-excluded-days-within.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
docs/img/Gantt-long-weekend-look.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
docs/img/er.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/img/liveEditorOptions.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
docs/img/simple-er.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
@@ -7,7 +7,8 @@
|
||||
<meta name="description" content="Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
||||
<script src="//cdn.jsdelivr.net/npm/mermaid@8.4.1/dist/mermaid.min.js"></script>
|
||||
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@8.5.0/dist/mermaid.min.js"></script> -->
|
||||
<script src="http://localhost:9000/mermaid.js"></script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
|
||||
140
docs/integrations.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Integrations
|
||||
|
||||
The following is a list of different integrations and plugins where mermaid is being used
|
||||
|
||||
## Productivity
|
||||
|
||||
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts) (**Native support**)
|
||||
- [GitHub](https://github.com)
|
||||
- [Github action: Compile mermaid to image](https://github.com/neenjaw/compile-mermaid-markdown-action)
|
||||
- [svg-generator](https://github.com/SimonKenyonShepard/mermaidjs-github-svg-generator)
|
||||
- [GitBook](http://gitbook.com)
|
||||
- [Mermaid Plugin](https://github.com/JozoVilcek/gitbook-plugin-mermaid)
|
||||
- [Markdown with Mermaid CLI](https://github.com/miao1007/gitbook-plugin-mermaid-cli)
|
||||
- [Mermaid plugin for GitBook](https://github.com/wwformat/gitbook-plugin-mermaid-pdf)
|
||||
- [Atlassian Products](https://www.atlassian.com)
|
||||
- [Mermaid Plugin for Confluence](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview)
|
||||
- [CloudScript.io Addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon?hosting=cloud&tab=overview)
|
||||
- [Auto convert diagrams in Jira](https://github.com/coddingtonbear/jirafs-mermaid)
|
||||
- [Redmine](https://redmine.org)
|
||||
- [Mermaid Macro](https://www.redmine.org/plugins/redmine_mermaid_macro)
|
||||
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
|
||||
- [markdown-for-mermaid-plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
|
||||
|
||||
## CRM/ERP/Similar
|
||||
|
||||
- [coreBOS](http://blog.corebos.org/blog/december2019)
|
||||
|
||||
## Blogs
|
||||
|
||||
- [Wordpress](https://wordpress.org)
|
||||
- [WordPress Markdown Editor](https://wordpress.org/plugins/wp-githuber-md)
|
||||
- [WP-ReliableMD](https://wordpress.org/plugins/wp-reliablemd/)
|
||||
- [Hexo](https://hexo.io)
|
||||
- [hexo-filter-mermid-diagrams](https://github.com/webappdevelp/hexo-filter-mermaid-diagrams)
|
||||
- [hexo-tag-mermaid](https://github.com/JameChou/hexo-tag-mermaid)
|
||||
- [hexo-mermaid-diagrams](https://github.com/mslxl/hexo-mermaid-diagrams)
|
||||
|
||||
## CMS
|
||||
|
||||
- [VuePress](https://vuepress.vuejs.org/)
|
||||
- [Plugin for Mermaid.js](https://github.com/eFrane/vuepress-plugin-mermaidjs)
|
||||
- [vuepress-plugin-mermaidjs-cli](https://github.com/gwleclerc/vuepress-plugin-mermaidjs-cli)
|
||||
- [Grav CMS](https://getgrav.org/)
|
||||
- [Mermaid Diagrams](https://github.com/DanielFlaum/grav-plugin-mermaid-diagrams)
|
||||
- [Gitlab Markdown Adapter](https://github.com/Goutte/grav-plugin-gitlab-markdown-adapter)
|
||||
|
||||
## Communication
|
||||
|
||||
- [Discourse](https://discourse.org)
|
||||
- [Mermaid Plugin](https://github.com/pnewell/discourse-mermaid), [And](https://github.com/unfoldingWord-dev/discourse-mermaid)
|
||||
- [Mattermost](https://mattermost.com/)
|
||||
- [Mermaid Plugin](https://github.com/SpikeTings/Mermaid)
|
||||
- [phpBB](https://phpbb.com)
|
||||
- [phpbb-ext-mermaid](https://github.com/AlfredoRamos/phpbb-ext-mermaid)
|
||||
- [NodeBB](https://nodebb.org)
|
||||
- [Mermaid Plugin](https://www.npmjs.com/package/nodebb-plugin-mermaid)
|
||||
|
||||
## Wikis
|
||||
|
||||
- [Media Wiki](https://www.mediawiki.org/wiki/Extension:Mermaid)
|
||||
- [Semantic Media Wiki](https://semantic-mediawiki.org)
|
||||
- [Mermaid Plugin](https://github.com/SemanticMediaWiki/Mermaid)
|
||||
- [FosWiki](https://foswiki.org)
|
||||
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
|
||||
- [DokuWiki](https://dokuwiki.org)
|
||||
- [Flowcharts](https://www.dokuwiki.org/plugin:flowcharts?s[]=mermaid)
|
||||
- [TiddlyWiki](https://tiddlywiki.com/)
|
||||
- [mermaid-tw5-plugin](https://github.com/michaeljmcd/mermaid-tw5-plugin)
|
||||
|
||||
## Editor Plugins
|
||||
|
||||
- [Vs Code](https://code.visualstudio.com/)
|
||||
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
|
||||
- [Mermaid Preview](https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview)
|
||||
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
|
||||
- [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
|
||||
- [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)
|
||||
- [Markdown PDF](https://marketplace.visualstudio.com/items?itemName=yzane.markdown-pdf)
|
||||
- [Preview](https://marketplace.visualstudio.com/items?itemName=searKing.preview-vscode)
|
||||
- [Preview Sequence Diagrams](https://marketplace.visualstudio.com/items?itemName=arichika.previewseqdiag-vscode)
|
||||
- [Markdown-It](https://github.com/markdown-it/markdown-it)
|
||||
- [Textual UML Parser](https://github.com/manastalukdar/markdown-it-textual-uml)
|
||||
- [Mermaid Plugin](https://github.com/tylingsoft/markdown-it-mermaid)
|
||||
- [md-it-mermaid](https://github.com/iamcco/md-it-mermaid)
|
||||
- [markdown-it-mermaid-fence-new](https://github.com/Revomatico/markdown-it-mermaid-fence-new)
|
||||
- [markdown-it-mermaid-less](https://github.com/searKing/markdown-it-mermaid-less)
|
||||
- [Atom](https://atom.io)
|
||||
- [Markdown Preview Enhanced](https://atom.io/packages/markdown-preview-enhanced)
|
||||
- [Atom Mermaid](https://atom.io/packages/atom-mermaid)
|
||||
- [Language Mermaid Syntax Highlighter](https://atom.io/packages/language-mermaid)
|
||||
- [Sublime Text 3](https://sublimetext.com)
|
||||
- [Mermaid Package](https://packagecontrol.io/packages/Mermaid)
|
||||
- [Astah](http://astah.net)
|
||||
- [Export to Mermaid](https://github.com/Avens666/Astah_Jude_UML_export_to_Markdown-mermaid-Plantuml-)
|
||||
- [Light Table](http://lighttable.com/)
|
||||
- [Mermaid Plugin](https://github.com/cldwalker/Mermaid)
|
||||
- [Draw.io](http://draw.io) - [Plugin](https://github.com/nopeslide/drawio_mermaid_plugin)
|
||||
- [Inkdrop](http://inkdrop.app) - [Plugin](https://github.com/inkdropapp/inkdrop-mermaid)
|
||||
- [Vim](https://vim.org)
|
||||
- [Vim Diagram Syntax](https://github.com/zhaozg/vim-diagram)
|
||||
- [Brackets](http://brackets.io/)
|
||||
- [Mermaid Preview](https://s3.amazonaws.com/extend.brackets/alanhohn.mermaid-preview/alanhohn.mermaid-preview-1.0.2.zip)
|
||||
- [Iodide](https://github.com/iodide-project/iodide)
|
||||
- [iodide-mermaid-plugin](https://github.com/iodide-project/iodide-mermaid-plugin)
|
||||
|
||||
## Document Generation
|
||||
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/)
|
||||
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
|
||||
- [remark.js](https://remark.js.org/)
|
||||
- [remark-mermaid](https://github.com/temando/remark-mermaid)
|
||||
- [jSDoc](https://jsdoc.app/)
|
||||
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
|
||||
- [MkDocs](https://mkdocs.org)
|
||||
- [MarkdownMermaid](https://github.com/sebastienwarin/mkdocs-mermaid-plugin)
|
||||
- [Type Doc](https://typedoc.org/)
|
||||
- [typedoc-plugin-mermaid](https://www.npmjs.com/package/typedoc-plugin-mermaid)
|
||||
|
||||
## Chrome Extensions
|
||||
|
||||
- [Mermaid Diagrams](https://chrome.google.com/webstore/detail/mermaid-diagrams/phfcghedmopjadpojhmmaffjmfiakfil)
|
||||
- [GitHub + Mermaid](https://chrome.google.com/webstore/detail/github-%2B-mermaid/goiiopgdnkogdbjmncgedmgpoajilohe)
|
||||
- [Mermaid Markdown](https://chrome.google.com/webstore/detail/mermaid-markdown/mboeoikjijmjcjgpccghbcoegikliijg)
|
||||
- [Monkeys](https://chrome.google.com/webstore/detail/monkeys-mermaid-for-githu/cplfdpoajbclbgphaphphcldamfkjlgi)
|
||||
- [Asciidoctor Live Preview](https://chrome.google.com/webstore/detail/asciidoctorjs-live-previe/iaalpfgpbocpdfblpnhhgllgbdbchmia) __works in the new IE as well__
|
||||
- [Chrome Diagrammer](https://chrome.google.com/webstore/detail/chrome-diagrammer/bkpbgjmkomfoakfklcjeoegkklgjnnpk)
|
||||
- [Diagram Tab](https://github.com/khafast/diagramtab)
|
||||
|
||||
## Other
|
||||
|
||||
- [Jekyll](https://jekyllrb.com/)
|
||||
- [jekyll-mermaid](https://rubygems.org/gems/jekyll-mermaid)
|
||||
- [jekyll-mermaid-diagrams](https://github.com/fuzhibo/jekyll-mermaid-diagrams)
|
||||
- [Reveal.js](https://github.com/hakimel/reveal.js)
|
||||
- [reveal.js-mermaid-plugin](https://github.com/ludwick/reveal.js-mermaid-plugin)
|
||||
- [Bisheng](https://www.npmjs.com/package/bisheng)
|
||||
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
|
||||
- [Reveal CK](https://github.com/jedcn/reveal-ck)
|
||||
- [reveal-ck-mermaid-plugin](https://github.com/tmtm/reveal-ck-mermaid-plugin)
|
||||
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
|
||||
BIN
docs/liveEditorOptions.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
@@ -19,7 +19,7 @@ These are the default options which can be overridden with the initialization ca
|
||||
<pre>
|
||||
mermaid.initialize({
|
||||
flowchart:{
|
||||
htmlLabels: false
|
||||
htmlLabels: false
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
@@ -27,7 +27,7 @@ mermaid.initialize({
|
||||
**Example 2:**
|
||||
|
||||
<pre>
|
||||
<script>
|
||||
<script>
|
||||
var config = {
|
||||
startOnLoad:true,
|
||||
flowchart:{
|
||||
@@ -39,10 +39,10 @@ mermaid.initialize({
|
||||
securityLevel:'loose',
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
A summary of all options and their defaults is found [here](https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#mermaidapi-configuration-defaults). A description of each option follows below.
|
||||
|
||||
A summary of all options and their defaults is found [here][2]. A description of each option follows below.
|
||||
|
||||
## theme
|
||||
|
||||
@@ -105,6 +105,18 @@ Flag for setting whether or not a html tag should be used for rendering labels
|
||||
on the edges.
|
||||
**Default value true**.
|
||||
|
||||
### nodeSpacing
|
||||
|
||||
Defines the spacing between nodes on the same level (meaning horizontal spacing for
|
||||
TB or BT graphs, and the vertical spacing for LR as well as RL graphs).
|
||||
**Default value 50**.
|
||||
|
||||
### rankSpacing
|
||||
|
||||
Defines the spacing between nodes on different levels (meaning vertical spacing for
|
||||
TB or BT graphs, and the horizontal spacing for LR as well as RL graphs).
|
||||
**Default value 50**.
|
||||
|
||||
### curve
|
||||
|
||||
How mermaid renders curves for flowcharts. Possible values are
|
||||
@@ -162,6 +174,14 @@ margin around notes.
|
||||
Space between messages.
|
||||
**Default value 35**.
|
||||
|
||||
### messageAlign
|
||||
|
||||
Multiline message alignment. Possible values are:
|
||||
|
||||
- left
|
||||
- center **default**
|
||||
- right
|
||||
|
||||
### mirrorActors
|
||||
|
||||
mirror actors under diagram.
|
||||
@@ -189,6 +209,41 @@ This will display arrows that start and begin at the same node as right angles,
|
||||
This will show the node numbers
|
||||
**Default value false**.
|
||||
|
||||
### actorFontSize
|
||||
|
||||
This sets the font size of the actor's description
|
||||
**Default value 14**.
|
||||
|
||||
### actorFontFamily
|
||||
|
||||
This sets the font family of the actor's description
|
||||
**Default value "Open-Sans", "sans-serif"**.
|
||||
|
||||
### noteFontSize
|
||||
|
||||
This sets the font size of actor-attached notes.
|
||||
**Default value 14**.
|
||||
|
||||
### noteFontFamily
|
||||
|
||||
This sets the font family of actor-attached notes.
|
||||
**Default value "trebuchet ms", verdana, arial**.
|
||||
|
||||
### noteAlign
|
||||
|
||||
This sets the text alignment of actor-attached notes.
|
||||
**Default value center**.
|
||||
|
||||
### messageFontSize
|
||||
|
||||
This sets the font size of actor messages.
|
||||
**Default value 16**.
|
||||
|
||||
### messageFontFamily
|
||||
|
||||
This sets the font family of actor messages.
|
||||
**Default value "trebuchet ms", verdana, arial**.
|
||||
|
||||
## gantt
|
||||
|
||||
The object containing configurations specific for gantt diagrams\*
|
||||
@@ -243,6 +298,117 @@ The number of alternating section styles.
|
||||
Datetime format of the axis. This might need adjustment to match your locale and preferences
|
||||
**Default value '%Y-%m-%d'**.
|
||||
|
||||
## journey
|
||||
|
||||
The object containing configurations specific for sequence diagrams
|
||||
|
||||
### diagramMarginX
|
||||
|
||||
margin to the right and left of the sequence diagram.
|
||||
**Default value 50**.
|
||||
|
||||
### diagramMarginY
|
||||
|
||||
margin to the over and under the sequence diagram.
|
||||
**Default value 10**.
|
||||
|
||||
### actorMargin
|
||||
|
||||
Margin between actors.
|
||||
**Default value 50**.
|
||||
|
||||
### width
|
||||
|
||||
Width of actor boxes
|
||||
**Default value 150**.
|
||||
|
||||
### height
|
||||
|
||||
Height of actor boxes
|
||||
**Default value 65**.
|
||||
|
||||
### boxMargin
|
||||
|
||||
Margin around loop boxes
|
||||
**Default value 10**.
|
||||
|
||||
### boxTextMargin
|
||||
|
||||
margin around the text in loop/alt/opt boxes
|
||||
**Default value 5**.
|
||||
|
||||
### noteMargin
|
||||
|
||||
margin around notes.
|
||||
**Default value 10**.
|
||||
|
||||
### messageMargin
|
||||
|
||||
Space between messages.
|
||||
**Default value 35**.
|
||||
|
||||
### messageAlign
|
||||
|
||||
Multiline message alignment. Possible values are:
|
||||
|
||||
- left
|
||||
- center **default**
|
||||
- right
|
||||
|
||||
### bottomMarginAdj
|
||||
|
||||
Depending on css styling this might need adjustment.
|
||||
Prolongs the edge of the diagram downwards.
|
||||
**Default value 1**.
|
||||
|
||||
### useMaxWidth
|
||||
|
||||
when this flag is set the height and width is set to 100% and is then scaling with the
|
||||
available space if not the absolute space required is used.
|
||||
**Default value true**.
|
||||
|
||||
### rightAngles
|
||||
|
||||
This will display arrows that start and begin at the same node as right angles, rather than a curve
|
||||
**Default value false**.
|
||||
|
||||
## er
|
||||
|
||||
The object containing configurations specific for entity relationship diagrams
|
||||
|
||||
### diagramPadding
|
||||
|
||||
The amount of padding around the diagram as a whole so that embedded diagrams have margins, expressed in pixels
|
||||
|
||||
### layoutDirection
|
||||
|
||||
Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL',
|
||||
where T = top, B = bottom, L = left, and R = right.
|
||||
|
||||
### minEntityWidth
|
||||
|
||||
The mimimum width of an entity box, expressed in pixels
|
||||
|
||||
### minEntityHeight
|
||||
|
||||
The minimum height of an entity box, expressed in pixels
|
||||
|
||||
### entityPadding
|
||||
|
||||
The minimum internal padding between the text in an entity box and the enclosing box borders, expressed in pixels
|
||||
|
||||
### stroke
|
||||
|
||||
Stroke color of box edges and lines
|
||||
|
||||
### fill
|
||||
|
||||
Fill color of entity boxes
|
||||
|
||||
### fontSize
|
||||
|
||||
Font size (expressed as an integer representing a number of pixels)
|
||||
|
||||
## render
|
||||
|
||||
Function that renders an svg with a graph from a chart definition. Usage example below.
|
||||
@@ -263,11 +429,12 @@ mermaidAPI.initialize({
|
||||
### Parameters
|
||||
|
||||
- `id` the id of the element to be rendered
|
||||
- `txt` the graph definition
|
||||
- `_txt`
|
||||
- `cb` callback which is called after rendering is finished with the svg code as inparam.
|
||||
- `container` selector to element in which a div with the graph temporarily will be inserted. In one is
|
||||
provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
|
||||
completed.
|
||||
- `txt` the graph definition
|
||||
|
||||
##
|
||||
|
||||
@@ -297,6 +464,7 @@ mermaidAPI.initialize({
|
||||
boxTextMargin:5,
|
||||
noteMargin:10,
|
||||
messageMargin:35,
|
||||
messageAlign:'center',
|
||||
mirrorActors:true,
|
||||
bottomMarginAdj:1,
|
||||
useMaxWidth:true,
|
||||
@@ -321,3 +489,5 @@ mermaidAPI.initialize({
|
||||
</pre>
|
||||
|
||||
[1]: https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#render
|
||||
|
||||
[2]: https://github.com/knsv/mermaid/blob/master/docs/mermaidAPI.md#mermaidapi-configuration-defaults
|
||||
|
||||
@@ -1,42 +1,46 @@
|
||||
# A more basic getting started
|
||||
# A basic mermaid User-Guide for Beginners
|
||||
|
||||
Writing mermaid code is simple.
|
||||
Creating diagrams and charts, using mermaid code is simple.
|
||||
|
||||
But how is the code turned into a diagram in a web page? To do this we need a mermaid renderer.
|
||||
But how is the code turned into a diagram in a web page? This is done with the use of a mermaid renderer.
|
||||
|
||||
Thankfully the mermaid renderer is very accessible, in essence it is a javascript.
|
||||
Thankfully the mermaid renderer is very accessible, in essence it is a piece of javascript that can be called.
|
||||
|
||||
The requirement is on the part of the web browser. Modern web browsers, such as Firefox, Chrome and Safari, can render mermaid. But Internet Explorer cannot. The web browser also needs access to the online mermaid renderer which it downloads from https://cdn.jsdelivr.net/npm/mermaid
|
||||
Most widely used web browsers, such as Firefox, Chrome and Safari, can render mermaid, Internet Explorer however cannot. The web browser also needs access to the online mermaid renderer which it downloads from https://cdn.jsdelivr.net/npm/mermaid
|
||||
|
||||
For an easy introduction, here follows three practical examples using:
|
||||
1. an online mermaid editor
|
||||
2. a mermaid plugin
|
||||
3. a generic web server of your choosing
|
||||
# For beginners, there are three relatively easy ways you can use mermaid:
|
||||
1. Using the mermaid [live editor](https://mermaid-js.github.io/mermaid-live-editor/)
|
||||
2. Using a mermaid plugin, such as that for Confluence or [Atom](https://atom.io/packages/atom-mermaid).
|
||||
3. Calling mermaid renderer with HTML, deployed in a friendly browser.
|
||||
|
||||
Following either of these examples, you can get started with converting your own mermaid code into web diagrams.
|
||||
# Following either of these examples, you can get started with creating your own diagrams using mermaid code.
|
||||
|
||||
## the mermaid live editor
|
||||
## 1. The mermaid live editor:
|
||||
|
||||
The quickest way to get started with mermaid is to visit [The mermaid live editor](https://mermaidjs.github.io/mermaid-live-editor).
|
||||
A great way to get started with mermaid is to visit [The mermaid live editor](https://mermaidjs.github.io/mermaid-live-editor).
|
||||
|
||||
In the `Code` section one can write or edit raw mermaid code, and instantly `Preview` the rendered result.
|
||||
In the `Code` section one can write or edit raw mermaid code, and instantly `Preview` the rendered result on the panel beside it.
|
||||
|
||||
This is a great way to get started.
|
||||

|
||||
|
||||
It is also the easiest way to develop diagrams, the code of which can be pasted straight into documentation.
|
||||
|
||||

|
||||
You can also copy the code from the code section and paste it into either a mermaid plugin or in inside an html file, which will be taught in numbers 2 and 3.
|
||||
|
||||
It is also an easier way to develop diagrams. You can also click "Copy Markdown" to copy the markdown code for the diagram, that can then be pasted directly into your documentation.
|
||||
|
||||

|
||||
|
||||
The `Mermaid configuration` is for controlling mermaid behaviour. An easy introduction to mermaid configuration is found in the [Advanced usage](n00b-advanced.md) section. A complete configuration reference cataloguing default values is found on the [mermaidAPI](mermaidAPI.md) page.
|
||||
|
||||
|
||||
## mermaid using plugins
|
||||
## 2. Using mermaid plugins:
|
||||
|
||||
Thanks to the growing popularity of mermaid, many plugins already exist which incorporate a mermaid renderer.
|
||||
Thanks to the growing popularity of mermaid, many plugins already exist which incorporate a mermaid renderer. An extensive list can be found [here](integrations.md).
|
||||
|
||||
One example is the [Atlassian Confluence mermaid plugin](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview)
|
||||
One example in the list is the [Atlassian Confluence mermaid plugin](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview)
|
||||
|
||||
When the mermaid plugin is installed on a Confluence server, one can insert a mermaid object into any Confluence page.
|
||||
# Here is a step by step process for using the mermaid-Confluence plugin:
|
||||
|
||||
---
|
||||
|
||||
@@ -64,24 +68,32 @@ When the mermaid plugin is installed on a Confluence server, one can insert a me
|
||||
|
||||
---
|
||||
|
||||
## mermaid using any web server (or just a browser)
|
||||
## 3. mermaid using any web server (or just a browser):
|
||||
|
||||
This example can be used with any common web server. Apache, IIS, nginx, node express [...], you pick your favourite.
|
||||
This method can be used with any common web server. Apache, IIS, nginx, node express [...], you pick your favourite.
|
||||
|
||||
We do not need to install anything on the server, apart from a program (like Notepad++) that can generate an html file, which is then deployed by a web browser (such as Firefox, Chrome, Safari, but not Internet Explorer).
|
||||
|
||||
So if you want to really simplify things when testing this out, don't use a web server at all but just create the file locally and drag it into your browser window. It is the browser which does all the work of rendering mermaid!
|
||||
|
||||
# Here are instructions for creating an html file with mermaid code:
|
||||
# Note that all this is written in the html `<body>` section of the web page.
|
||||
|
||||
When writing the html file, we give the web browser three instructions inside the html code:
|
||||
|
||||
a. A reference for fetching the online mermaid renderer, which is written in Javascript.
|
||||
|
||||
b. The mermaid code for the diagram we want to create.
|
||||
|
||||
c. The `mermaid.initialize()` command to start the rendering process.
|
||||
|
||||
We do not need to install anything on the server, apart from a normal file of html to be reached by a web browser (such as Firefox, Chrome, Safari, but not Internet Explorer). So if you want to really simplify things when testing this out, don't use a web server at all but just create the file locally and drag it into your browser window. It is the browser which does all the work of rendering mermaid!
|
||||
|
||||
Through the html file, we give the web browser three instructions inside the html code it retrieves:
|
||||
1. a reference for fetching the online mermaid renderer, the renderer is just a javascript.
|
||||
2. the mermaid code we want to diagram.
|
||||
3. the `mermaid.initialize()` command to start the rendering process
|
||||
|
||||
All this is done in the html `<body>` section of the web page.
|
||||
|
||||
This is what needs to go into the html file:
|
||||
|
||||
|
||||
|
||||
1. The reference to the mermaid renderer is done in a `<script src>` tag like so:
|
||||
# a. The reference to the mermaid renderer has to be contained in a `<script src>` tag like so:
|
||||
|
||||
```
|
||||
<body>
|
||||
@@ -89,7 +101,7 @@ This is what needs to go into the html file:
|
||||
</body>
|
||||
```
|
||||
|
||||
2. The embedded mermaid code is similarly placed in a `<div>` tag:
|
||||
# b. The embedded mermaid code is similarly placed inside a `<div>` tag:
|
||||
|
||||
```
|
||||
<body>
|
||||
@@ -102,8 +114,9 @@ This is what needs to go into the html file:
|
||||
</div>
|
||||
</body>
|
||||
```
|
||||
(take note that every mermaid chart/graph/diagram, has to have separate `<div>` tags.)
|
||||
|
||||
3. When initializing mermaid using `mermaid.initialize()`, mermaid takes all the `<div class="mermaid">` tags it can find in the html body and starts to render them one by one. This is done like so:
|
||||
# c. When initializing mermaid using `mermaid.initialize()`, mermaid takes all the `<div class="mermaid">` tags it can find in the html body and starts to render them one by one. This is done like so:
|
||||
|
||||
```
|
||||
<body>
|
||||
@@ -111,8 +124,8 @@ This is what needs to go into the html file:
|
||||
</body>
|
||||
```
|
||||
|
||||
*Finally*
|
||||
4. Putting the three steps together is as simple as:
|
||||
# *Finally*
|
||||
# If the three steps mentioned are followed you will end up with something like this:
|
||||
```
|
||||
<html>
|
||||
<body>
|
||||
@@ -137,7 +150,7 @@ This is what needs to go into the html file:
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
Save this to a html file and fetch it with a browser from the web server (or just drag it into your web browser window) and voila!
|
||||
# Save this to a html file and fetch it with a browser from the web server (or just drag it into your web browser window) and voila!
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Overview for n00bs
|
||||
|
||||
As a sysadmin I frequently have to document things, including drawing stuff.
|
||||
mermaid is a tool that aims to make diagrams and flowcharts for documentation, easier.
|
||||
|
||||
Using mermaid, I can type this as a comment in a script:
|
||||
with mermaid, diagrams can be created through comments like this in a script:
|
||||
|
||||
```
|
||||
graph TD
|
||||
@@ -11,17 +11,19 @@ B --> C[Server01]
|
||||
B --> D[Server02]
|
||||
```
|
||||
|
||||
And end up with this in the documentation:
|
||||
And they are rendered into this and made part of the documentation:
|
||||
|
||||

|
||||
|
||||
Most of the stuff I need to visualize can be scripted in a similar way, with a varitety of different symbols and chart types available. Since the diagram source is text based, it can be part of production scripts (and other pieces of code). So less time needs be spent on documenting as a separate task.
|
||||
Most of the similar visuals that you might need to create can be scripted in a similar way, with a varitety of different symbols and chart types available.
|
||||
Since the diagram source is text based, it can be part of production scripts (and other pieces of code). So less time needs be spent on documenting as a separate task.
|
||||
|
||||
Comparing with Visio and similar applications, mermaid is a really fast way to create good visualizations. This is especially apparent when editing a complex visualisation, this could take me hours in a desktop application but takes minutes (or even less if generation has been scripted) with mermaid.
|
||||
|
||||
With mermaid I can spend a fraction of the time I normally would spend, and instead automate the diagram generation and end up saving even more time. I love it!
|
||||
Comparing with Visio and similar applications, mermaid is a really fast way to create good visualizations. This is especially apparent when editing a complex visualisations, a process that usually takes hours in a desktop application, but only takes minutes (or even less if generation has been scripted) with mermaid.
|
||||
|
||||
mermaid can potentially cut down the amount of time and effort spent on the process of creating diagrams, to a fraction of what you usually put in.
|
||||
|
||||
However, a lot of the mermaid documentation is geared to professional frontend developers, presuming a skill set which I simply do not have.
|
||||
|
||||
I needed a really basic instruction. And here it is.
|
||||
If you need some basic instructions and introductions, here are a few good places to start:
|
||||
|
||||
For information on how to use mermaid, click [here](https://mermaid-js.github.io/mermaid/#/n00b-gettingStarted), or you can try out the mermaid [live editor](https://mermaid-js.github.io/mermaid-live-editor/), alternatively, you could also view the [integrations and uses](https://github.com/mermaid-js/mermaid/blob/develop/docs/integrations.md) for mermaid.
|
||||
|
||||
@@ -9,6 +9,7 @@ sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
John-->>Alice: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
@@ -31,6 +32,7 @@ sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
John-->>Alice: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant John
|
||||
@@ -50,6 +52,7 @@ sequenceDiagram
|
||||
A->>J: Hello John, how are you?
|
||||
J->>A: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Alice
|
||||
@@ -68,15 +71,14 @@ Messages can be of two displayed either solid or with a dotted line.
|
||||
|
||||
There are six types of arrows currently supported:
|
||||
|
||||
Type | Description
|
||||
--- | ---
|
||||
-> | Solid line without arrow
|
||||
--> | Dotted line without arrow
|
||||
->> | Solid line with arrowhead
|
||||
-->> | Dotted line with arrowhead
|
||||
-x | Solid line with a cross at the end (async)
|
||||
--x | Dotted line with a cross at the end (async)
|
||||
|
||||
| Type | Description |
|
||||
| ---- | ------------------------------------------- |
|
||||
| -> | Solid line without arrow |
|
||||
| --> | Dotted line without arrow |
|
||||
| ->> | Solid line with arrowhead |
|
||||
| -->> | Dotted line with arrowhead |
|
||||
| -x | Solid line with a cross at the end (async) |
|
||||
| --x | Dotted line with a cross at the end (async) |
|
||||
|
||||
## Activations
|
||||
|
||||
@@ -89,6 +91,7 @@ sequenceDiagram
|
||||
John-->>Alice: Great!
|
||||
deactivate John
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
@@ -104,6 +107,7 @@ sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
John-->>-Alice: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
@@ -119,6 +123,7 @@ sequenceDiagram
|
||||
John-->>-Alice: Hi Alice, I can hear you!
|
||||
John-->>-Alice: I feel great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
@@ -127,7 +132,6 @@ sequenceDiagram
|
||||
John-->>-Alice: I feel great!
|
||||
```
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
It is possible to add notes to a sequence diagram. This is done by the notation
|
||||
@@ -140,6 +144,7 @@ sequenceDiagram
|
||||
participant John
|
||||
Note right of John: Text in note
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant John
|
||||
@@ -153,13 +158,13 @@ sequenceDiagram
|
||||
Alice->John: Hello John, how are you?
|
||||
Note over Alice,John: A typical interaction
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->John: Hello John, how are you?
|
||||
Note over Alice,John: A typical interaction
|
||||
```
|
||||
|
||||
|
||||
## Loops
|
||||
|
||||
It is possible to express loops in a sequence diagram. This is done by the notation
|
||||
@@ -179,6 +184,7 @@ sequenceDiagram
|
||||
John-->Alice: Great!
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->John: Hello John, how are you?
|
||||
@@ -187,7 +193,6 @@ sequenceDiagram
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## Alt
|
||||
|
||||
It is possible to express alternative paths in a sequence diagram. This is done by the notation
|
||||
@@ -222,6 +227,7 @@ sequenceDiagram
|
||||
Bob->>Alice: Thanks for asking
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hello Bob, how are you?
|
||||
@@ -235,15 +241,63 @@ sequenceDiagram
|
||||
end
|
||||
```
|
||||
|
||||
## Parallel
|
||||
|
||||
It is possible to show actions that are happening in parallel.
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```
|
||||
par [Action 1]
|
||||
... statements ...
|
||||
and [Action 2]
|
||||
... statements ...
|
||||
and [Action N]
|
||||
... statements ...
|
||||
end
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
par Alice to Bob
|
||||
Alice->>Bob: Hello guys!
|
||||
and Alice to John
|
||||
Alice->>John: Hello guys!
|
||||
end
|
||||
Bob-->>Alice: Hi Alice!
|
||||
John-->>Alice: Hi Alice!
|
||||
```
|
||||
|
||||
It is also possible to nest parallel blocks.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
par Alice to Bob
|
||||
Alice->>Bob: Go help John
|
||||
and Alice to John
|
||||
Alice->>John: I want this done today
|
||||
par John to Charlie
|
||||
John->>Charlie: Can we do this today?
|
||||
and John to Diana
|
||||
John->>Diana: Can you help us today?
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Background Highlighting
|
||||
|
||||
It is possible to highlight flows by providing colored background rects. This is done by the notation
|
||||
|
||||
The colors are defined using rgb and rgba syntax.
|
||||
|
||||
```
|
||||
rect rgb(0, 255, 0)
|
||||
... content ...
|
||||
end
|
||||
```
|
||||
|
||||
```
|
||||
rect rgba(0, 0, 255, .1)
|
||||
... content ...
|
||||
@@ -271,116 +325,161 @@ sequenceDiagram
|
||||
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a sequence diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
|
||||
```
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
%% this is a comment
|
||||
John-->>Alice: Great!
|
||||
```
|
||||
|
||||
## sequenceNumbers
|
||||
|
||||
It is possible to get a sequence number attached to each arrow in a sequence diagram. This can be configured when adding mermaid to the website as shown below:
|
||||
|
||||
```
|
||||
<script>
|
||||
mermaid.initialize({
|
||||
sequence: { showSequenceNumbers: true },
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
It can also be be turned on via the diagram code as in the diagram:
|
||||
|
||||
```
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts!
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts!
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
Styling of the a sequence diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/sequence.scss
|
||||
Styling of a sequence diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/sequence.scss
|
||||
|
||||
### Classes used
|
||||
|
||||
Class | Description
|
||||
--- | ---
|
||||
actor | Style for the actor box at the top of the diagram.
|
||||
text.actor | Styles for text in the actor box at the top of the diagram.
|
||||
actor-line | The vertical line for an actor.
|
||||
messageLine0 | Styles for the solid message line.
|
||||
messageLine1 | Styles for the dotted message line.
|
||||
messageText | Defines styles for the text on the message arrows.
|
||||
labelBox | Defines styles label to left in a loop.
|
||||
labelText | Styles for the text in label for loops.
|
||||
loopText | Styles for the text in the loop box.
|
||||
loopLine | Defines styles for the lines in the loop box.
|
||||
note | Styles for the note box.
|
||||
noteText | Styles for the text on in the note boxes.
|
||||
|
||||
| Class | Description |
|
||||
| ------------ | ----------------------------------------------------------- |
|
||||
| actor | Style for the actor box at the top of the diagram. |
|
||||
| text.actor | Styles for text in the actor box at the top of the diagram. |
|
||||
| actor-line | The vertical line for an actor. |
|
||||
| messageLine0 | Styles for the solid message line. |
|
||||
| messageLine1 | Styles for the dotted message line. |
|
||||
| messageText | Defines styles for the text on the message arrows. |
|
||||
| labelBox | Defines styles label to left in a loop. |
|
||||
| labelText | Styles for the text in label for loops. |
|
||||
| loopText | Styles for the text in the loop box. |
|
||||
| loopLine | Defines styles for the lines in the loop box. |
|
||||
| note | Styles for the note box. |
|
||||
| noteText | Styles for the text on in the note boxes. |
|
||||
|
||||
### Sample stylesheet
|
||||
|
||||
|
||||
```css
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.actor {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF;
|
||||
stroke: #ccccff;
|
||||
fill: #ececff;
|
||||
}
|
||||
text.actor {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: Helvetica;
|
||||
}
|
||||
|
||||
.actor-line {
|
||||
stroke:grey;
|
||||
stroke: grey;
|
||||
}
|
||||
|
||||
.messageLine0 {
|
||||
stroke-width:1.5;
|
||||
stroke-dasharray: "2 2";
|
||||
marker-end:"url(#arrowhead)";
|
||||
stroke:black;
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
marker-end: 'url(#arrowhead)';
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
.messageLine1 {
|
||||
stroke-width:1.5;
|
||||
stroke-dasharray: "2 2";
|
||||
stroke:black;
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
#arrowhead {
|
||||
fill:black;
|
||||
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.messageText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-size:14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.labelBox {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF;
|
||||
stroke: #ccccff;
|
||||
fill: #ececff;
|
||||
}
|
||||
|
||||
.labelText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
}
|
||||
|
||||
.loopText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
}
|
||||
|
||||
.loopLine {
|
||||
stroke-width:2;
|
||||
stroke-dasharray: "2 2";
|
||||
marker-end:"url(#arrowhead)";
|
||||
stroke: #CCCCFF;
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: '2 2';
|
||||
marker-end: 'url(#arrowhead)';
|
||||
stroke: #ccccff;
|
||||
}
|
||||
|
||||
.note {
|
||||
stroke: #decc93;
|
||||
stroke: #CCCCFF;
|
||||
stroke: #ccccff;
|
||||
fill: #fff5ad;
|
||||
}
|
||||
|
||||
.noteText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-size:14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Is it possible to adjust the margins for rendering the sequence diagram.
|
||||
@@ -391,19 +490,25 @@ How to use the CLI is described in the [mermaidCLI](mermaidCLI.html) page.
|
||||
|
||||
```javascript
|
||||
mermaid.sequenceConfig = {
|
||||
diagramMarginX:50,
|
||||
diagramMarginY:10,
|
||||
boxTextMargin:5,
|
||||
noteMargin:10,
|
||||
messageMargin:35,
|
||||
mirrorActors:true
|
||||
diagramMarginX: 50,
|
||||
diagramMarginY: 10,
|
||||
boxTextMargin: 5,
|
||||
noteMargin: 10,
|
||||
messageMargin: 35,
|
||||
mirrorActors: true
|
||||
};
|
||||
```
|
||||
|
||||
### Possible configuration params:
|
||||
|
||||
Param | Description | Default value
|
||||
--- | --- | ---
|
||||
mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false
|
||||
bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwantewd clipping which is why this config param exists. | 1
|
||||
|
||||
| Param | Description | Default value |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ |
|
||||
| mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false |
|
||||
| bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwanted clipping which is why this config param exists. | 1 |
|
||||
| actorFontSize | Sets the font size for the actor's description | 14 |
|
||||
| actorFontFamily | Sets the font family for the actor's description | "Open-Sans", "sans-serif" |
|
||||
| noteFontSize | Sets the font size for actor-attached notes | 14 |
|
||||
| noteFontFamily | Sets the font family for actor-attached notes | "trebuchet ms", verdana, arial |
|
||||
| noteAlign | Sets the text alignment for text in actor-attached notes | center |
|
||||
| messageFontSize | Sets the font size for actor<->actor messages | 16 |
|
||||
| messageFontFamily | Sets the font family for actor<->actor messages | "trebuchet ms", verdana, arial |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make it easier for users to share diagrams between mermaid and plantUml.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
@@ -15,6 +15,16 @@ stateDiagram
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
```mermaid
|
||||
stateDiagram
|
||||
[*] --> Still
|
||||
@@ -30,39 +40,39 @@ In state diagrams systems are described in terms of its states and how the syste
|
||||
|
||||
## States
|
||||
|
||||
A state can be declares in multiple ways. The simplest way is to define a state id as a description.
|
||||
A state can be declared in multiple ways. The simplest way is to define a state id as a description.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s1
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s1
|
||||
```
|
||||
|
||||
Another way is by using the state keyword with a description as per below:
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
state "This is a state description" as s2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
state "This is a state description" as s2
|
||||
```
|
||||
|
||||
Another way to define a state with a description is to define the state id followed by a colon and the description:
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s2 : This is a state description
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s2 : This is a state description
|
||||
```
|
||||
|
||||
@@ -73,24 +83,24 @@ Transitions are path/edges when one state passes into another. This is represent
|
||||
When you define a transition between two states and the states are not already defined the undefined states are defined with the id from the transition. You can later add descriptions to states defined this way.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s1 --> s2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s1 --> s2
|
||||
```
|
||||
|
||||
It is possible to add text to a transition. To describe what it represents.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s1 --> s2: A transition
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
s1 --> s2: A transition
|
||||
```
|
||||
|
||||
@@ -99,13 +109,13 @@ stateDiagram
|
||||
There are two special states indicating the start and stop of the diagram. These are written with the [\*] syntax and the direction of the transition to it defines it either as a start or a stop state.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> s1
|
||||
s1 --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> s1
|
||||
s1 --> [*]
|
||||
```
|
||||
@@ -118,7 +128,7 @@ have several internal states. These are called composite states in this terminol
|
||||
In order to define a composite state you need to use the state keyword followed by and id and the body of the composite state between \{\}. See the example below:
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
state First {
|
||||
[*] --> second
|
||||
@@ -127,7 +137,7 @@ stateDiagram
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
state First {
|
||||
[*] --> second
|
||||
@@ -138,7 +148,7 @@ stateDiagram
|
||||
You can do this in several layers:
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
|
||||
state First {
|
||||
@@ -157,14 +167,14 @@ stateDiagram
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2-v2
|
||||
[*] --> First
|
||||
|
||||
state First {
|
||||
[*] --> Second
|
||||
state Second {
|
||||
[*] --> second
|
||||
second --> Third
|
||||
[*] --> second2
|
||||
second2 --> Third
|
||||
|
||||
state Third {
|
||||
[*] --> third
|
||||
@@ -177,7 +187,7 @@ stateDiagram
|
||||
You can also define transitions also between composite states:
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
First --> Second
|
||||
First --> Third
|
||||
@@ -197,7 +207,7 @@ stateDiagram
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
First --> Second
|
||||
First --> Third
|
||||
@@ -223,7 +233,7 @@ stateDiagram
|
||||
It is possible to specify a fork in the diagram using <<fork>> <<join>>.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
@@ -237,7 +247,7 @@ It is possible to specify a fork in the diagram using <<fork>> <&
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
@@ -258,7 +268,7 @@ Sometimes nothing says it better then a Post-it note. That is also the case in s
|
||||
Here you can choose to put the note to the *right of* or to the *left of* a node.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You can write
|
||||
@@ -269,7 +279,7 @@ Here you can choose to put the note to the *right of* or to the *left of* a node
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You can write
|
||||
@@ -285,7 +295,7 @@ Here you can choose to put the note to the *right of* or to the *left of* a node
|
||||
As in plantUml you can specify concurrency using the -- symbol.
|
||||
|
||||
```markdown
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
|
||||
state Active {
|
||||
@@ -304,7 +314,7 @@ As in plantUml you can specify concurrency using the -- symbol.
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
|
||||
state Active {
|
||||
@@ -322,6 +332,21 @@ As in plantUml you can specify concurrency using the -- symbol.
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
|
||||
```
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
%% this is a comment
|
||||
Still --> Moving
|
||||
Moving --> Still %% another comment
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
Styling of the a state diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/state.scss
|
||||
|
||||
@@ -159,14 +159,13 @@ The main idea of the API is to be able to call a render function with the graph
|
||||
will render the graph and call a callback with the resulting svg code. With this approach it is up to the site creator to
|
||||
fetch the graph definition from the site (perhaps from a textarea), render it and place the graph somewhere in the site.
|
||||
|
||||
To do this, include mermaidAPI on your web website instead of mermaid.js. The example below show an outline of how this
|
||||
could be used. The example just logs the resulting svg to the javascript console.
|
||||
The example below show an outline of how this could be used. The example just logs the resulting svg to the javascript console.
|
||||
|
||||
```html
|
||||
<script src="mermaidAPI.js"></script>
|
||||
<script src="mermaid.js"></script>
|
||||
|
||||
<script>
|
||||
mermaidAPI.initialize({
|
||||
mermaid.mermaidAPI.initialize({
|
||||
startOnLoad:false
|
||||
});
|
||||
$(function(){
|
||||
@@ -178,7 +177,7 @@ could be used. The example just logs the resulting svg to the javascript console
|
||||
};
|
||||
|
||||
var graphDefinition = 'graph TB\na-->b';
|
||||
var graph = mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
|
||||
var graph = mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
34
docs/user-journey.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# User Journey Diagram
|
||||
|
||||
> User journeys describe at a high level of detail exactly what steps different users take to complete a specific task within a system, application or website. This technique shows the current (as-is) user workflow, and reveals areas of improvement for the to-be workflow. (Wikipedia)
|
||||
|
||||
Mermaid can render user journey diagrams:
|
||||
|
||||
```markdown
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 5: Me
|
||||
```
|
||||
```mermaid
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 5: Me
|
||||
```
|
||||
|
||||
Each user journey is split into sections, these describe the part of the task
|
||||
the user is trying to complete.
|
||||
|
||||
Tasks syntax is `Task name: <score>: <comma separated list of actors>`
|
||||
|
||||
BIN
img/gray-journey.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
img/gray-user-journey.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
48
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "8.4.2",
|
||||
"version": "8.5.0",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"main": "dist/mermaid.core.js",
|
||||
"keywords": [
|
||||
@@ -13,7 +13,7 @@
|
||||
"git graph"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"build": "webpack --progress --colors -p",
|
||||
"postbuild": "documentation build src/mermaidAPI.js --shallow -f md --markdown-toc false -o docs/mermaidAPI.md",
|
||||
"build:watch": "yarn build --watch",
|
||||
"minify": "minify ./dist/mermaid.js > ./dist/mermaid.min.js",
|
||||
@@ -24,10 +24,11 @@
|
||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||
"e2e-upd": "yarn lint && jest e2e -u --config e2e/jest.config.js",
|
||||
"dev": "webpack-dev-server --config webpack.config.e2e.js",
|
||||
"test": "yarn lint && jest src",
|
||||
"test": "yarn lint && jest src/.*",
|
||||
"test:watch": "jest --watch src",
|
||||
"prepublishOnly": "yarn build && yarn release && yarn test && yarn e2e",
|
||||
"prepush": "yarn test"
|
||||
"prepush": "yarn test",
|
||||
"prepare": "yarn build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -49,45 +50,44 @@
|
||||
"@braintree/sanitize-url": "^3.1.0",
|
||||
"crypto-random-string": "^3.0.1",
|
||||
"d3": "^5.7.0",
|
||||
"dagre-d3": "dagrejs/dagre-d3",
|
||||
"dagre": "^0.8.4",
|
||||
"dagre-d3": "^0.6.4",
|
||||
"entity-decode": "^2.0.2",
|
||||
"graphlib": "^2.1.7",
|
||||
"he": "^1.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"minify": "^4.1.1",
|
||||
"moment-mini": "^2.22.1",
|
||||
"prettier": "^1.18.2",
|
||||
"scope-css": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"documentation": "^12.0.1",
|
||||
"eslint": "^6.3.0",
|
||||
"eslint-config-prettier": "^6.3.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/preset-env": "^7.2.0",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@percy/cypress": "^2.0.1",
|
||||
"@percy/cypress": "*",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "^8.0.4",
|
||||
"coveralls": "^3.0.2",
|
||||
"css-loader": "^2.0.1",
|
||||
"css-to-string-loader": "^0.1.3",
|
||||
"cypress": "3.4.0",
|
||||
"cypress": "4.0.1",
|
||||
"documentation": "^12.0.1",
|
||||
"eslint": "^6.3.0",
|
||||
"eslint-config-prettier": "^6.3.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"husky": "^1.2.1",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest-environment-puppeteer": "^4.2.0",
|
||||
"jest-image-snapshot": "^2.8.2",
|
||||
"jest-puppeteer": "^4.2.0",
|
||||
"jest": "^24.9.0",
|
||||
"jison": "^0.4.18",
|
||||
"moment": "^2.23.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"prettier": "^1.18.2",
|
||||
"puppeteer": "^1.17.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"start-server-and-test": "^1.10.0",
|
||||
"webpack": "^4.27.1",
|
||||
"start-server-and-test": "^1.10.6",
|
||||
"terser-webpack-plugin": "^2.2.2",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-bundle-analyzer": "^3.7.0",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.4.1",
|
||||
"webpack-node-externals": "^1.7.2",
|
||||
@@ -100,5 +100,9 @@
|
||||
"ignore": [
|
||||
"babel-core"
|
||||
]
|
||||
}
|
||||
},
|
||||
"sideEffects": [
|
||||
"**/*.css",
|
||||
"**/*.scss"
|
||||
]
|
||||
}
|
||||
|
||||
3
src/__mocks__/entity-decode/browser.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = function(txt) {
|
||||
return txt;
|
||||
};
|
||||
136
src/dagre-wrapper/GraphObjects.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Cluster handling
|
||||
|
||||
Dagre does not support edges between nodes and clusters or between clusters to other clusters. In order to remedy this shortcoming the dagre wrapper implements a few work-arounds.
|
||||
|
||||
In the diagram below there are two clusters and there are no edges to nodes outside the own cluster.
|
||||
|
||||
```mermaid
|
||||
flowchart
|
||||
subgraph C1
|
||||
a --> b
|
||||
end
|
||||
subgraph C2
|
||||
c
|
||||
end
|
||||
C1 --> C2
|
||||
```
|
||||
|
||||
In this case the dagre-wrapper will transform the graph to the graph below.
|
||||
```mermaid
|
||||
flowchart
|
||||
C1 --> C2
|
||||
```
|
||||
|
||||
The new nodes C1 and C2 are a special type of nodes, clusterNodes. ClusterNodes have have the nodes in the cluster including the cluster attached in a graph object.
|
||||
|
||||
When rendering this diagram it it beeing rendered recursivly. The diagram is rendered by the dagre-mermaid:render function which in turn will be used to render the node C1 and the node C2. The result of those renderings will be inserted as nodes in the "root" diagram. With this recursive approach it would be possible to have different layout direction for each cluster.
|
||||
|
||||
```
|
||||
{ clusterNode: true, graph }
|
||||
```
|
||||
*Data for a clusterNode*
|
||||
|
||||
When a cluster has edges to or from some of its nodes leading outside the cluster the approach of recursive rendering can not be used as the layout of the graph needs to take responsibility for nodes outside of the cluster.
|
||||
|
||||
```mermaid
|
||||
flowchart
|
||||
subgraph C1
|
||||
a
|
||||
end
|
||||
subgraph C2
|
||||
b
|
||||
end
|
||||
a --> C2
|
||||
```
|
||||
|
||||
To handle this case a special type of edge is inserted. The edge to/from the cluster is replaced with an edge to/from a node in the cluster which is tagged with toCluster/fromCluster. When rendering this edge the intersection between the edge and the border of the cluster is calculated making the edge start/stop there. In practice this renders like an an edge to/from the cluster.
|
||||
|
||||
In the diagram above the root diagram would be rendered with C1 whereas C2 would be rendered recursively.
|
||||
|
||||
Of these two approaches the top one renders better and is used when possible. When this is not possible, ie an edge is added crossing the border the non recursive approach is used.
|
||||
|
||||
|
||||
# Graph objects and their properties
|
||||
|
||||
Explains the representation of various objects used to render the flow charts and what the properties mean. This ofc from the perspective of the dagre-wrapper.
|
||||
|
||||
## node
|
||||
|
||||
Sample object:
|
||||
```json
|
||||
{
|
||||
"shape":"rect",
|
||||
"labelText":"Test",
|
||||
"rx":0,
|
||||
"ry":0,
|
||||
"class":"default",
|
||||
"style":"",
|
||||
"id":"Test",
|
||||
"type":"group",
|
||||
"padding":15}
|
||||
```
|
||||
|
||||
This is set by the renderer of the diagram and insert the data that the wrapper neds for rendering.
|
||||
|
||||
| property | description |
|
||||
| ---------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| labelStyle | Css styles for the label. User for instance for stylling the labels for clusters |
|
||||
| shape | The shape of the node. |
|
||||
| labelText | The text on the label |
|
||||
| rx | The corner radius - maybe part of the shape instead? Used for rects. |
|
||||
| ry | The corner radius - maybe part of the shape instead? Used for rects. |
|
||||
| classes | Classes to be set for the shape. Not used |
|
||||
| style | Css styles for the actual shape |
|
||||
| id | id of the shape |
|
||||
| type | if set to group then this node indicates *a cluster*. |
|
||||
| padding | Padding. Passed from the render as this might differ between different diagrams. Maybe obsolete. |
|
||||
|
||||
|
||||
# edge
|
||||
|
||||
arrowType sets the type of arrows to use. The following arrow types are currently supported:
|
||||
|
||||
arrow_cross
|
||||
double_arrow_cross
|
||||
arrow_point
|
||||
double_arrow_point
|
||||
arrow_circle
|
||||
double_arrow_circle
|
||||
|
||||
Lets try to make these types semantic free so that diagram type semantics does not find its way in to this more generic layer.
|
||||
|
||||
|
||||
Required edgeData for proper rendering:
|
||||
|
||||
| property | description |
|
||||
| ---------- | ---------------------------------------- |
|
||||
| id | Id of the edge |
|
||||
| arrowHead | overlap between arrowHead and arrowType? |
|
||||
| arrowType | overlap between arrowHead and arrowType? |
|
||||
| style | |
|
||||
| labelStyle | |
|
||||
| label | overlap between label and labelText? |
|
||||
| labelPos | |
|
||||
| labelType | overlap between label and labelText? |
|
||||
|
||||
|
||||
# Markers
|
||||
|
||||
Define what markers that should be included in the diagram with the insert markers function. The function takes two arguments, first the element in which the markers should be included and a list of the markers that should be added.
|
||||
|
||||
Ex:
|
||||
insertMarkers(el, ['point', 'circle'])
|
||||
|
||||
The example above adds the markers point and cross. This means that edges with the arrowTypes arrow_cross, double_arrow_cross, arrow_point and double_arrow_cross will get the corresponding markers but arrowType arrow_cross will have no impact.
|
||||
|
||||
Current markers:
|
||||
* point - the standard arrow from flowcharts
|
||||
* circle - Arrows ending with circle
|
||||
* cross - arrows starting and ending with a cross
|
||||
|
||||
|
||||
// Todo - in case of common renderer
|
||||
# Common functions used by the renderer to be implemented by the Db
|
||||
|
||||
getDirection
|
||||
getClasses
|
||||
210
src/dagre-wrapper/clusters.js
Normal file
@@ -0,0 +1,210 @@
|
||||
import intersectRect from './intersect/intersect-rect';
|
||||
import { logger as log } from '../logger'; // eslint-disable-line
|
||||
import createLabel from './createLabel';
|
||||
|
||||
const rect = (parent, node) => {
|
||||
log.trace('Creating subgraph rect for ', node.id, node);
|
||||
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'cluster')
|
||||
.attr('id', node.id);
|
||||
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
// Create the label and insert it after the rect
|
||||
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
|
||||
|
||||
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
|
||||
|
||||
// Get the size of the label
|
||||
const bbox = text.getBBox();
|
||||
|
||||
const padding = 0 * node.padding;
|
||||
const halfPadding = padding / 2;
|
||||
|
||||
log.trace('Data ', node, JSON.stringify(node));
|
||||
// center the rect around its coordinate
|
||||
rect
|
||||
.attr('rx', node.rx)
|
||||
.attr('ry', node.ry)
|
||||
.attr('x', node.x - node.width / 2 - halfPadding)
|
||||
.attr('y', node.y - node.height / 2 - halfPadding)
|
||||
.attr('width', node.width + padding)
|
||||
.attr('height', node.height + padding);
|
||||
|
||||
// Center the label
|
||||
label.attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
(node.x - bbox.width / 2) +
|
||||
', ' +
|
||||
(node.y - node.height / 2 - node.padding / 3 + 3) +
|
||||
')'
|
||||
);
|
||||
|
||||
const rectBox = rect.node().getBBox();
|
||||
node.width = rectBox.width;
|
||||
node.height = rectBox.height;
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersectRect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Non visiable cluster where the note is group with its
|
||||
*/
|
||||
const noteGroup = (parent, node) => {
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'note-cluster')
|
||||
.attr('id', node.id);
|
||||
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
const padding = 0 * node.padding;
|
||||
const halfPadding = padding / 2;
|
||||
|
||||
// center the rect around its coordinate
|
||||
rect
|
||||
.attr('rx', node.rx)
|
||||
.attr('ry', node.ry)
|
||||
.attr('x', node.x - node.width / 2 - halfPadding)
|
||||
.attr('y', node.y - node.height / 2 - halfPadding)
|
||||
.attr('width', node.width + padding)
|
||||
.attr('height', node.height + padding)
|
||||
.attr('fill', 'none');
|
||||
|
||||
const rectBox = rect.node().getBBox();
|
||||
node.width = rectBox.width;
|
||||
node.height = rectBox.height;
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersectRect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
const roundedWithTitle = (parent, node) => {
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', node.classes)
|
||||
.attr('id', node.id);
|
||||
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
// Create the label and insert it after the rect
|
||||
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
|
||||
const innerRect = shapeSvg.append('rect');
|
||||
|
||||
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
|
||||
|
||||
// Get the size of the label
|
||||
const bbox = text.getBBox();
|
||||
|
||||
const padding = 0 * node.padding;
|
||||
const halfPadding = padding / 2;
|
||||
|
||||
// center the rect around its coordinate
|
||||
rect
|
||||
.attr('class', 'outer')
|
||||
.attr('x', node.x - node.width / 2 - halfPadding)
|
||||
.attr('y', node.y - node.height / 2 - halfPadding)
|
||||
.attr('width', node.width + padding)
|
||||
.attr('height', node.height + padding);
|
||||
innerRect
|
||||
.attr('class', 'inner')
|
||||
.attr('x', node.x - node.width / 2 - halfPadding)
|
||||
.attr('y', node.y - node.height / 2 - halfPadding + bbox.height - 1)
|
||||
.attr('width', node.width + padding)
|
||||
.attr('height', node.height + padding - bbox.height - 3);
|
||||
|
||||
// Center the label
|
||||
label.attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
(node.x - bbox.width / 2) +
|
||||
', ' +
|
||||
(node.y - node.height / 2 - node.padding / 3 + 3) +
|
||||
')'
|
||||
);
|
||||
|
||||
const rectBox = rect.node().getBBox();
|
||||
node.width = rectBox.width;
|
||||
node.height = rectBox.height;
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersectRect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const divider = (parent, node) => {
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', node.classes)
|
||||
.attr('id', node.id);
|
||||
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
const padding = 0 * node.padding;
|
||||
const halfPadding = padding / 2;
|
||||
|
||||
// center the rect around its coordinate
|
||||
rect
|
||||
.attr('class', 'divider')
|
||||
.attr('x', node.x - node.width / 2 - halfPadding)
|
||||
.attr('y', node.y - node.height / 2)
|
||||
.attr('width', node.width + padding)
|
||||
.attr('height', node.height + padding);
|
||||
|
||||
const rectBox = rect.node().getBBox();
|
||||
node.width = rectBox.width;
|
||||
node.height = rectBox.height;
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersectRect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const shapes = { rect, roundedWithTitle, noteGroup, divider };
|
||||
|
||||
let clusterElems = {};
|
||||
|
||||
export const insertCluster = (elem, node) => {
|
||||
log.trace('Inserting cluster');
|
||||
const shape = node.shape || 'rect';
|
||||
clusterElems[node.id] = shapes[shape](elem, node);
|
||||
};
|
||||
export const getClusterTitleWidth = (elem, node) => {
|
||||
const label = createLabel(node.labelText, node.labelStyle);
|
||||
elem.node().appendChild(label);
|
||||
const width = label.getBBox().width;
|
||||
elem.node().removeChild(label);
|
||||
return width;
|
||||
};
|
||||
|
||||
export const clear = () => {
|
||||
clusterElems = {};
|
||||
};
|
||||
|
||||
export const positionCluster = node => {
|
||||
log.info('Position cluster');
|
||||
const el = clusterElems[node.id];
|
||||
|
||||
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||
};
|
||||
29
src/dagre-wrapper/createLabel.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const createLabel = (vertexText, style, isTitle) => {
|
||||
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
|
||||
let rows = [];
|
||||
if (typeof vertexText === 'string') {
|
||||
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
|
||||
} else if (Array.isArray(vertexText)) {
|
||||
rows = vertexText;
|
||||
} else {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (let j = 0; j < rows.length; j++) {
|
||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||
tspan.setAttribute('dy', '1em');
|
||||
tspan.setAttribute('x', '0');
|
||||
if (isTitle) {
|
||||
tspan.setAttribute('class', 'title-row');
|
||||
} else {
|
||||
tspan.setAttribute('class', 'row');
|
||||
}
|
||||
tspan.textContent = rows[j].trim();
|
||||
svgLabel.appendChild(tspan);
|
||||
}
|
||||
return svgLabel;
|
||||
};
|
||||
|
||||
export default createLabel;
|
||||
231
src/dagre-wrapper/edges.js
Normal file
@@ -0,0 +1,231 @@
|
||||
import { logger } from '../logger'; // eslint-disable-line
|
||||
import createLabel from './createLabel';
|
||||
import { line, curveBasis } from 'd3';
|
||||
import { getConfig } from '../config';
|
||||
|
||||
let edgeLabels = {};
|
||||
|
||||
export const clear = () => {
|
||||
edgeLabels = {};
|
||||
};
|
||||
|
||||
export const insertEdgeLabel = (elem, edge) => {
|
||||
// Create the actual text element
|
||||
const labelElement = createLabel(edge.label, edge.labelStyle);
|
||||
|
||||
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
|
||||
|
||||
// Create inner g, label, this will be positioned now for centering the text
|
||||
const label = edgeLabel.insert('g').attr('class', 'label');
|
||||
label.node().appendChild(labelElement);
|
||||
|
||||
// Center the label
|
||||
const bbox = labelElement.getBBox();
|
||||
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||
|
||||
// Make element accessible by id for positioning
|
||||
edgeLabels[edge.id] = edgeLabel;
|
||||
|
||||
// Update the abstract data of the edge with the new information about its width and height
|
||||
edge.width = bbox.width;
|
||||
edge.height = bbox.height;
|
||||
};
|
||||
|
||||
export const positionEdgeLabel = edge => {
|
||||
logger.info('Moving label', edge.id, edge.label, edgeLabels[edge.id]);
|
||||
if (edge.label) {
|
||||
const el = edgeLabels[edge.id];
|
||||
el.attr('transform', 'translate(' + edge.x + ', ' + edge.y + ')');
|
||||
}
|
||||
};
|
||||
|
||||
// const getRelationType = function(type) {
|
||||
// switch (type) {
|
||||
// case stateDb.relationType.AGGREGATION:
|
||||
// return 'aggregation';
|
||||
// case stateDb.relationType.EXTENSION:
|
||||
// return 'extension';
|
||||
// case stateDb.relationType.COMPOSITION:
|
||||
// return 'composition';
|
||||
// case stateDb.relationType.DEPENDENCY:
|
||||
// return 'dependency';
|
||||
// }
|
||||
// };
|
||||
|
||||
const outsideNode = (node, point) => {
|
||||
const x = node.x;
|
||||
const y = node.y;
|
||||
const dx = Math.abs(point.x - x);
|
||||
const dy = Math.abs(point.y - y);
|
||||
const w = node.width / 2;
|
||||
const h = node.height / 2;
|
||||
if (dx > w || dy > h) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const intersection = (node, outsidePoint, insidePoint) => {
|
||||
logger.trace('intersection o:', outsidePoint, ' i:', insidePoint, node);
|
||||
const x = node.x;
|
||||
const y = node.y;
|
||||
|
||||
const dx = Math.abs(x - insidePoint.x);
|
||||
const w = node.width / 2;
|
||||
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
|
||||
const dy = Math.abs(y - insidePoint.y);
|
||||
const h = node.height / 2;
|
||||
let q = insidePoint.y < outsidePoint.y ? h - dy : h - dy;
|
||||
|
||||
const Q = Math.abs(outsidePoint.y - insidePoint.y);
|
||||
const R = Math.abs(outsidePoint.x - insidePoint.x);
|
||||
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h || false) { // eslint-disable-line
|
||||
// Intersection is top or bottom of rect.
|
||||
|
||||
r = (R * q) / Q;
|
||||
|
||||
return {
|
||||
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - r,
|
||||
y: insidePoint.y + q
|
||||
};
|
||||
} else {
|
||||
q = (Q * r) / R;
|
||||
r = (R * q) / Q;
|
||||
|
||||
return {
|
||||
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x + dx - w,
|
||||
y: insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const insertEdge = function(elem, edge, clusterDb, diagramType) {
|
||||
let points = edge.points;
|
||||
if (edge.toCluster) {
|
||||
logger.trace('edge', edge);
|
||||
logger.trace('to cluster', clusterDb[edge.toCluster]);
|
||||
points = [];
|
||||
let lastPointOutside;
|
||||
let isInside = false;
|
||||
edge.points.forEach(point => {
|
||||
const node = clusterDb[edge.toCluster].node;
|
||||
|
||||
if (!outsideNode(node, point) && !isInside) {
|
||||
logger.trace('inside', edge.toCluster, point, lastPointOutside);
|
||||
|
||||
// First point inside the rect
|
||||
const insterection = intersection(node, lastPointOutside, point);
|
||||
logger.trace('intersect', insterection);
|
||||
points.push(insterection);
|
||||
isInside = true;
|
||||
} else {
|
||||
if (!isInside) points.push(point);
|
||||
}
|
||||
lastPointOutside = point;
|
||||
});
|
||||
}
|
||||
|
||||
if (edge.fromCluster) {
|
||||
logger.trace('edge', edge);
|
||||
logger.trace('from cluster', clusterDb[edge.toCluster]);
|
||||
const updatedPoints = [];
|
||||
let lastPointOutside;
|
||||
let isInside = false;
|
||||
for (let i = points.length - 1; i >= 0; i--) {
|
||||
const point = points[i];
|
||||
const node = clusterDb[edge.fromCluster].node;
|
||||
|
||||
if (!outsideNode(node, point) && !isInside) {
|
||||
logger.trace('inside', edge.toCluster, point);
|
||||
|
||||
// First point inside the rect
|
||||
const insterection = intersection(node, lastPointOutside, point);
|
||||
// logger.trace('intersect', intersection(node, lastPointOutside, point));
|
||||
updatedPoints.unshift(insterection);
|
||||
// points.push(insterection);
|
||||
isInside = true;
|
||||
} else {
|
||||
// at the outside
|
||||
logger.trace('Outside point', point);
|
||||
if (!isInside) updatedPoints.unshift(point);
|
||||
}
|
||||
lastPointOutside = point;
|
||||
}
|
||||
points = updatedPoints;
|
||||
}
|
||||
|
||||
// The data for our line
|
||||
const lineData = points.filter(p => !Number.isNaN(p.y));
|
||||
|
||||
// This is the accessor function we talked about above
|
||||
const lineFunction = line()
|
||||
.x(function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.curve(curveBasis);
|
||||
|
||||
const svgPath = elem
|
||||
.append('path')
|
||||
.attr('d', lineFunction(lineData))
|
||||
.attr('id', edge.id)
|
||||
.attr('class', 'transition' + (edge.classes ? ' ' + edge.classes : ''));
|
||||
|
||||
// DEBUG code, adds a red circle at each edge coordinate
|
||||
// edge.points.forEach(point => {
|
||||
// elem
|
||||
// .append('circle')
|
||||
// .style('stroke', 'red')
|
||||
// .style('fill', 'red')
|
||||
// .attr('r', 1)
|
||||
// .attr('cx', point.x)
|
||||
// .attr('cy', point.y);
|
||||
// });
|
||||
|
||||
let url = '';
|
||||
if (getConfig().state.arrowMarkerAbsolute) {
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
logger.info('arrowType', edge.arrowType);
|
||||
switch (edge.arrowType) {
|
||||
case 'arrow_cross':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
|
||||
break;
|
||||
case 'double_arrow_cross':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
|
||||
break;
|
||||
case 'arrow_point':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
|
||||
break;
|
||||
case 'double_arrow_point':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
|
||||
break;
|
||||
case 'arrow_barb':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
|
||||
break;
|
||||
case 'double_arrow_barb':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barnEnd' + ')');
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
|
||||
break;
|
||||
case 'arrow_circle':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
|
||||
break;
|
||||
case 'double_arrow_circle':
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
156
src/dagre-wrapper/index.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import dagre from 'dagre';
|
||||
import graphlib from 'graphlib';
|
||||
import insertMarkers from './markers';
|
||||
import { updateNodeBounds } from './shapes/util';
|
||||
import {
|
||||
clear as clearGraphlib,
|
||||
clusterDb,
|
||||
adjustClustersAndEdges,
|
||||
findNonClusterChild
|
||||
} from './mermaid-graphlib';
|
||||
import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './nodes';
|
||||
import { insertCluster, clear as clearClusters } from './clusters';
|
||||
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges';
|
||||
import { logger as log } from '../logger';
|
||||
|
||||
const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
|
||||
log.info('Graph in recursive render:', graphlib.json.write(graph), parentCluster);
|
||||
const dir = graph.graph().rankdir;
|
||||
log.warn('Dir in recursive render - dir:', dir);
|
||||
|
||||
const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line
|
||||
if (!graph.nodes()) {
|
||||
log.trace('No nodes found for', graph);
|
||||
} else {
|
||||
log.trace('Recursive render', graph.nodes());
|
||||
}
|
||||
if (graph.edges().length > 0) {
|
||||
log.trace('Recursive edges', graph.edge(graph.edges()[0]));
|
||||
}
|
||||
const clusters = elem.insert('g').attr('class', 'clusters'); // eslint-disable-line
|
||||
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
|
||||
const edgeLabels = elem.insert('g').attr('class', 'edgeLabels');
|
||||
const nodes = elem.insert('g').attr('class', 'nodes');
|
||||
|
||||
// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
|
||||
// to the abstract node and is later used by dagre for the layout
|
||||
graph.nodes().forEach(function(v) {
|
||||
const node = graph.node(v);
|
||||
if (typeof parentCluster !== 'undefined') {
|
||||
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
|
||||
// data.clusterPositioning = true;
|
||||
log.trace('Setting data for cluster', data);
|
||||
graph.setNode(parentCluster.id, data);
|
||||
graph.setParent(v, parentCluster.id, data);
|
||||
}
|
||||
log.trace('(Insert) Node ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
if (node && node.clusterNode) {
|
||||
// const children = graph.children(v);
|
||||
log.trace('Cluster identified', v, node, graph.node(v));
|
||||
const newEl = recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
|
||||
updateNodeBounds(node, newEl);
|
||||
setNodeElem(newEl, node);
|
||||
|
||||
log.warn('Recursive render complete', newEl, node);
|
||||
} else {
|
||||
if (graph.children(v).length > 0) {
|
||||
// This is a cluster but not to be rendered recusively
|
||||
// Render as before
|
||||
log.trace('Cluster - the non recursive path', v, node.id, node, graph);
|
||||
log.trace(findNonClusterChild(node.id, graph));
|
||||
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
|
||||
// insertCluster(clusters, graph.node(v));
|
||||
} else {
|
||||
log.trace('Node - the non recursive path', v, node.id, node);
|
||||
insertNode(nodes, graph.node(v), dir);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Insert labels, this will insert them into the dom so that the width can be calculated
|
||||
// Also figure out which edges point to/from clusters and adjust them accordingly
|
||||
// Edges from/to clusters really points to the first child in the cluster.
|
||||
// TODO: pick optimal child in the cluster to us as link anchor
|
||||
graph.edges().forEach(function(e) {
|
||||
const edge = graph.edge(e.v, e.w, e.name);
|
||||
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
|
||||
|
||||
// Check if link is either from or to a cluster
|
||||
log.trace('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
|
||||
insertEdgeLabel(edgeLabels, edge);
|
||||
});
|
||||
|
||||
graph.edges().forEach(function(e) {
|
||||
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||
});
|
||||
log.info('#############################################');
|
||||
log.info('### Layout ###');
|
||||
log.info('#############################################');
|
||||
log.info(graph);
|
||||
dagre.layout(graph);
|
||||
log.trace('Graph after layout:', graphlib.json.write(graph));
|
||||
// Move the nodes to the correct place
|
||||
graph.nodes().forEach(function(v) {
|
||||
const node = graph.node(v);
|
||||
// log.trace('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||
log.trace(
|
||||
'Position ' + v + ': (' + node.x,
|
||||
',' + node.y,
|
||||
') width: ',
|
||||
node.width,
|
||||
' height: ',
|
||||
node.height
|
||||
);
|
||||
if (node && node.clusterNode) {
|
||||
// clusterDb[node.id].node = node;
|
||||
|
||||
positionNode(node);
|
||||
} else {
|
||||
// Non cluster node
|
||||
if (graph.children(v).length > 0) {
|
||||
// A cluster in the non-recurive way
|
||||
// positionCluster(node);
|
||||
insertCluster(clusters, node);
|
||||
clusterDb[node.id].node = node;
|
||||
} else {
|
||||
positionNode(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move the edge labels to the correct place after layout
|
||||
graph.edges().forEach(function(e) {
|
||||
const edge = graph.edge(e);
|
||||
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
|
||||
|
||||
insertEdge(edgePaths, edge, clusterDb, diagramtype);
|
||||
positionEdgeLabel(edge);
|
||||
});
|
||||
|
||||
return elem;
|
||||
};
|
||||
|
||||
export const render = (elem, graph, markers, diagramtype, id) => {
|
||||
insertMarkers(elem, markers, diagramtype, id);
|
||||
clearNodes();
|
||||
clearEdges();
|
||||
clearClusters();
|
||||
clearGraphlib();
|
||||
|
||||
log.warn('Graph before:', graphlib.json.write(graph));
|
||||
adjustClustersAndEdges(graph);
|
||||
log.warn('Graph after:', graphlib.json.write(graph));
|
||||
log.warn('Graph ever after:', graph.graph());
|
||||
recursiveRender(elem, graph, diagramtype);
|
||||
};
|
||||
|
||||
// const shapeDefinitions = {};
|
||||
// export const addShape = ({ shapeType: fun }) => {
|
||||
// shapeDefinitions[shapeType] = fun;
|
||||
// };
|
||||
|
||||
// const arrowDefinitions = {};
|
||||
// export const addArrow = ({ arrowType: fun }) => {
|
||||
// arrowDefinitions[arrowType] = fun;
|
||||
// };
|
||||
7
src/dagre-wrapper/intersect.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
node: require('./intersect-node'),
|
||||
circle: require('./intersect-circle'),
|
||||
ellipse: require('./intersect-ellipse'),
|
||||
polygon: require('./intersect-polygon'),
|
||||
rect: require('./intersect-rect')
|
||||
};
|
||||
17
src/dagre-wrapper/intersect/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Borrowed with love from from dagrge-d3. Many thanks to cpettitt!
|
||||
*/
|
||||
|
||||
import node from './intersect-node.js';
|
||||
import circle from './intersect-circle.js';
|
||||
import ellipse from './intersect-ellipse.js';
|
||||
import polygon from './intersect-polygon.js';
|
||||
import rect from './intersect-rect.js';
|
||||
|
||||
export default {
|
||||
node,
|
||||
circle,
|
||||
ellipse,
|
||||
polygon,
|
||||
rect
|
||||
};
|
||||
7
src/dagre-wrapper/intersect/intersect-circle.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import intersectEllipse from './intersect-ellipse';
|
||||
|
||||
function intersectCircle(node, rx, point) {
|
||||
return intersectEllipse(node, rx, rx, point);
|
||||
}
|
||||
|
||||
export default intersectCircle;
|
||||
24
src/dagre-wrapper/intersect/intersect-ellipse.js
Normal file
@@ -0,0 +1,24 @@
|
||||
function intersectEllipse(node, rx, ry, point) {
|
||||
// Formulae from: http://mathworld.wolfram.com/Ellipse-LineIntersection.html
|
||||
|
||||
var cx = node.x;
|
||||
var cy = node.y;
|
||||
|
||||
var px = cx - point.x;
|
||||
var py = cy - point.y;
|
||||
|
||||
var det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
|
||||
|
||||
var dx = Math.abs((rx * ry * px) / det);
|
||||
if (point.x < cx) {
|
||||
dx = -dx;
|
||||
}
|
||||
var dy = Math.abs((rx * ry * py) / det);
|
||||
if (point.y < cy) {
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
return { x: cx + dx, y: cy + dy };
|
||||
}
|
||||
|
||||
export default intersectEllipse;
|
||||
70
src/dagre-wrapper/intersect/intersect-line.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Returns the point at which two lines, p and q, intersect or returns
|
||||
* undefined if they do not intersect.
|
||||
*/
|
||||
function intersectLine(p1, p2, q1, q2) {
|
||||
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
|
||||
// p7 and p473.
|
||||
|
||||
var a1, a2, b1, b2, c1, c2;
|
||||
var r1, r2, r3, r4;
|
||||
var denom, offset, num;
|
||||
var x, y;
|
||||
|
||||
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
|
||||
// b1 y + c1 = 0.
|
||||
a1 = p2.y - p1.y;
|
||||
b1 = p1.x - p2.x;
|
||||
c1 = p2.x * p1.y - p1.x * p2.y;
|
||||
|
||||
// Compute r3 and r4.
|
||||
r3 = a1 * q1.x + b1 * q1.y + c1;
|
||||
r4 = a1 * q2.x + b1 * q2.y + c1;
|
||||
|
||||
// Check signs of r3 and r4. If both point 3 and point 4 lie on
|
||||
// same side of line 1, the line segments do not intersect.
|
||||
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
|
||||
return /*DONT_INTERSECT*/;
|
||||
}
|
||||
|
||||
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
|
||||
a2 = q2.y - q1.y;
|
||||
b2 = q1.x - q2.x;
|
||||
c2 = q2.x * q1.y - q1.x * q2.y;
|
||||
|
||||
// Compute r1 and r2
|
||||
r1 = a2 * p1.x + b2 * p1.y + c2;
|
||||
r2 = a2 * p2.x + b2 * p2.y + c2;
|
||||
|
||||
// Check signs of r1 and r2. If both point 1 and point 2 lie
|
||||
// on same side of second line segment, the line segments do
|
||||
// not intersect.
|
||||
if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
|
||||
return /*DONT_INTERSECT*/;
|
||||
}
|
||||
|
||||
// Line segments intersect: compute intersection point.
|
||||
denom = a1 * b2 - a2 * b1;
|
||||
if (denom === 0) {
|
||||
return /*COLLINEAR*/;
|
||||
}
|
||||
|
||||
offset = Math.abs(denom / 2);
|
||||
|
||||
// The denom/2 is to get rounding instead of truncating. It
|
||||
// is added or subtracted to the numerator, depending upon the
|
||||
// sign of the numerator.
|
||||
num = b1 * c2 - b2 * c1;
|
||||
x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
|
||||
|
||||
num = a2 * c1 - a1 * c2;
|
||||
y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
|
||||
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
function sameSign(r1, r2) {
|
||||
return r1 * r2 > 0;
|
||||
}
|
||||
|
||||
export default intersectLine;
|
||||
5
src/dagre-wrapper/intersect/intersect-node.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = intersectNode;
|
||||
|
||||
function intersectNode(node, point) {
|
||||
return node.intersect(point);
|
||||
}
|
||||
61
src/dagre-wrapper/intersect/intersect-polygon.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/* eslint "no-console": off */
|
||||
|
||||
import intersectLine from './intersect-line';
|
||||
|
||||
export default intersectPolygon;
|
||||
|
||||
/*
|
||||
* Returns the point ({x, y}) at which the point argument intersects with the
|
||||
* node argument assuming that it has the shape specified by polygon.
|
||||
*/
|
||||
function intersectPolygon(node, polyPoints, point) {
|
||||
var x1 = node.x;
|
||||
var y1 = node.y;
|
||||
|
||||
var intersections = [];
|
||||
|
||||
var minX = Number.POSITIVE_INFINITY;
|
||||
var minY = Number.POSITIVE_INFINITY;
|
||||
polyPoints.forEach(function(entry) {
|
||||
minX = Math.min(minX, entry.x);
|
||||
minY = Math.min(minY, entry.y);
|
||||
});
|
||||
|
||||
var left = x1 - node.width / 2 - minX;
|
||||
var top = y1 - node.height / 2 - minY;
|
||||
|
||||
for (var i = 0; i < polyPoints.length; i++) {
|
||||
var p1 = polyPoints[i];
|
||||
var p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
|
||||
var intersect = intersectLine(
|
||||
node,
|
||||
point,
|
||||
{ x: left + p1.x, y: top + p1.y },
|
||||
{ x: left + p2.x, y: top + p2.y }
|
||||
);
|
||||
if (intersect) {
|
||||
intersections.push(intersect);
|
||||
}
|
||||
}
|
||||
|
||||
if (!intersections.length) {
|
||||
console.log('NO INTERSECTION FOUND, RETURN NODE CENTER', node);
|
||||
return node;
|
||||
}
|
||||
|
||||
if (intersections.length > 1) {
|
||||
// More intersections, find the one nearest to edge end point
|
||||
intersections.sort(function(p, q) {
|
||||
var pdx = p.x - point.x;
|
||||
var pdy = p.y - point.y;
|
||||
var distp = Math.sqrt(pdx * pdx + pdy * pdy);
|
||||
|
||||
var qdx = q.x - point.x;
|
||||
var qdy = q.y - point.y;
|
||||
var distq = Math.sqrt(qdx * qdx + qdy * qdy);
|
||||
|
||||
return distp < distq ? -1 : distp === distq ? 0 : 1;
|
||||
});
|
||||
}
|
||||
return intersections[0];
|
||||
}
|
||||
32
src/dagre-wrapper/intersect/intersect-rect.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const intersectRect = (node, point) => {
|
||||
var x = node.x;
|
||||
var y = node.y;
|
||||
|
||||
// Rectangle intersection algorithm from:
|
||||
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
|
||||
var dx = point.x - x;
|
||||
var dy = point.y - y;
|
||||
var w = node.width / 2;
|
||||
var h = node.height / 2;
|
||||
|
||||
var sx, sy;
|
||||
if (Math.abs(dy) * w > Math.abs(dx) * h) {
|
||||
// Intersection is top or bottom of rect.
|
||||
if (dy < 0) {
|
||||
h = -h;
|
||||
}
|
||||
sx = dy === 0 ? 0 : (h * dx) / dy;
|
||||
sy = h;
|
||||
} else {
|
||||
// Intersection is left or right of rect.
|
||||
if (dx < 0) {
|
||||
w = -w;
|
||||
}
|
||||
sx = w;
|
||||
sy = dx === 0 ? 0 : (w * dy) / dx;
|
||||
}
|
||||
|
||||
return { x: x + sx, y: y + sy };
|
||||
};
|
||||
|
||||
export default intersectRect;
|
||||
260
src/dagre-wrapper/markers.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*/
|
||||
|
||||
import { logger } from '../logger';
|
||||
|
||||
// Only add the number of markers that the diagram needs
|
||||
const insertMarkers = (elem, markerArray, type, id) => {
|
||||
markerArray.forEach(markerName => {
|
||||
markers[markerName](elem, type, id);
|
||||
});
|
||||
};
|
||||
|
||||
const extension = (elem, type, id) => {
|
||||
logger.trace('Making markers for ', id);
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-extensionStart')
|
||||
.attr('class', 'extension ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 1,7 L18,13 V 1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-extensionEnd ' + type)
|
||||
.attr('class', 'extension ' + type)
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
const composition = (elem, type) => {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-compositionStart')
|
||||
.attr('class', 'extension ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-compositionEnd')
|
||||
.attr('class', 'extension ' + type)
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
};
|
||||
const aggregation = (elem, type) => {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-aggregationStart')
|
||||
.attr('class', 'extension ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-aggregationEnd')
|
||||
.attr('class', type)
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
};
|
||||
const dependency = (elem, type) => {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-dependencyStart')
|
||||
.attr('class', 'extension ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-dependencyEnd')
|
||||
.attr('class', type)
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
const point = (elem, type) => {
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', type + '-pointEnd')
|
||||
.attr('class', type)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 10)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 8)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||
.attr('class', 'arrowMarkerPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', type + '-pointStart')
|
||||
.attr('class', type)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 8)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 0 5 L 10 10 L 10 0 z')
|
||||
.attr('class', 'arrowMarkerPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
};
|
||||
const circle = (elem, type) => {
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', type + '-circleEnd')
|
||||
.attr('class', type)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 11)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 7)
|
||||
.attr('markerHeight', 7)
|
||||
.attr('orient', 'auto')
|
||||
.append('circle')
|
||||
.attr('cx', '5')
|
||||
.attr('cy', '5')
|
||||
.attr('r', '5')
|
||||
.attr('class', 'arrowMarkerPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', type + '-circleStart')
|
||||
.attr('class', type)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', -1)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 7)
|
||||
.attr('markerHeight', 7)
|
||||
.attr('orient', 'auto')
|
||||
.append('circle')
|
||||
.attr('cx', '5')
|
||||
.attr('cy', '5')
|
||||
.attr('r', '5')
|
||||
.attr('class', 'arrowMarkerPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
};
|
||||
const cross = (elem, type) => {
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', type + '-crossEnd')
|
||||
.attr('class', type)
|
||||
.attr('viewBox', '0 0 11 11')
|
||||
.attr('refX', 12)
|
||||
.attr('refY', 5.2)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 7)
|
||||
.attr('markerHeight', 7)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('stroke', 'black')
|
||||
.attr('d', 'M 1,1 l 9,9 M 10,1 l -9,9')
|
||||
.attr('class', 'arrowMarkerPath')
|
||||
.style('stroke-width', 2)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
|
||||
elem
|
||||
.append('marker')
|
||||
.attr('id', type + '-crossStart')
|
||||
.attr('class', type)
|
||||
.attr('viewBox', '0 0 11 11')
|
||||
.attr('refX', -1)
|
||||
.attr('refY', 5.2)
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 7)
|
||||
.attr('markerHeight', 7)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('stroke', 'black')
|
||||
.attr('d', 'M 1,1 l 9,9 M 10,1 l -9,9')
|
||||
.attr('class', 'arrowMarkerPath')
|
||||
.style('stroke-width', 2)
|
||||
.style('stroke-dasharray', '1,0');
|
||||
};
|
||||
const barb = (elem, type) => {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-barbEnd')
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 14)
|
||||
.attr('markerUnits', 0)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
// TODO rename the class diagram markers to something shape descriptive and semanitc free
|
||||
const markers = {
|
||||
extension,
|
||||
composition,
|
||||
aggregation,
|
||||
dependency,
|
||||
point,
|
||||
circle,
|
||||
cross,
|
||||
barb
|
||||
};
|
||||
export default insertMarkers;
|
||||
403
src/dagre-wrapper/mermaid-graphlib.js
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* Decorates with functions required by mermaids dagre-wrapper.
|
||||
*/
|
||||
import { logger as log } from '../logger';
|
||||
import graphlib from 'graphlib';
|
||||
|
||||
export let clusterDb = {};
|
||||
let decendants = {};
|
||||
let parents = {};
|
||||
|
||||
export const clear = () => {
|
||||
decendants = {};
|
||||
parents = {};
|
||||
clusterDb = {};
|
||||
};
|
||||
|
||||
const isDecendant = (id, ancenstorId) => {
|
||||
// if (id === ancenstorId) return true;
|
||||
|
||||
log.debug(
|
||||
'In isDecendant',
|
||||
ancenstorId,
|
||||
' ',
|
||||
id,
|
||||
' = ',
|
||||
decendants[ancenstorId].indexOf(id) >= 0
|
||||
);
|
||||
if (decendants[ancenstorId].indexOf(id) >= 0) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const edgeInCluster = (edge, clusterId) => {
|
||||
log.info('Decendants of ', clusterId, ' is ', decendants[clusterId]);
|
||||
log.info('Edge is ', edge);
|
||||
// Edges to/from the cluster is not in the cluster, they are in the parent
|
||||
if (edge.v === clusterId) return false;
|
||||
if (edge.w === clusterId) return false;
|
||||
|
||||
if (!decendants[clusterId]) {
|
||||
log.debug('Tilt, ', clusterId, ',not in decendants');
|
||||
return false;
|
||||
}
|
||||
log.info('Here ');
|
||||
|
||||
if (decendants[clusterId].indexOf(edge.v) >= 0) return true;
|
||||
if (isDecendant(edge.v, clusterId)) return true;
|
||||
if (isDecendant(edge.w, clusterId)) return true;
|
||||
if (decendants[clusterId].indexOf(edge.w) >= 0) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const copy = (clusterId, graph, newGraph, rootId) => {
|
||||
log.info(
|
||||
'Copying children of ',
|
||||
clusterId,
|
||||
'root',
|
||||
rootId,
|
||||
'data',
|
||||
graph.node(clusterId),
|
||||
rootId
|
||||
);
|
||||
const nodes = graph.children(clusterId) || [];
|
||||
|
||||
// Include cluster node if it is not the root
|
||||
if (clusterId !== rootId) {
|
||||
nodes.push(clusterId);
|
||||
}
|
||||
|
||||
log.debug('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (graph.children(node).length > 0) {
|
||||
copy(node, graph, newGraph, rootId);
|
||||
} else {
|
||||
const data = graph.node(node);
|
||||
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
|
||||
newGraph.setNode(node, data);
|
||||
log.debug('Setting parent', node, graph.parent(node));
|
||||
if (rootId !== graph.parent(node)) {
|
||||
newGraph.setParent(node, graph.parent(node));
|
||||
}
|
||||
|
||||
if (clusterId !== rootId && node !== clusterId) {
|
||||
log.debug('Setting parent', node, clusterId);
|
||||
newGraph.setParent(node, clusterId);
|
||||
} else {
|
||||
log.info('In copy ', clusterId, 'root', rootId, 'data', graph.node(clusterId), rootId);
|
||||
log.debug(
|
||||
'Not Setting parent for node=',
|
||||
node,
|
||||
'cluster!==rootId',
|
||||
clusterId !== rootId,
|
||||
'node!==clusterId',
|
||||
node !== clusterId
|
||||
);
|
||||
}
|
||||
const edges = graph.edges(node);
|
||||
log.debug('Copying Edges', edges);
|
||||
edges.forEach(edge => {
|
||||
log.info('Edge', edge);
|
||||
const data = graph.edge(edge.v, edge.w, edge.name);
|
||||
log.info('Edge data', data, rootId);
|
||||
try {
|
||||
// Do not copy edges in and out of the root cluster, they belong to the parent graph
|
||||
if (edgeInCluster(edge, rootId)) {
|
||||
log.info('Copying as ', edge.v, edge.w, data, edge.name);
|
||||
newGraph.setEdge(edge.v, edge.w, data, edge.name);
|
||||
log.info('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0]));
|
||||
} else {
|
||||
log.info(
|
||||
'Skipping copy of edge ',
|
||||
edge.v,
|
||||
'-->',
|
||||
edge.w,
|
||||
' rootId: ',
|
||||
rootId,
|
||||
' clusterId:',
|
||||
clusterId
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
log.debug('Removing node', node);
|
||||
graph.removeNode(node);
|
||||
});
|
||||
};
|
||||
export const extractDecendants = (id, graph) => {
|
||||
// log.debug('Extracting ', id);
|
||||
const children = graph.children(id);
|
||||
let res = [].concat(children);
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
parents[children[i]] = id;
|
||||
res = res.concat(extractDecendants(children[i], graph));
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the graph, checking that all parent child relation points to existing nodes and that
|
||||
* edges between nodes also ia correct. When not correct the function logs the discrepancies.
|
||||
* @param {graphlib graph} g
|
||||
*/
|
||||
export const validate = graph => {
|
||||
const edges = graph.edges();
|
||||
log.trace('Edges: ', edges);
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
if (graph.children(edges[i].v).length > 0) {
|
||||
log.trace('The node ', edges[i].v, ' is part of and edge even though it has children');
|
||||
return false;
|
||||
}
|
||||
if (graph.children(edges[i].w).length > 0) {
|
||||
log.trace('The node ', edges[i].w, ' is part of and edge even though it has children');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a child that is not a cluster. When faking a edge between a node and a cluster.
|
||||
* @param {Finds a } id
|
||||
* @param {*} graph
|
||||
*/
|
||||
export const findNonClusterChild = (id, graph) => {
|
||||
// const node = graph.node(id);
|
||||
log.trace('Searching', id);
|
||||
const children = graph.children(id);
|
||||
if (children.length < 1) {
|
||||
log.trace('This is a valid node', id);
|
||||
return id;
|
||||
}
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const _id = findNonClusterChild(children[i], graph);
|
||||
if (_id) {
|
||||
log.trace('Found replacement for', id, ' => ', _id);
|
||||
return _id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getAnchorId = id => {
|
||||
if (!clusterDb[id]) {
|
||||
return id;
|
||||
}
|
||||
// If the cluster has no external connections
|
||||
if (!clusterDb[id].externalConnections) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Return the replacement node
|
||||
if (clusterDb[id]) {
|
||||
return clusterDb[id].id;
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
export const adjustClustersAndEdges = (graph, depth) => {
|
||||
if (!graph || depth > 10) {
|
||||
log.debug('Opting out, no graph ');
|
||||
return;
|
||||
} else {
|
||||
log.debug('Opting in, graph ');
|
||||
}
|
||||
// Go through the nodes and for each cluster found, save a replacment node, this can be used when
|
||||
// faking a link to a cluster
|
||||
graph.nodes().forEach(function(id) {
|
||||
const children = graph.children(id);
|
||||
if (children.length > 0) {
|
||||
log.trace(
|
||||
'Cluster identified',
|
||||
id,
|
||||
' Replacement id in edges: ',
|
||||
findNonClusterChild(id, graph)
|
||||
);
|
||||
decendants[id] = extractDecendants(id, graph);
|
||||
clusterDb[id] = { id: findNonClusterChild(id, graph), clusterData: graph.node(id) };
|
||||
}
|
||||
});
|
||||
|
||||
// Check incoming and outgoing edges for each cluster
|
||||
graph.nodes().forEach(function(id) {
|
||||
const children = graph.children(id);
|
||||
const edges = graph.edges();
|
||||
if (children.length > 0) {
|
||||
log.debug('Cluster identified', id, decendants);
|
||||
edges.forEach(edge => {
|
||||
// log.debug('Edge, decendants: ', edge, decendants[id]);
|
||||
|
||||
// Check if any edge leaves the cluster (not the actual cluster, thats a link from the box)
|
||||
if (edge.v !== id && edge.w !== id) {
|
||||
// Any edge where either the one of the nodes is decending to the cluster but not the other
|
||||
// if (decendants[id].indexOf(edge.v) < 0 && decendants[id].indexOf(edge.w) < 0) {
|
||||
|
||||
const d1 = isDecendant(edge.v, id);
|
||||
const d2 = isDecendant(edge.w, id);
|
||||
|
||||
// d1 xor d2 - if either d1 is true and d2 is false or the other way around
|
||||
if (d1 ^ d2) {
|
||||
log.debug('Edge: ', edge, ' leaves cluster ', id);
|
||||
log.debug('Decendants of ', id, ': ', decendants[id]);
|
||||
clusterDb[id].externalConnections = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
extractor(graph, 0);
|
||||
|
||||
// For clusters with incoming and/or outgoing edges translate those edges to a real node
|
||||
// in the cluster inorder to fake the edge
|
||||
graph.edges().forEach(function(e) {
|
||||
const edge = graph.edge(e);
|
||||
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
|
||||
|
||||
let v = e.v;
|
||||
let w = e.w;
|
||||
// Check if link is either from or to a cluster
|
||||
log.trace('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
|
||||
if (clusterDb[e.v] || clusterDb[e.w]) {
|
||||
log.trace('Fixing and trixing - removing', e.v, e.w, e.name);
|
||||
v = getAnchorId(e.v);
|
||||
w = getAnchorId(e.w);
|
||||
graph.removeEdge(e.v, e.w, e.name);
|
||||
if (v !== e.v) edge.fromCluster = e.v;
|
||||
if (w !== e.w) edge.toCluster = e.w;
|
||||
log.trace('Replacing with', v, w, e.name);
|
||||
graph.setEdge(v, w, edge, e.name);
|
||||
}
|
||||
});
|
||||
log.debug('Adjusted Graph', graphlib.json.write(graph));
|
||||
|
||||
log.trace(clusterDb);
|
||||
|
||||
// Remove references to extracted cluster
|
||||
// graph.edges().forEach(edge => {
|
||||
// if (isDecendant(edge.v, clusterId) || isDecendant(edge.w, clusterId)) {
|
||||
// graph.removeEdge(edge);
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
export const extractor = (graph, depth) => {
|
||||
log.debug('extractor - ', depth, graphlib.json.write(graph), graph.children('D'));
|
||||
if (depth > 10) {
|
||||
log.error('Bailing out');
|
||||
return;
|
||||
}
|
||||
// For clusters without incoming and/or outgoing edges, create a new cluster-node
|
||||
// containing the nodes and edges in the custer in a new graph
|
||||
// for (let i = 0;)
|
||||
let nodes = graph.nodes();
|
||||
let hasChildren = false;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
const children = graph.children(node);
|
||||
hasChildren = hasChildren || children.length > 0;
|
||||
}
|
||||
|
||||
if (!hasChildren) {
|
||||
log.debug('Done, no node has children', graph.nodes());
|
||||
return;
|
||||
}
|
||||
// const clusters = Object.keys(clusterDb);
|
||||
// clusters.forEach(clusterId => {
|
||||
log.debug('Nodes = ', nodes, depth);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
|
||||
log.debug(
|
||||
'Extracting node',
|
||||
node,
|
||||
clusterDb,
|
||||
clusterDb[node] && !clusterDb[node].externalConnections,
|
||||
!graph.parent(node),
|
||||
graph.node(node),
|
||||
graph.children('D'),
|
||||
' Depth ',
|
||||
depth
|
||||
);
|
||||
// Note that the node might have been removed after the Object.keys call so better check
|
||||
// that it still is in the game
|
||||
if (!clusterDb[node]) {
|
||||
// Skip if the node is not a cluster
|
||||
log.debug('Not a cluster', node, depth);
|
||||
// break;
|
||||
} else if (
|
||||
!clusterDb[node].externalConnections &&
|
||||
!graph.parent(node) &&
|
||||
graph.children(node) &&
|
||||
graph.children(node).length > 0
|
||||
) {
|
||||
log.debug(
|
||||
'Cluster without external connections, without a parent and with children',
|
||||
node,
|
||||
depth
|
||||
);
|
||||
|
||||
const graphSettings = graph.graph();
|
||||
|
||||
const clusterGraph = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true
|
||||
})
|
||||
.setGraph({
|
||||
rankdir: graphSettings.rankdir === 'TB' ? 'LR' : 'TB',
|
||||
// Todo: set proper spacing
|
||||
nodesep: 50,
|
||||
ranksep: 50,
|
||||
marginx: 8,
|
||||
marginy: 8
|
||||
})
|
||||
.setDefaultEdgeLabel(function() {
|
||||
return {};
|
||||
});
|
||||
|
||||
log.debug('Old graph before copy', graphlib.json.write(graph));
|
||||
copy(node, graph, clusterGraph, node);
|
||||
graph.setNode(node, {
|
||||
clusterNode: true,
|
||||
id: node,
|
||||
clusterData: clusterDb[node].clusterData,
|
||||
labelText: clusterDb[node].labelText,
|
||||
graph: clusterGraph
|
||||
});
|
||||
log.debug('New graph after copy', graphlib.json.write(clusterGraph));
|
||||
log.debug('Old graph after copy', graphlib.json.write(graph));
|
||||
} else {
|
||||
log.debug(
|
||||
'Cluster ** ',
|
||||
node,
|
||||
' **not meeting the criteria !externalConnections:',
|
||||
!clusterDb[node].externalConnections,
|
||||
' no parent: ',
|
||||
!graph.parent(node),
|
||||
' children ',
|
||||
graph.children(node) && graph.children(node).length > 0,
|
||||
graph.children('D'),
|
||||
depth
|
||||
);
|
||||
log.debug(clusterDb);
|
||||
}
|
||||
}
|
||||
|
||||
nodes = graph.nodes();
|
||||
log.debug('New list of nodes', nodes);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
const data = graph.node(node);
|
||||
log.debug(' Now next leveö', node, data);
|
||||
if (data.clusterNode) {
|
||||
extractor(data.graph, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
428
src/dagre-wrapper/mermaid-graphlib.spec.js
Normal file
@@ -0,0 +1,428 @@
|
||||
import graphlib from 'graphlib';
|
||||
import dagre from 'dagre';
|
||||
import { validate, adjustClustersAndEdges, extractDecendants } from './mermaid-graphlib';
|
||||
import { setLogLevel, logger } from '../logger';
|
||||
|
||||
describe('Graphlib decorations', () => {
|
||||
let g;
|
||||
beforeEach(function () {
|
||||
setLogLevel(1);
|
||||
g = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true
|
||||
});
|
||||
g.setGraph({
|
||||
rankdir: 'TB',
|
||||
nodesep: 10,
|
||||
ranksep: 10,
|
||||
marginx: 8,
|
||||
marginy: 8
|
||||
});
|
||||
g.setDefaultEdgeLabel(function () {
|
||||
return {};
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', function () {
|
||||
it('Validate should detect edges between clusters', function () {
|
||||
/*
|
||||
subgraph C1
|
||||
a --> b
|
||||
end
|
||||
subgraph C2
|
||||
c
|
||||
end
|
||||
C1 --> C2
|
||||
*/
|
||||
g.setNode('a', { data:1});
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setParent('a', 'C1');
|
||||
g.setParent('b', 'C1');
|
||||
g.setParent('c', 'C2');
|
||||
g.setEdge('a', 'b');
|
||||
g.setEdge('C1', 'C2');
|
||||
|
||||
console.log(g.nodes())
|
||||
|
||||
expect(validate(g)).toBe(false);
|
||||
});
|
||||
it('Validate should not detect edges between clusters after adjustment', function () {
|
||||
/*
|
||||
subgraph C1
|
||||
a --> b
|
||||
end
|
||||
subgraph C2
|
||||
c
|
||||
end
|
||||
C1 --> C2
|
||||
*/
|
||||
g.setNode('a', {});
|
||||
g.setNode('b', {});
|
||||
g.setNode('c', {});
|
||||
g.setParent('a', 'C1');
|
||||
g.setParent('b', 'C1');
|
||||
g.setParent('c', 'C2');
|
||||
g.setEdge('a', 'b');
|
||||
g.setEdge('C1', 'C2');
|
||||
|
||||
console.log(g.nodes())
|
||||
adjustClustersAndEdges(g);
|
||||
logger.info(g.edges())
|
||||
expect(validate(g)).toBe(true);
|
||||
});
|
||||
|
||||
it('Validate should detect edges between clusters and transform clusters GLB4', function () {
|
||||
/*
|
||||
a --> b
|
||||
subgraph C1
|
||||
subgraph C2
|
||||
a
|
||||
end
|
||||
b
|
||||
end
|
||||
C1 --> c
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setNode('C1', { data: 4 });
|
||||
g.setNode('C2', { data: 5 });
|
||||
g.setParent('a', 'C2');
|
||||
g.setParent('b', 'C1');
|
||||
g.setParent('C2', 'C1');
|
||||
g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
||||
g.setEdge('C1', 'c', { name: 'C1-external-link' });
|
||||
|
||||
adjustClustersAndEdges(g);
|
||||
logger.info(g.nodes())
|
||||
expect(g.nodes().length).toBe(2);
|
||||
expect(validate(g)).toBe(true);
|
||||
});
|
||||
it('Validate should detect edges between clusters and transform clusters GLB5', function () {
|
||||
/*
|
||||
a --> b
|
||||
subgraph C1
|
||||
a
|
||||
end
|
||||
subgraph C2
|
||||
b
|
||||
end
|
||||
C1 -->
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setParent('a', 'C1');
|
||||
g.setParent('b', 'C2');
|
||||
// g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
||||
g.setEdge('C1', 'C2', { name: 'C1-external-link' });
|
||||
|
||||
logger.info(g.nodes())
|
||||
adjustClustersAndEdges(g);
|
||||
logger.info(g.nodes())
|
||||
expect(g.nodes().length).toBe(2);
|
||||
expect(validate(g)).toBe(true);
|
||||
});
|
||||
it('adjustClustersAndEdges GLB6', function () {
|
||||
/*
|
||||
subgraph C1
|
||||
a
|
||||
end
|
||||
C1 --> b
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('C1', { data: 3 });
|
||||
g.setParent('a', 'C1');
|
||||
g.setEdge('C1', 'b', { data: 'link1' }, '1');
|
||||
|
||||
// logger.info(g.edges())
|
||||
adjustClustersAndEdges(g);
|
||||
logger.info(g.edges())
|
||||
expect(g.nodes()).toEqual(['b', 'C1']);
|
||||
expect(g.edges().length).toBe(1);
|
||||
expect(validate(g)).toBe(true);
|
||||
expect(g.node('C1').clusterNode).toBe(true);
|
||||
|
||||
const C1Graph = g.node('C1').graph;
|
||||
expect(C1Graph.nodes()).toEqual(['a']);
|
||||
});
|
||||
it('adjustClustersAndEdges GLB7', function () {
|
||||
/*
|
||||
subgraph C1
|
||||
a
|
||||
end
|
||||
C1 --> b
|
||||
C1 --> c
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setParent('a', 'C1');
|
||||
g.setNode('C1', { data: 4 });
|
||||
g.setEdge('C1', 'b', { data: 'link1' }, '1');
|
||||
g.setEdge('C1', 'c', { data: 'link2' }, '2');
|
||||
|
||||
logger.info(g.node('C1'))
|
||||
adjustClustersAndEdges(g);
|
||||
logger.info(g.edges())
|
||||
expect(g.nodes()).toEqual(['b', 'c', 'C1']);
|
||||
expect(g.nodes().length).toBe(3);
|
||||
expect(g.edges().length).toBe(2);
|
||||
|
||||
expect(g.edges().length).toBe(2);
|
||||
const edgeData = g.edge(g.edges()[1]);
|
||||
expect(edgeData.data).toBe('link2');
|
||||
expect(validate(g)).toBe(true);
|
||||
|
||||
const C1Graph = g.node('C1').graph;
|
||||
expect(C1Graph.nodes()).toEqual(['a']);
|
||||
});
|
||||
it('adjustClustersAndEdges GLB8', function () {
|
||||
/*
|
||||
subgraph A
|
||||
a
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
subgraph C
|
||||
c
|
||||
end
|
||||
A --> B
|
||||
A --> C
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setParent('a', 'A');
|
||||
g.setParent('b', 'B');
|
||||
g.setParent('c', 'C');
|
||||
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||
|
||||
// logger.info(g.edges())
|
||||
adjustClustersAndEdges(g);
|
||||
expect(g.nodes()).toEqual(['A', 'B', 'C']);
|
||||
expect(g.edges().length).toBe(2);
|
||||
|
||||
expect(g.edges().length).toBe(2);
|
||||
const edgeData = g.edge(g.edges()[1]);
|
||||
expect(edgeData.data).toBe('link2');
|
||||
expect(validate(g)).toBe(true);
|
||||
|
||||
const CGraph = g.node('C').graph;
|
||||
expect(CGraph.nodes()).toEqual(['c']);
|
||||
|
||||
});
|
||||
|
||||
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB10', function () {
|
||||
/*
|
||||
subgraph C
|
||||
subgraph D
|
||||
d
|
||||
end
|
||||
end
|
||||
*/
|
||||
|
||||
g.setNode('C', { data: 1 });
|
||||
g.setNode('D', { data: 2 });
|
||||
g.setNode('d', { data: 3 });
|
||||
g.setParent('d', 'D');
|
||||
g.setParent('D', 'C');
|
||||
|
||||
// logger.info('Graph before', g.node('D'))
|
||||
// logger.info('Graph before', graphlib.json.write(g))
|
||||
adjustClustersAndEdges(g);
|
||||
// logger.info('Graph after', graphlib.json.write(g), g.node('C').graph)
|
||||
|
||||
const CGraph = g.node('C').graph;
|
||||
const DGraph = CGraph.node('D').graph;
|
||||
|
||||
expect(CGraph.nodes()).toEqual(['D']);
|
||||
expect(DGraph.nodes()).toEqual(['d']);
|
||||
|
||||
expect(g.nodes()).toEqual(['C']);
|
||||
expect(g.nodes().length).toBe(1);
|
||||
});
|
||||
|
||||
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB11', function () {
|
||||
/*
|
||||
subgraph A
|
||||
a
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
subgraph C
|
||||
subgraph D
|
||||
d
|
||||
end
|
||||
end
|
||||
A --> B
|
||||
A --> C
|
||||
*/
|
||||
|
||||
g.setNode('C', { data: 1 });
|
||||
g.setNode('D', { data: 2 });
|
||||
g.setNode('d', { data: 3 });
|
||||
g.setNode('B', { data: 4 });
|
||||
g.setNode('b', { data: 5 });
|
||||
g.setNode('A', { data: 6 });
|
||||
g.setNode('a', { data: 7 });
|
||||
g.setParent('a', 'A');
|
||||
g.setParent('b', 'B');
|
||||
g.setParent('d', 'D');
|
||||
g.setParent('D', 'C');
|
||||
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||
|
||||
logger.info('Graph before', g.node('D'))
|
||||
logger.info('Graph before', graphlib.json.write(g))
|
||||
adjustClustersAndEdges(g);
|
||||
logger.trace('Graph after', graphlib.json.write(g))
|
||||
expect(g.nodes()).toEqual(['C', 'B', 'A']);
|
||||
expect(g.nodes().length).toBe(3);
|
||||
expect(g.edges().length).toBe(2);
|
||||
|
||||
const AGraph = g.node('A').graph;
|
||||
const BGraph = g.node('B').graph;
|
||||
const CGraph = g.node('C').graph;
|
||||
// logger.info(CGraph.nodes());
|
||||
const DGraph = CGraph.node('D').graph;
|
||||
// logger.info('DG', CGraph.children('D'));
|
||||
|
||||
logger.info('A', AGraph.nodes());
|
||||
expect(AGraph.nodes().length).toBe(1);
|
||||
expect(AGraph.nodes()).toEqual(['a']);
|
||||
logger.trace('Nodes', BGraph.nodes())
|
||||
expect(BGraph.nodes().length).toBe(1);
|
||||
expect(BGraph.nodes()).toEqual(['b']);
|
||||
expect(CGraph.nodes()).toEqual(['D']);
|
||||
expect(CGraph.nodes().length).toEqual(1);
|
||||
|
||||
expect(AGraph.edges().length).toBe(0);
|
||||
expect(BGraph.edges().length).toBe(0);
|
||||
expect(CGraph.edges().length).toBe(0);
|
||||
expect(DGraph.nodes()).toEqual(['d']);
|
||||
expect(DGraph.edges().length).toBe(0);
|
||||
// expect(CGraph.node('D')).toEqual({ data: 2 });
|
||||
expect(g.edges().length).toBe(2);
|
||||
|
||||
// expect(g.edges().length).toBe(2);
|
||||
// const edgeData = g.edge(g.edges()[1]);
|
||||
// expect(edgeData.data).toBe('link2');
|
||||
// expect(validate(g)).toBe(true);
|
||||
});
|
||||
it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB20', function () {
|
||||
/*
|
||||
a --> b
|
||||
subgraph b [Test]
|
||||
c --> d -->e
|
||||
end
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setNode('d', { data: 3 });
|
||||
g.setNode('e', { data: 3 });
|
||||
g.setParent('c', 'b');
|
||||
g.setParent('d', 'b');
|
||||
g.setParent('e', 'b');
|
||||
g.setEdge('a', 'b', { data: 'link1' }, '1');
|
||||
g.setEdge('c', 'd', { data: 'link2' }, '2');
|
||||
g.setEdge('d', 'e', { data: 'link2' }, '2');
|
||||
|
||||
logger.info('Graph before', graphlib.json.write(g))
|
||||
adjustClustersAndEdges(g);
|
||||
const bGraph = g.node('b').graph;
|
||||
// logger.trace('Graph after', graphlib.json.write(g))
|
||||
logger.info('Graph after', graphlib.json.write(bGraph));
|
||||
expect(bGraph.nodes().length).toBe(3);
|
||||
expect(bGraph.edges().length).toBe(2);
|
||||
});
|
||||
it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB21', function () {
|
||||
/*
|
||||
state a {
|
||||
state b {
|
||||
state c {
|
||||
e
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setNode('e', { data: 3 });
|
||||
g.setParent('b', 'a');
|
||||
g.setParent('c', 'b');
|
||||
g.setParent('e', 'c');
|
||||
|
||||
logger.info('Graph before', graphlib.json.write(g))
|
||||
adjustClustersAndEdges(g);
|
||||
const aGraph = g.node('a').graph;
|
||||
const bGraph = aGraph.node('b').graph;
|
||||
logger.info('Graph after', graphlib.json.write(aGraph));
|
||||
const cGraph = bGraph.node('c').graph;
|
||||
// logger.trace('Graph after', graphlib.json.write(g))
|
||||
expect(aGraph.nodes().length).toBe(1);
|
||||
expect(bGraph.nodes().length).toBe(1);
|
||||
expect(cGraph.nodes().length).toBe(1);
|
||||
expect(bGraph.edges().length).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
describe('extractDecendants', function () {
|
||||
let g;
|
||||
beforeEach(function () {
|
||||
setLogLevel(1);
|
||||
g = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true
|
||||
});
|
||||
g.setGraph({
|
||||
rankdir: 'TB',
|
||||
nodesep: 10,
|
||||
ranksep: 10,
|
||||
marginx: 8,
|
||||
marginy: 8
|
||||
});
|
||||
g.setDefaultEdgeLabel(function () {
|
||||
return {};
|
||||
});
|
||||
});
|
||||
it('Simple case of one level decendants GLB9', function () {
|
||||
/*
|
||||
subgraph A
|
||||
a
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
subgraph C
|
||||
c
|
||||
end
|
||||
A --> B
|
||||
A --> C
|
||||
*/
|
||||
g.setNode('a', { data: 1 });
|
||||
g.setNode('b', { data: 2 });
|
||||
g.setNode('c', { data: 3 });
|
||||
g.setParent('a', 'A');
|
||||
g.setParent('b', 'B');
|
||||
g.setParent('c', 'C');
|
||||
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||
|
||||
// logger.info(g.edges())
|
||||
const d1 = extractDecendants('A',g)
|
||||
const d2 = extractDecendants('B',g)
|
||||
const d3 = extractDecendants('C',g)
|
||||
|
||||
expect(d1).toEqual(['a']);
|
||||
expect(d2).toEqual(['b']);
|
||||
expect(d3).toEqual(['c']);
|
||||
});
|
||||
});
|
||||
555
src/dagre-wrapper/nodes.js
Normal file
@@ -0,0 +1,555 @@
|
||||
import intersect from './intersect/index.js';
|
||||
import { select } from 'd3';
|
||||
import { logger } from '../logger'; // eslint-disable-line
|
||||
import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util';
|
||||
import createLabel from './createLabel';
|
||||
import note from './shapes/note';
|
||||
|
||||
const question = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const s = (w + h) * 0.9;
|
||||
const points = [
|
||||
{ x: s / 2, y: 0 },
|
||||
{ x: s, y: -s / 2 },
|
||||
{ x: s / 2, y: -s },
|
||||
{ x: 0, y: -s / 2 }
|
||||
];
|
||||
|
||||
const questionElem = insertPolygonShape(shapeSvg, s, s, points);
|
||||
updateNodeBounds(node, questionElem);
|
||||
node.intersect = function(point) {
|
||||
return intersect.polugon(node, points, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const hexagon = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const f = 4;
|
||||
const h = bbox.height + node.padding;
|
||||
const m = h / f;
|
||||
const w = bbox.width + 2 * m + node.padding;
|
||||
const points = [
|
||||
{ x: m, y: 0 },
|
||||
{ x: w - m, y: 0 },
|
||||
{ x: w, y: -h / 2 },
|
||||
{ x: w - m, y: -h },
|
||||
{ x: m, y: -h },
|
||||
{ x: 0, y: -h / 2 }
|
||||
];
|
||||
const hex = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, hex);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const rect_left_inv_arrow = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const points = [
|
||||
{ x: -h / 2, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w, y: -h },
|
||||
{ x: -h / 2, y: -h },
|
||||
{ x: 0, y: -h / 2 }
|
||||
];
|
||||
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
const lean_right = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
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 }
|
||||
];
|
||||
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const lean_left = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
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 }
|
||||
];
|
||||
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const trapezoid = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const points = [
|
||||
{ x: (-2 * h) / 6, y: 0 },
|
||||
{ x: w + (2 * h) / 6, y: 0 },
|
||||
{ x: w - h / 6, y: -h },
|
||||
{ x: h / 6, y: -h }
|
||||
];
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const inv_trapezoid = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
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 }
|
||||
];
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
const rect_right_inv_arrow = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const points = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w + h / 2, y: 0 },
|
||||
{ x: w, y: -h / 2 },
|
||||
{ x: w + h / 2, y: -h },
|
||||
{ x: 0, y: -h }
|
||||
];
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
const cylinder = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, 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;
|
||||
|
||||
const shape =
|
||||
'M 0,' +
|
||||
ry +
|
||||
' a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
w +
|
||||
' 0 a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
-w +
|
||||
' 0 l 0,' +
|
||||
h +
|
||||
' a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
w +
|
||||
' 0 l 0,' +
|
||||
-h;
|
||||
|
||||
const el = shapeSvg
|
||||
.attr('label-offset-y', ry)
|
||||
.insert('path', ':first-child')
|
||||
.attr('d', shape)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2 + ry) + ')');
|
||||
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
const pos = intersect.rect(node, point);
|
||||
const x = pos.x - node.x;
|
||||
|
||||
if (
|
||||
rx != 0 &&
|
||||
(Math.abs(x) < node.width / 2 ||
|
||||
(Math.abs(x) == node.width / 2 && Math.abs(pos.y - node.y) > node.height / 2 - ry))
|
||||
) {
|
||||
// ellipsis equation: x*x / a*a + y*y / b*b = 1
|
||||
// solve for y to get adjustion value for pos.y
|
||||
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) y = -y;
|
||||
|
||||
pos.y += y;
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const rect = (parent, node) => {
|
||||
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
|
||||
|
||||
logger.trace('Classes = ', node.classes);
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
rect
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('rx', node.rx)
|
||||
.attr('ry', node.ry)
|
||||
.attr('x', -bbox.width / 2 - halfPadding)
|
||||
.attr('y', -bbox.height / 2 - halfPadding)
|
||||
.attr('width', bbox.width + node.padding)
|
||||
.attr('height', bbox.height + node.padding);
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
const rectWithTitle = (parent, node) => {
|
||||
// const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
|
||||
|
||||
let classes;
|
||||
if (!node.classes) {
|
||||
classes = 'node default';
|
||||
} else {
|
||||
classes = 'node ' + node.classes;
|
||||
}
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', classes)
|
||||
.attr('id', node.id);
|
||||
|
||||
// Create the title label and insert it after the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
// const innerRect = shapeSvg.insert('rect');
|
||||
const innerLine = shapeSvg.insert('line');
|
||||
|
||||
const label = shapeSvg.insert('g').attr('class', 'label');
|
||||
|
||||
const text2 = node.labelText.flat();
|
||||
logger.info('Label text', text2[0]);
|
||||
|
||||
const text = label.node().appendChild(createLabel(text2[0], node.labelStyle, true));
|
||||
const textRows = text2.slice(1, text2.length);
|
||||
let titleBox = text.getBBox();
|
||||
const descr = label
|
||||
.node()
|
||||
.appendChild(createLabel(textRows.join('<br/>'), node.labelStyle, true));
|
||||
|
||||
logger.info(descr);
|
||||
const halfPadding = node.padding / 2;
|
||||
select(descr).attr('transform', 'translate( 0' + ', ' + (titleBox.height + halfPadding) + ')');
|
||||
// Get the size of the label
|
||||
|
||||
// Bounding box for title and text
|
||||
const bbox = label.node().getBBox();
|
||||
|
||||
// Center the label
|
||||
label.attr(
|
||||
'transform',
|
||||
'translate(' + -bbox.width / 2 + ', ' + (-bbox.height / 2 - halfPadding + 3) + ')'
|
||||
);
|
||||
|
||||
rect
|
||||
.attr('class', 'outer title-state')
|
||||
.attr('x', -bbox.width / 2 - halfPadding)
|
||||
.attr('y', -bbox.height / 2 - halfPadding)
|
||||
.attr('width', bbox.width + node.padding)
|
||||
.attr('height', bbox.height + node.padding);
|
||||
|
||||
innerLine
|
||||
.attr('class', 'divider')
|
||||
.attr('x1', -bbox.width / 2 - halfPadding)
|
||||
.attr('x2', bbox.width / 2 + halfPadding)
|
||||
.attr('y1', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding)
|
||||
.attr('y2', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding);
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const stadium = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const h = bbox.height + node.padding;
|
||||
const w = bbox.width + h / 4 + node.padding;
|
||||
|
||||
// add the rect
|
||||
const rect = shapeSvg
|
||||
.insert('rect', ':first-child')
|
||||
.attr('rx', h / 2)
|
||||
.attr('ry', h / 2)
|
||||
.attr('x', -w / 2)
|
||||
.attr('y', -h / 2)
|
||||
.attr('width', w)
|
||||
.attr('height', h);
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
const circle = (parent, node) => {
|
||||
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node);
|
||||
const circle = shapeSvg.insert('circle', ':first-child');
|
||||
|
||||
// center the circle around its coordinate
|
||||
circle
|
||||
.attr('rx', node.rx)
|
||||
.attr('ry', node.ry)
|
||||
.attr('r', bbox.width / 2 + halfPadding)
|
||||
.attr('width', bbox.width + node.padding)
|
||||
.attr('height', bbox.height + node.padding);
|
||||
|
||||
updateNodeBounds(node, circle);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.circle(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const subroutine = (parent, node) => {
|
||||
const { shapeSvg, bbox } = labelHelper(parent, node);
|
||||
|
||||
const w = bbox.width + node.padding;
|
||||
const h = bbox.height + node.padding;
|
||||
const points = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w, y: -h },
|
||||
{ x: 0, y: -h },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: -8, y: 0 },
|
||||
{ x: w + 8, y: 0 },
|
||||
{ x: w + 8, y: -h },
|
||||
{ x: -8, y: -h },
|
||||
{ x: -8, y: 0 }
|
||||
];
|
||||
const el = insertPolygonShape(shapeSvg, w, h, points);
|
||||
updateNodeBounds(node, el);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.polygon(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const start = (parent, node) => {
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'node default')
|
||||
.attr('id', node.id);
|
||||
const circle = shapeSvg.insert('circle', ':first-child');
|
||||
|
||||
// center the circle around its coordinate
|
||||
circle
|
||||
.attr('class', 'state-start')
|
||||
.attr('r', 7)
|
||||
.attr('width', 14)
|
||||
.attr('height', 14);
|
||||
|
||||
updateNodeBounds(node, circle);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.circle(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const forkJoin = (parent, node, dir) => {
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'node default')
|
||||
.attr('id', node.id);
|
||||
|
||||
let width = 70;
|
||||
let height = 10;
|
||||
|
||||
if (dir === 'LR') {
|
||||
width = 10;
|
||||
height = 70;
|
||||
}
|
||||
|
||||
const shape = shapeSvg
|
||||
.append('rect')
|
||||
.style('stroke', 'black')
|
||||
.style('fill', 'black')
|
||||
.attr('x', (-1 * width) / 2)
|
||||
.attr('y', (-1 * height) / 2)
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('class', 'fork-join');
|
||||
|
||||
updateNodeBounds(node, shape);
|
||||
node.height = node.height + node.padding / 2;
|
||||
node.width = node.width + node.padding / 2;
|
||||
node.intersect = function(point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const end = (parent, node) => {
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', 'node default')
|
||||
.attr('id', node.id);
|
||||
const innerCircle = shapeSvg.insert('circle', ':first-child');
|
||||
const circle = shapeSvg.insert('circle', ':first-child');
|
||||
|
||||
circle
|
||||
.attr('class', 'state-start')
|
||||
.attr('r', 7)
|
||||
.attr('width', 14)
|
||||
.attr('height', 14);
|
||||
|
||||
innerCircle
|
||||
.attr('class', 'state-end')
|
||||
.attr('r', 5)
|
||||
.attr('width', 10)
|
||||
.attr('height', 10);
|
||||
|
||||
updateNodeBounds(node, circle);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.circle(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
const shapes = {
|
||||
question,
|
||||
rect,
|
||||
rectWithTitle,
|
||||
circle,
|
||||
stadium,
|
||||
hexagon,
|
||||
rect_left_inv_arrow,
|
||||
lean_right,
|
||||
lean_left,
|
||||
trapezoid,
|
||||
inv_trapezoid,
|
||||
rect_right_inv_arrow,
|
||||
cylinder,
|
||||
start,
|
||||
end,
|
||||
note,
|
||||
subroutine,
|
||||
fork: forkJoin,
|
||||
join: forkJoin
|
||||
};
|
||||
|
||||
let nodeElems = {};
|
||||
|
||||
export const insertNode = (elem, node, dir) => {
|
||||
nodeElems[node.id] = shapes[node.shape](elem, node, dir);
|
||||
};
|
||||
export const setNodeElem = (elem, node) => {
|
||||
nodeElems[node.id] = elem;
|
||||
};
|
||||
export const clear = () => {
|
||||
nodeElems = {};
|
||||
};
|
||||
|
||||
export const positionNode = node => {
|
||||
const el = nodeElems[node.id];
|
||||
logger.trace(
|
||||
'Transforming node',
|
||||
node,
|
||||
'translate(' + (node.x - node.width / 2 - 5) + ', ' + (node.y - node.height / 2 - 5) + ')'
|
||||
);
|
||||
const padding = 8;
|
||||
if (node.clusterNode) {
|
||||
el.attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
(node.x - node.width / 2 - padding) +
|
||||
', ' +
|
||||
(node.y - node.height / 2 - padding) +
|
||||
')'
|
||||
);
|
||||
} else {
|
||||
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||
}
|
||||
};
|
||||
54
src/dagre-wrapper/patterns.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*/
|
||||
|
||||
// import { logger } from '../logger';
|
||||
|
||||
// Only add the number of markers that the diagram needs
|
||||
const insertPatterns = (elem, patternArray, type, id) => {
|
||||
patternArray.forEach(patternName => {
|
||||
patterns[patternName](elem, type, id);
|
||||
});
|
||||
};
|
||||
|
||||
{
|
||||
/* <svg height="10" width="10" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
{' '}
|
||||
<defs>
|
||||
{' '}
|
||||
<pattern id="circles-1" patternUnits="userSpaceOnUse" width="10" height="10">
|
||||
{' '}
|
||||
<image
|
||||
xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+CiAgPHJlY3Qgd2lkdGg9JzEwJyBoZWlnaHQ9JzEwJyBmaWxsPSJ3aGl0ZSIgLz4KICA8Y2lyY2xlIGN4PSIxIiBjeT0iMSIgcj0iMSIgZmlsbD0iYmxhY2siLz4KPC9zdmc+"
|
||||
x="0"
|
||||
y="0"
|
||||
width="10"
|
||||
height="10"
|
||||
>
|
||||
{' '}
|
||||
</image>{' '}
|
||||
</pattern>{' '}
|
||||
</defs>{' '}
|
||||
</svg>; */
|
||||
}
|
||||
|
||||
const dots = (elem, type) => {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-barbEnd')
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 14)
|
||||
.attr('markerUnits', 0)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
// TODO rename the class diagram markers to something shape descriptive and semanitc free
|
||||
const patterns = {
|
||||
dots
|
||||
};
|
||||
export default insertPatterns;
|
||||
29
src/dagre-wrapper/shapes/note.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { updateNodeBounds, labelHelper } from './util';
|
||||
import { logger } from '../../logger'; // eslint-disable-line
|
||||
import intersect from '../intersect/index.js';
|
||||
|
||||
const note = (parent, node) => {
|
||||
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
|
||||
|
||||
logger.info('Classes = ', node.classes);
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
rect
|
||||
.attr('rx', node.rx)
|
||||
.attr('ry', node.ry)
|
||||
.attr('x', -bbox.width / 2 - halfPadding)
|
||||
.attr('y', -bbox.height / 2 - halfPadding)
|
||||
.attr('width', bbox.width + node.padding)
|
||||
.attr('height', bbox.height + node.padding);
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return intersect.rect(node, point);
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
export default note;
|
||||
51
src/dagre-wrapper/shapes/util.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import createLabel from '../createLabel';
|
||||
|
||||
export const labelHelper = (parent, node, _classes) => {
|
||||
let classes;
|
||||
if (!_classes) {
|
||||
classes = 'node default';
|
||||
} else {
|
||||
classes = _classes;
|
||||
}
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
.insert('g')
|
||||
.attr('class', classes)
|
||||
.attr('id', node.id);
|
||||
|
||||
// Create the label and insert it after the rect
|
||||
const label = shapeSvg.insert('g').attr('class', 'label');
|
||||
|
||||
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
|
||||
|
||||
// Get the size of the label
|
||||
const bbox = text.getBBox();
|
||||
|
||||
const halfPadding = node.padding / 2;
|
||||
|
||||
// Center the label
|
||||
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||
|
||||
return { shapeSvg, bbox, halfPadding, label };
|
||||
};
|
||||
|
||||
export const updateNodeBounds = (node, element) => {
|
||||
const bbox = element.node().getBBox();
|
||||
node.width = bbox.width;
|
||||
node.height = bbox.height;
|
||||
};
|
||||
|
||||
export function insertPolygonShape(parent, w, h, points) {
|
||||
return parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('class', 'label-container')
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')');
|
||||
}
|
||||
@@ -1,7 +1,31 @@
|
||||
import { select } from 'd3';
|
||||
import { logger } from '../../logger';
|
||||
import { getConfig } from '../../config';
|
||||
import common from '../common/common';
|
||||
import utils from '../../utils';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'classid-';
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
let relations = [];
|
||||
let classes = {};
|
||||
let classCounter = 0;
|
||||
|
||||
let funs = [];
|
||||
|
||||
const splitClassNameAndType = function(id) {
|
||||
let genericType = '';
|
||||
let className = id;
|
||||
|
||||
if (id.indexOf('~') > 0) {
|
||||
let split = id.split('~');
|
||||
className = split[0];
|
||||
genericType = split[1];
|
||||
}
|
||||
|
||||
return { className: className, type: genericType };
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a node definition has been found.
|
||||
@@ -9,20 +33,41 @@ let classes = {};
|
||||
* @public
|
||||
*/
|
||||
export const addClass = function(id) {
|
||||
let classId = splitClassNameAndType(id);
|
||||
// Only add class if not exists
|
||||
if (typeof classes[id] !== 'undefined') return;
|
||||
if (typeof classes[classId.className] !== 'undefined') return;
|
||||
|
||||
classes[id] = {
|
||||
id: id,
|
||||
classes[classId.className] = {
|
||||
id: classId.className,
|
||||
type: classId.type,
|
||||
cssClasses: [],
|
||||
methods: [],
|
||||
members: [],
|
||||
annotations: []
|
||||
annotations: [],
|
||||
domId: MERMAID_DOM_ID_PREFIX + classId.className + '-' + classCounter
|
||||
};
|
||||
classCounter++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to lookup domId from id in the graph definition.
|
||||
* @param id
|
||||
* @public
|
||||
*/
|
||||
export const lookUpDomId = function(id) {
|
||||
const classKeys = Object.keys(classes);
|
||||
for (let i = 0; i < classKeys.length; i++) {
|
||||
if (classes[classKeys[i]].id === id) {
|
||||
return classes[classKeys[i]].domId;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const clear = function() {
|
||||
relations = [];
|
||||
classes = {};
|
||||
funs = [];
|
||||
funs.push(setupToolTips);
|
||||
};
|
||||
|
||||
export const getClass = function(id) {
|
||||
@@ -40,6 +85,10 @@ export const addRelation = function(relation) {
|
||||
logger.debug('Adding relation: ' + JSON.stringify(relation));
|
||||
addClass(relation.id1);
|
||||
addClass(relation.id2);
|
||||
|
||||
relation.id1 = splitClassNameAndType(relation.id1).className;
|
||||
relation.id2 = splitClassNameAndType(relation.id2).className;
|
||||
|
||||
relations.push(relation);
|
||||
};
|
||||
|
||||
@@ -51,7 +100,8 @@ export const addRelation = function(relation) {
|
||||
* @public
|
||||
*/
|
||||
export const addAnnotation = function(className, annotation) {
|
||||
classes[className].annotations.push(annotation);
|
||||
const validatedClassName = splitClassNameAndType(className).className;
|
||||
classes[validatedClassName].annotations.push(annotation);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,7 +114,9 @@ export const addAnnotation = function(className, annotation) {
|
||||
* @public
|
||||
*/
|
||||
export const addMember = function(className, member) {
|
||||
const theClass = classes[className];
|
||||
const validatedClassName = splitClassNameAndType(className).className;
|
||||
const theClass = classes[validatedClassName];
|
||||
|
||||
if (typeof member === 'string') {
|
||||
// Member can contain white spaces, we trim them out
|
||||
const memberString = member.trim();
|
||||
@@ -72,7 +124,7 @@ export const addMember = function(className, member) {
|
||||
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
|
||||
// Remove leading and trailing brackets
|
||||
theClass.annotations.push(memberString.substring(2, memberString.length - 2));
|
||||
} else if (memberString.endsWith(')')) {
|
||||
} else if (memberString.indexOf(')') > 0) {
|
||||
theClass.methods.push(memberString);
|
||||
} else if (memberString) {
|
||||
theClass.members.push(memberString);
|
||||
@@ -89,12 +141,97 @@ export const addMembers = function(className, members) {
|
||||
|
||||
export const cleanupLabel = function(label) {
|
||||
if (label.substring(0, 1) === ':') {
|
||||
return label.substr(2).trim();
|
||||
return label.substr(1).trim();
|
||||
} else {
|
||||
return label.trim();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a special node is found, e.g. a clickable element.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param className Class to add
|
||||
*/
|
||||
export const setCssClass = function(ids, className) {
|
||||
ids.split(',').forEach(function(_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
if (typeof classes[id] !== 'undefined') {
|
||||
classes[id].cssClasses.push(className);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param linkStr URL to create a link for
|
||||
* @param tooltip Tooltip for the clickable element
|
||||
*/
|
||||
export const setLink = function(ids, linkStr, tooltip) {
|
||||
ids.split(',').forEach(function(_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
if (typeof classes[id] !== 'undefined') {
|
||||
classes[id].link = utils.formatUrl(linkStr, config);
|
||||
|
||||
if (tooltip) {
|
||||
classes[id].tooltip = common.sanitizeText(tooltip, config);
|
||||
}
|
||||
}
|
||||
});
|
||||
setCssClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a click definition is found. Registers an event handler.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param functionName Function to be called on click
|
||||
* @param tooltip Tooltip for the clickable element
|
||||
*/
|
||||
export const setClickEvent = function(ids, functionName, tooltip) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
setClickFunc(id, functionName, tooltip);
|
||||
});
|
||||
setCssClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
const setClickFunc = function(domId, functionName, tooltip) {
|
||||
let id = domId;
|
||||
let elemId = lookUpDomId(id);
|
||||
|
||||
if (config.securityLevel !== 'loose') {
|
||||
return;
|
||||
}
|
||||
if (typeof functionName === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (typeof classes[id] !== 'undefined') {
|
||||
if (tooltip) {
|
||||
classes[id].tooltip = common.sanitizeText(tooltip, config);
|
||||
}
|
||||
|
||||
funs.push(function() {
|
||||
const elem = document.querySelector(`[id="${elemId}"]`);
|
||||
if (elem !== null) {
|
||||
elem.addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
window[functionName](elemId);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const bindFunctions = function(element) {
|
||||
funs.forEach(function(fun) {
|
||||
fun(element);
|
||||
});
|
||||
};
|
||||
|
||||
export const lineType = {
|
||||
LINE: 0,
|
||||
DOTTED_LINE: 1
|
||||
@@ -107,8 +244,52 @@ export const relationType = {
|
||||
DEPENDENCY: 3
|
||||
};
|
||||
|
||||
const setupToolTips = function(element) {
|
||||
let tooltipElem = select('.mermaidTooltip');
|
||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
||||
tooltipElem = select('body')
|
||||
.append('div')
|
||||
.attr('class', 'mermaidTooltip')
|
||||
.style('opacity', 0);
|
||||
}
|
||||
|
||||
const svg = select(element).select('svg');
|
||||
|
||||
const nodes = svg.selectAll('g.node');
|
||||
nodes
|
||||
.on('mouseover', function() {
|
||||
const el = select(this);
|
||||
const title = el.attr('title');
|
||||
// Dont try to draw a tooltip if no data is provided
|
||||
if (title === null) {
|
||||
return;
|
||||
}
|
||||
const rect = this.getBoundingClientRect();
|
||||
|
||||
tooltipElem
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('opacity', '.9');
|
||||
tooltipElem
|
||||
.html(el.attr('title'))
|
||||
.style('left', rect.left + (rect.right - rect.left) / 2 + 'px')
|
||||
.style('top', rect.top - 14 + document.body.scrollTop + 'px');
|
||||
el.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
tooltipElem
|
||||
.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 0);
|
||||
const el = select(this);
|
||||
el.classed('hover', false);
|
||||
});
|
||||
};
|
||||
funs.push(setupToolTips);
|
||||
|
||||
export default {
|
||||
addClass,
|
||||
bindFunctions,
|
||||
clear,
|
||||
getClass,
|
||||
getClasses,
|
||||
@@ -119,5 +300,9 @@ export default {
|
||||
addMembers,
|
||||
cleanupLabel,
|
||||
lineType,
|
||||
relationType
|
||||
relationType,
|
||||
setClickEvent,
|
||||
setCssClass,
|
||||
setLink,
|
||||
lookUpDomId
|
||||
};
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import { parser } from './parser/classDiagram';
|
||||
import classDb from './classDb';
|
||||
|
||||
describe('class diagram, ', function() {
|
||||
describe('when parsing an info graph it', function() {
|
||||
beforeEach(function() {
|
||||
describe('class diagram, ', function () {
|
||||
describe('when parsing an info graph it', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
});
|
||||
|
||||
it('should handle relation definitions', function() {
|
||||
it('should handle relation definitions', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class01 <|-- Class02\n' +
|
||||
@@ -19,7 +19,8 @@ describe('class diagram, ', function() {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
it('should handle relation definition of different types and directions', function() {
|
||||
|
||||
it('should handle relation definition of different types and directions', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class11 <|.. Class12\n' +
|
||||
@@ -31,7 +32,7 @@ describe('class diagram, ', function() {
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle cardinality and labels', function() {
|
||||
it('should handle cardinality and labels', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class01 "1" *-- "many" Class02 : contains\n' +
|
||||
@@ -40,6 +41,90 @@ describe('class diagram, ', function() {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle visibility for methods and members', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class TestClass\n' +
|
||||
'TestClass : -int privateMember\n' +
|
||||
'TestClass : +int publicMember\n' +
|
||||
'TestClass : #int protectedMember\n' +
|
||||
'TestClass : -privateMethod()\n' +
|
||||
'TestClass : +publicMethod()\n' +
|
||||
'TestClass : #protectedMethod()\n';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle generic class', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Car~T~\n' +
|
||||
'Driver -- Car : drives >\n' +
|
||||
'Car *-- Wheel : have 4 >\n' +
|
||||
'Car -- Person : < owns';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class~T~ {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Dummy_Class {\n' +
|
||||
'class Flight {\n' +
|
||||
' flightNumber : Integer\n' +
|
||||
' departureTime : Date\n' +
|
||||
'}';
|
||||
let testPased =false;
|
||||
try{
|
||||
parser.parse(str);
|
||||
}catch (error){
|
||||
console.log(error.name);
|
||||
testPased = true;
|
||||
}
|
||||
expect(testPased).toBe(true);
|
||||
});
|
||||
|
||||
it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class~T~ {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Dummy_Class {\n';
|
||||
let testPased =false;
|
||||
try{
|
||||
parser.parse(str);
|
||||
}catch (error){
|
||||
console.log(error.name);
|
||||
testPased = true;
|
||||
}
|
||||
expect(testPased).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle generic class with brackets', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class~T~ {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Flight {\n' +
|
||||
' flightNumber : Integer\n' +
|
||||
' departureTime : Date\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle class definitions', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
@@ -51,7 +136,7 @@ describe('class diagram, ', function() {
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle method statements', function() {
|
||||
it('should handle method statements', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Object <|-- ArrayList\n' +
|
||||
@@ -61,7 +146,8 @@ describe('class diagram, ', function() {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
it('should handle parsing of method statements grouped by brackets', function() {
|
||||
|
||||
it('should handle parsing of method statements grouped by brackets', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class {\n' +
|
||||
@@ -77,7 +163,37 @@ describe('class diagram, ', function() {
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle parsing of separators', function() {
|
||||
it('should handle return types on methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Object <|-- ArrayList\n' +
|
||||
'Object : equals()\n' +
|
||||
'Object : -Object[] objects\n' +
|
||||
'Object : +getObjects() Object[]\n' +
|
||||
'ArrayList : Dummy elementData\n' +
|
||||
'ArrayList : getDummy() Dummy';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle return types on methods grouped by brackets', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class {\n' +
|
||||
'string data\n' +
|
||||
'getDummy() Dummy\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Flight {\n' +
|
||||
' int flightNumber\n' +
|
||||
' datetime departureTime\n' +
|
||||
' getDepartureTime() datetime\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle parsing of separators', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Foo1 {\n' +
|
||||
@@ -109,14 +225,205 @@ describe('class diagram, ', function() {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle a comment', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle comments at the start', function () {
|
||||
const str =
|
||||
'%% Comment\n' +
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle comments at the end', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'\n}' +
|
||||
'%% Comment\n';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle comments at the end no trailing newline', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'%% Comment';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle a comment with multiple line feeds', function () {
|
||||
const str =
|
||||
'classDiagram\n\n\n' +
|
||||
'%% Comment\n\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle a comment with mermaid class diagram code in them', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle a comment inside brackets', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with link', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'link Class01 "google.com" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with link and tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'link Class01 "google.com" "A Tooltip" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with callback', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'callback Class01 "functionCall" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with callback and tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'callback Class01 "functionCall" "A Tooltip" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle dashed relation definition of different types and directions', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class11 <|.. Class12\n' +
|
||||
'Class13 <.. Class14\n' +
|
||||
'Class15 ..|> Class16\n' +
|
||||
'Class17 ..> Class18\n' +
|
||||
'Class19 .. Class20';
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle generic types in members', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Car~T~\n' +
|
||||
'Car : -List~Wheel~ wheels\n' +
|
||||
'Car : +setWheels(List~Wheel~ wheels)\n' +
|
||||
'Car : +getWheels() List~Wheel~';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle generic types in members in class with brackets', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Car {\n' +
|
||||
'List~Wheel~ wheels\n' +
|
||||
'setWheels(List~Wheel~ wheels)\n' +
|
||||
'+getWheels() List~Wheel~\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when fetching data from an classDiagram graph it', function() {
|
||||
beforeEach(function() {
|
||||
describe('when fetching data from a classDiagram graph it', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
parser.yy.clear();
|
||||
});
|
||||
it('should handle relation definitions EXTENSION', function() {
|
||||
it('should handle relation definitions EXTENSION', function () {
|
||||
const str = 'classDiagram\n' + 'Class01 <|-- Class02';
|
||||
|
||||
parser.parse(str);
|
||||
@@ -129,7 +436,8 @@ describe('class diagram, ', function() {
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
it('should handle relation definitions AGGREGATION and dotted line', function() {
|
||||
|
||||
it('should handle relation definitions AGGREGATION and dotted line', function () {
|
||||
const str = 'classDiagram\n' + 'Class01 o.. Class02';
|
||||
|
||||
parser.parse(str);
|
||||
@@ -142,7 +450,8 @@ describe('class diagram, ', function() {
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
|
||||
});
|
||||
it('should handle relation definitions COMPOSITION on both sides', function() {
|
||||
|
||||
it('should handle relation definitions COMPOSITION on both sides', function () {
|
||||
const str = 'classDiagram\n' + 'Class01 *--* Class02';
|
||||
|
||||
parser.parse(str);
|
||||
@@ -155,7 +464,8 @@ describe('class diagram, ', function() {
|
||||
expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION);
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
it('should handle relation definitions no types', function() {
|
||||
|
||||
it('should handle relation definitions no types', function () {
|
||||
const str = 'classDiagram\n' + 'Class01 -- Class02';
|
||||
|
||||
parser.parse(str);
|
||||
@@ -168,7 +478,8 @@ describe('class diagram, ', function() {
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
it('should handle relation definitions with type only on right side', function() {
|
||||
|
||||
it('should handle relation definitions with type only on right side', function () {
|
||||
const str = 'classDiagram\n' + 'Class01 --|> Class02';
|
||||
|
||||
parser.parse(str);
|
||||
@@ -182,7 +493,7 @@ describe('class diagram, ', function() {
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
|
||||
it('should handle multiple classes and relation definitions', function() {
|
||||
it('should handle multiple classes and relation definitions', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class01 <|-- Class02\n' +
|
||||
@@ -208,7 +519,22 @@ describe('class diagram, ', function() {
|
||||
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
|
||||
});
|
||||
|
||||
it('should handle class annotations', function() {
|
||||
it('should handle generic class with relation definitions', function () {
|
||||
const str = 'classDiagram\n' + 'Class01~T~ <|-- Class02';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class01').type).toBe('T');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION);
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
|
||||
it('should handle class annotations', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + '<<interface>> Class1';
|
||||
parser.parse(str);
|
||||
|
||||
@@ -219,7 +545,7 @@ describe('class diagram, ', function() {
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations with members and methods', function() {
|
||||
it('should handle class annotations with members and methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
@@ -235,7 +561,7 @@ describe('class diagram, ', function() {
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations in brackets', function() {
|
||||
it('should handle class annotations in brackets', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1 {\n' + '<<interface>>\n' + '}';
|
||||
parser.parse(str);
|
||||
|
||||
@@ -246,7 +572,7 @@ describe('class diagram, ', function() {
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations in brackets with members and methods', function() {
|
||||
it('should handle class annotations in brackets with members and methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
@@ -263,7 +589,7 @@ describe('class diagram, ', function() {
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should add bracket members in right order', function() {
|
||||
it('should add bracket members in right order', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
@@ -282,5 +608,64 @@ describe('class diagram, ', function() {
|
||||
expect(testClass.methods[0]).toBe('test()');
|
||||
expect(testClass.methods[1]).toBe('foo()');
|
||||
});
|
||||
|
||||
it('should handle abstract methods', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()*');
|
||||
});
|
||||
|
||||
it('should handle static methods', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()$');
|
||||
});
|
||||
|
||||
it('should associate link and css appropriately', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('about:blank');//('google.com'); security needs to be set to 'loose' for this to work right
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate link with tooltip', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('about:blank');//('google.com'); security needs to be set to 'loose' for this to work right
|
||||
expect(testClass.tooltip).toBe('A tooltip');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate callback appropriately', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'callback Class1 "functionCall"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall', undefined);
|
||||
});
|
||||
|
||||
it('should associate callback with tooltip', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'callback Class1 "functionCall" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall', 'A tooltip');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import * as d3 from 'd3';
|
||||
import { select } from 'd3';
|
||||
import dagre from 'dagre';
|
||||
import graphlib from 'graphlib';
|
||||
import { logger } from '../../logger';
|
||||
import classDb from './classDb';
|
||||
import utils from '../../utils';
|
||||
import classDb, { lookUpDomId } from './classDb';
|
||||
import { parser } from './parser/classDiagram';
|
||||
import svgDraw from './svgDraw';
|
||||
|
||||
parser.yy = classDb;
|
||||
|
||||
const idCache = {};
|
||||
let idCache = {};
|
||||
const padding = 20;
|
||||
|
||||
let classCnt = 0;
|
||||
const conf = {
|
||||
dividerMargin: 10,
|
||||
padding: 5,
|
||||
@@ -135,269 +135,6 @@ const insertMarkers = function(elem) {
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
let edgeCount = 0;
|
||||
let total = 0;
|
||||
const drawEdge = function(elem, path, relation) {
|
||||
const getRelationType = function(type) {
|
||||
switch (type) {
|
||||
case classDb.relationType.AGGREGATION:
|
||||
return 'aggregation';
|
||||
case classDb.relationType.EXTENSION:
|
||||
return 'extension';
|
||||
case classDb.relationType.COMPOSITION:
|
||||
return 'composition';
|
||||
case classDb.relationType.DEPENDENCY:
|
||||
return 'dependency';
|
||||
}
|
||||
};
|
||||
|
||||
path.points = path.points.filter(p => !Number.isNaN(p.y));
|
||||
|
||||
// The data for our line
|
||||
const lineData = path.points;
|
||||
|
||||
// This is the accessor function we talked about above
|
||||
const lineFunction = d3
|
||||
.line()
|
||||
.x(function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.curve(d3.curveBasis);
|
||||
|
||||
const svgPath = elem
|
||||
.append('path')
|
||||
.attr('d', lineFunction(lineData))
|
||||
.attr('id', 'edge' + edgeCount)
|
||||
.attr('class', 'relation');
|
||||
let url = '';
|
||||
if (conf.arrowMarkerAbsolute) {
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
if (relation.relation.type1 !== 'none') {
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')'
|
||||
);
|
||||
}
|
||||
if (relation.relation.type2 !== 'none') {
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')'
|
||||
);
|
||||
}
|
||||
|
||||
let x, y;
|
||||
const l = path.points.length;
|
||||
// Calculate Label position
|
||||
let labalPosition = utils.calcLabelPosition(path.points);
|
||||
x = labalPosition.x;
|
||||
y = labalPosition.y;
|
||||
|
||||
let p1_card_x, p1_card_y;
|
||||
// p1_card_padd_x = conf.padding * 2,
|
||||
// p1_card_padd_y = conf.padding;
|
||||
let p2_card_x, p2_card_y;
|
||||
// p2_card_padd_x = conf.padding * 2,
|
||||
// p2_card_padd_y = -conf.padding / 2;
|
||||
if (l % 2 !== 0 && l > 1) {
|
||||
let cardinality_1_point = utils.calcCardinalityPosition(
|
||||
relation.relation.type1 !== 'none',
|
||||
path.points,
|
||||
path.points[0]
|
||||
);
|
||||
let cardinality_2_point = utils.calcCardinalityPosition(
|
||||
relation.relation.type2 !== 'none',
|
||||
path.points,
|
||||
path.points[l - 1]
|
||||
);
|
||||
|
||||
logger.debug('cardinality_1_point ' + JSON.stringify(cardinality_1_point));
|
||||
logger.debug('cardinality_2_point ' + JSON.stringify(cardinality_2_point));
|
||||
|
||||
p1_card_x = cardinality_1_point.x;
|
||||
p1_card_y = cardinality_1_point.y;
|
||||
p2_card_x = cardinality_2_point.x;
|
||||
p2_card_y = cardinality_2_point.y;
|
||||
}
|
||||
|
||||
if (typeof relation.title !== 'undefined') {
|
||||
const g = elem.append('g').attr('class', 'classLabel');
|
||||
const label = g
|
||||
.append('text')
|
||||
.attr('class', 'label')
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('fill', 'red')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(relation.title);
|
||||
|
||||
window.label = label;
|
||||
const bounds = label.node().getBBox();
|
||||
|
||||
g.insert('rect', ':first-child')
|
||||
.attr('class', 'box')
|
||||
.attr('x', bounds.x - conf.padding / 2)
|
||||
.attr('y', bounds.y - conf.padding / 2)
|
||||
.attr('width', bounds.width + conf.padding)
|
||||
.attr('height', bounds.height + conf.padding);
|
||||
}
|
||||
|
||||
logger.info('Rendering relation ' + JSON.stringify(relation));
|
||||
if (typeof relation.relationTitle1 !== 'undefined' && relation.relationTitle1 !== 'none') {
|
||||
const g = elem.append('g').attr('class', 'cardinality');
|
||||
g.append('text')
|
||||
.attr('class', 'type1')
|
||||
.attr('x', p1_card_x)
|
||||
.attr('y', p1_card_y)
|
||||
.attr('fill', 'black')
|
||||
.attr('font-size', '6')
|
||||
.text(relation.relationTitle1);
|
||||
}
|
||||
if (typeof relation.relationTitle2 !== 'undefined' && relation.relationTitle2 !== 'none') {
|
||||
const g = elem.append('g').attr('class', 'cardinality');
|
||||
g.append('text')
|
||||
.attr('class', 'type2')
|
||||
.attr('x', p2_card_x)
|
||||
.attr('y', p2_card_y)
|
||||
.attr('fill', 'black')
|
||||
.attr('font-size', '6')
|
||||
.text(relation.relationTitle2);
|
||||
}
|
||||
|
||||
edgeCount++;
|
||||
};
|
||||
|
||||
const drawClass = function(elem, classDef) {
|
||||
logger.info('Rendering class ' + classDef);
|
||||
|
||||
const addTspan = function(textEl, txt, isFirst) {
|
||||
const tSpan = textEl
|
||||
.append('tspan')
|
||||
.attr('x', conf.padding)
|
||||
.text(txt);
|
||||
if (!isFirst) {
|
||||
tSpan.attr('dy', conf.textHeight);
|
||||
}
|
||||
};
|
||||
|
||||
const id = 'classId' + (classCnt % total);
|
||||
const classInfo = {
|
||||
id: id,
|
||||
label: classDef.id,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
// add class group
|
||||
const g = elem
|
||||
.append('g')
|
||||
.attr('id', id)
|
||||
.attr('class', 'classGroup');
|
||||
|
||||
// add title
|
||||
const title = g
|
||||
.append('text')
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.attr('x', 0);
|
||||
|
||||
// add annotations
|
||||
let isFirst = true;
|
||||
classDef.annotations.forEach(function(member) {
|
||||
const titleText2 = title.append('tspan').text('«' + member + '»');
|
||||
if (!isFirst) titleText2.attr('dy', conf.textHeight);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
// add class title
|
||||
const classTitle = title
|
||||
.append('tspan')
|
||||
.text(classDef.id)
|
||||
.attr('class', 'title');
|
||||
|
||||
// If class has annotations the title needs to have an offset of the text height
|
||||
if (!isFirst) classTitle.attr('dy', conf.textHeight);
|
||||
|
||||
const titleHeight = title.node().getBBox().height;
|
||||
|
||||
const membersLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
||||
|
||||
const members = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true;
|
||||
classDef.members.forEach(function(member) {
|
||||
addTspan(members, member, isFirst);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const membersBox = members.node().getBBox();
|
||||
|
||||
const methodsLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
|
||||
|
||||
const methods = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true;
|
||||
|
||||
classDef.methods.forEach(function(method) {
|
||||
addTspan(methods, method, isFirst);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const classBox = g.node().getBBox();
|
||||
const rect = g
|
||||
.insert('rect', ':first-child')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('width', classBox.width + 2 * conf.padding)
|
||||
.attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin);
|
||||
|
||||
const rectWidth = rect.node().getBBox().width;
|
||||
|
||||
// Center title
|
||||
// We subtract the width of each text element from the class box width and divide it by 2
|
||||
title.node().childNodes.forEach(function(x) {
|
||||
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
|
||||
});
|
||||
|
||||
membersLine.attr('x2', rectWidth);
|
||||
methodsLine.attr('x2', rectWidth);
|
||||
|
||||
classInfo.width = rectWidth;
|
||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
||||
|
||||
idCache[id] = classInfo;
|
||||
classCnt++;
|
||||
return classInfo;
|
||||
};
|
||||
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
@@ -405,19 +142,21 @@ export const setConf = function(cnf) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
export const draw = function(text, id) {
|
||||
idCache = {};
|
||||
parser.yy.clear();
|
||||
parser.parse(text);
|
||||
|
||||
logger.info('Rendering diagram ' + text);
|
||||
|
||||
/// / Fetch the default direction, use TD if none was found
|
||||
const diagram = d3.select(`[id='${id}']`);
|
||||
// Fetch the default direction, use TD if none was found
|
||||
const diagram = select(`[id='${id}']`);
|
||||
insertMarkers(diagram);
|
||||
|
||||
// Layout graph, Create a new directed graph
|
||||
@@ -437,14 +176,16 @@ export const draw = function(text, id) {
|
||||
|
||||
const classes = classDb.getClasses();
|
||||
const keys = Object.keys(classes);
|
||||
total = keys.length;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const classDef = classes[keys[i]];
|
||||
const node = drawClass(diagram, classDef);
|
||||
const node = svgDraw.drawClass(diagram, classDef, conf);
|
||||
idCache[node.id] = node;
|
||||
|
||||
// Add nodes to the graph. The first argument is the node id. The second is
|
||||
// metadata about the node. In this case we're going to add labels to each of
|
||||
// our nodes.
|
||||
g.setNode(node.id, node);
|
||||
|
||||
logger.info('Org height: ' + node.height);
|
||||
}
|
||||
|
||||
@@ -453,15 +194,21 @@ export const draw = function(text, id) {
|
||||
logger.info(
|
||||
'tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation)
|
||||
);
|
||||
g.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), {
|
||||
relation: relation
|
||||
});
|
||||
g.setEdge(
|
||||
getGraphId(relation.id1),
|
||||
getGraphId(relation.id2),
|
||||
{
|
||||
relation: relation
|
||||
},
|
||||
relation.title || 'DEFAULT'
|
||||
);
|
||||
});
|
||||
|
||||
dagre.layout(g);
|
||||
g.nodes().forEach(function(v) {
|
||||
if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') {
|
||||
logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)));
|
||||
d3.select('#' + v).attr(
|
||||
select('#' + lookUpDomId(v)).attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
(g.node(v).x - g.node(v).width / 2) +
|
||||
@@ -471,16 +218,30 @@ export const draw = function(text, id) {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
g.edges().forEach(function(e) {
|
||||
if (typeof e !== 'undefined' && typeof g.edge(e) !== 'undefined') {
|
||||
logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e)));
|
||||
drawEdge(diagram, g.edge(e), g.edge(e).relation);
|
||||
svgDraw.drawEdge(diagram, g.edge(e), g.edge(e).relation, conf);
|
||||
}
|
||||
});
|
||||
|
||||
diagram.attr('height', '100%');
|
||||
diagram.attr('width', `${g.graph().width * 1.5 + 20}`);
|
||||
diagram.attr('viewBox', '-10 -10 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20));
|
||||
const svgBounds = diagram.node().getBBox();
|
||||
const width = svgBounds.width + padding * 2;
|
||||
const height = svgBounds.height + padding * 2;
|
||||
|
||||
if (conf.useMaxWidth) {
|
||||
diagram.attr('width', '100%');
|
||||
diagram.attr('style', `max-width: ${width}px;`);
|
||||
} else {
|
||||
diagram.attr('height', height);
|
||||
diagram.attr('width', width);
|
||||
}
|
||||
|
||||
// Ensure the viewBox includes the whole svgBounds area with extra space for padding
|
||||
const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`;
|
||||
logger.debug(`viewBox ${vBox}`);
|
||||
diagram.attr('viewBox', vBox);
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
|
||||
/* lexical grammar */
|
||||
%lex
|
||||
%x string struct
|
||||
%x string generic struct
|
||||
|
||||
%%
|
||||
\%\%[^\n]* /* do nothing */
|
||||
\%\%[^\n]*\n* /* do nothing */
|
||||
\n+ return 'NEWLINE';
|
||||
\s+ /* skip whitespace */
|
||||
"classDiagram" return 'CLASS_DIAGRAM';
|
||||
[\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';}
|
||||
<struct><<EOF>> return "EOF_IN_STRUCT";
|
||||
<struct>[\{] return "OPEN_IN_STRUCT";
|
||||
<struct>\} { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
|
||||
<struct>[\n] /* nothing */
|
||||
<struct>[^\{\}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
|
||||
@@ -21,8 +23,14 @@
|
||||
|
||||
|
||||
"class" return 'CLASS';
|
||||
//"click" return 'CLICK';
|
||||
"callback" return 'CALLBACK';
|
||||
"link" return 'LINK';
|
||||
"<<" return 'ANNOTATION_START';
|
||||
">>" return 'ANNOTATION_END';
|
||||
[~] this.begin("generic");
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
@@ -36,7 +44,7 @@
|
||||
\s*o return 'AGGREGATION';
|
||||
\-\- return 'LINE';
|
||||
\.\. return 'DOTTED_LINE';
|
||||
":"[^#\n;]+ return 'LABEL';
|
||||
":"[^\n;]+ return 'LABEL';
|
||||
\- return 'MINUS';
|
||||
"." return 'DOT';
|
||||
\+ return 'PLUS';
|
||||
@@ -136,6 +144,8 @@ statements
|
||||
className
|
||||
: alphaNumToken className { $$=$1+$2; }
|
||||
| alphaNumToken { $$=$1; }
|
||||
| alphaNumToken GENERICTYPE className { $$=$1+'~'+$2+$3; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2; }
|
||||
;
|
||||
|
||||
statement
|
||||
@@ -144,6 +154,7 @@ statement
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| annotationStatement
|
||||
| clickStatement
|
||||
;
|
||||
|
||||
classStatement
|
||||
@@ -193,6 +204,13 @@ lineType
|
||||
| DOTTED_LINE {$$=yy.lineType.DOTTED_LINE;}
|
||||
;
|
||||
|
||||
clickStatement
|
||||
: CALLBACK className STR {$$ = $1;yy.setClickEvent($2, $3, undefined);}
|
||||
| CALLBACK className STR STR {$$ = $1;yy.setClickEvent($2, $3, $4);}
|
||||
| LINK className STR {$$ = $1;yy.setLink($2, $3, undefined);}
|
||||
| LINK className STR STR {$$ = $1;yy.setLink($2, $3, $4);}
|
||||
;
|
||||
|
||||
commentToken : textToken | graphCodeTokens ;
|
||||
|
||||
textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT;
|
||||
|
||||
412
src/diagrams/class/svgDraw.js
Normal file
@@ -0,0 +1,412 @@
|
||||
import { line, curveBasis } from 'd3';
|
||||
import { lookUpDomId, relationType } from './classDb';
|
||||
import utils from '../../utils';
|
||||
import { logger } from '../../logger';
|
||||
|
||||
let edgeCount = 0;
|
||||
export const drawEdge = function(elem, path, relation, conf) {
|
||||
const getRelationType = function(type) {
|
||||
switch (type) {
|
||||
case relationType.AGGREGATION:
|
||||
return 'aggregation';
|
||||
case relationType.EXTENSION:
|
||||
return 'extension';
|
||||
case relationType.COMPOSITION:
|
||||
return 'composition';
|
||||
case relationType.DEPENDENCY:
|
||||
return 'dependency';
|
||||
}
|
||||
};
|
||||
|
||||
path.points = path.points.filter(p => !Number.isNaN(p.y));
|
||||
|
||||
// The data for our line
|
||||
const lineData = path.points;
|
||||
|
||||
// This is the accessor function we talked about above
|
||||
const lineFunction = line()
|
||||
.x(function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.curve(curveBasis);
|
||||
|
||||
const svgPath = elem
|
||||
.append('path')
|
||||
.attr('d', lineFunction(lineData))
|
||||
.attr('id', 'edge' + edgeCount)
|
||||
.attr('class', 'relation');
|
||||
let url = '';
|
||||
if (conf.arrowMarkerAbsolute) {
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
if (relation.relation.lineType == 1) {
|
||||
svgPath.attr('class', 'relation dashed-line');
|
||||
}
|
||||
if (relation.relation.type1 !== 'none') {
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')'
|
||||
);
|
||||
}
|
||||
if (relation.relation.type2 !== 'none') {
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')'
|
||||
);
|
||||
}
|
||||
|
||||
let x, y;
|
||||
const l = path.points.length;
|
||||
// Calculate Label position
|
||||
let labelPosition = utils.calcLabelPosition(path.points);
|
||||
x = labelPosition.x;
|
||||
y = labelPosition.y;
|
||||
|
||||
let p1_card_x, p1_card_y;
|
||||
let p2_card_x, p2_card_y;
|
||||
|
||||
if (l % 2 !== 0 && l > 1) {
|
||||
let cardinality_1_point = utils.calcCardinalityPosition(
|
||||
relation.relation.type1 !== 'none',
|
||||
path.points,
|
||||
path.points[0]
|
||||
);
|
||||
let cardinality_2_point = utils.calcCardinalityPosition(
|
||||
relation.relation.type2 !== 'none',
|
||||
path.points,
|
||||
path.points[l - 1]
|
||||
);
|
||||
|
||||
logger.debug('cardinality_1_point ' + JSON.stringify(cardinality_1_point));
|
||||
logger.debug('cardinality_2_point ' + JSON.stringify(cardinality_2_point));
|
||||
|
||||
p1_card_x = cardinality_1_point.x;
|
||||
p1_card_y = cardinality_1_point.y;
|
||||
p2_card_x = cardinality_2_point.x;
|
||||
p2_card_y = cardinality_2_point.y;
|
||||
}
|
||||
|
||||
if (typeof relation.title !== 'undefined') {
|
||||
const g = elem.append('g').attr('class', 'classLabel');
|
||||
const label = g
|
||||
.append('text')
|
||||
.attr('class', 'label')
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('fill', 'red')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(relation.title);
|
||||
|
||||
window.label = label;
|
||||
const bounds = label.node().getBBox();
|
||||
|
||||
g.insert('rect', ':first-child')
|
||||
.attr('class', 'box')
|
||||
.attr('x', bounds.x - conf.padding / 2)
|
||||
.attr('y', bounds.y - conf.padding / 2)
|
||||
.attr('width', bounds.width + conf.padding)
|
||||
.attr('height', bounds.height + conf.padding);
|
||||
}
|
||||
|
||||
logger.info('Rendering relation ' + JSON.stringify(relation));
|
||||
if (typeof relation.relationTitle1 !== 'undefined' && relation.relationTitle1 !== 'none') {
|
||||
const g = elem.append('g').attr('class', 'cardinality');
|
||||
g.append('text')
|
||||
.attr('class', 'type1')
|
||||
.attr('x', p1_card_x)
|
||||
.attr('y', p1_card_y)
|
||||
.attr('fill', 'black')
|
||||
.attr('font-size', '6')
|
||||
.text(relation.relationTitle1);
|
||||
}
|
||||
if (typeof relation.relationTitle2 !== 'undefined' && relation.relationTitle2 !== 'none') {
|
||||
const g = elem.append('g').attr('class', 'cardinality');
|
||||
g.append('text')
|
||||
.attr('class', 'type2')
|
||||
.attr('x', p2_card_x)
|
||||
.attr('y', p2_card_y)
|
||||
.attr('fill', 'black')
|
||||
.attr('font-size', '6')
|
||||
.text(relation.relationTitle2);
|
||||
}
|
||||
|
||||
edgeCount++;
|
||||
};
|
||||
|
||||
export const drawClass = function(elem, classDef, conf) {
|
||||
logger.info('Rendering class ' + classDef);
|
||||
|
||||
let cssClassStr = 'classGroup ';
|
||||
if (classDef.cssClasses.length > 0) {
|
||||
cssClassStr = cssClassStr + classDef.cssClasses.join(' ');
|
||||
}
|
||||
|
||||
const id = classDef.id;
|
||||
const classInfo = {
|
||||
id: id,
|
||||
label: classDef.id,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
// add class group
|
||||
const g = elem
|
||||
.append('g')
|
||||
.attr('id', lookUpDomId(id))
|
||||
.attr('class', cssClassStr);
|
||||
|
||||
// add title
|
||||
let title;
|
||||
if (classDef.link) {
|
||||
title = g
|
||||
.append('svg:a')
|
||||
.attr('xlink:href', classDef.link)
|
||||
.attr('target', '_blank')
|
||||
.append('text')
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.attr('x', 0);
|
||||
} else {
|
||||
title = g
|
||||
.append('text')
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.attr('x', 0);
|
||||
}
|
||||
|
||||
// add annotations
|
||||
let isFirst = true;
|
||||
classDef.annotations.forEach(function(member) {
|
||||
const titleText2 = title.append('tspan').text('«' + member + '»');
|
||||
if (!isFirst) titleText2.attr('dy', conf.textHeight);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
let classTitleString = classDef.id;
|
||||
|
||||
if (classDef.type !== undefined && classDef.type !== '') {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
}
|
||||
|
||||
const classTitle = title
|
||||
.append('tspan')
|
||||
.text(classTitleString)
|
||||
.attr('class', 'title');
|
||||
|
||||
// If class has annotations the title needs to have an offset of the text height
|
||||
if (!isFirst) classTitle.attr('dy', conf.textHeight);
|
||||
|
||||
const titleHeight = title.node().getBBox().height;
|
||||
|
||||
const membersLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
||||
|
||||
const members = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true;
|
||||
classDef.members.forEach(function(member) {
|
||||
addTspan(members, member, isFirst, conf);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const membersBox = members.node().getBBox();
|
||||
|
||||
const methodsLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
|
||||
|
||||
const methods = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true;
|
||||
|
||||
classDef.methods.forEach(function(method) {
|
||||
addTspan(methods, method, isFirst, conf);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const classBox = g.node().getBBox();
|
||||
const rect = g
|
||||
.insert('rect', ':first-child')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('width', classBox.width + 2 * conf.padding)
|
||||
.attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin);
|
||||
|
||||
const rectWidth = rect.node().getBBox().width;
|
||||
|
||||
// Center title
|
||||
// We subtract the width of each text element from the class box width and divide it by 2
|
||||
title.node().childNodes.forEach(function(x) {
|
||||
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
|
||||
});
|
||||
|
||||
if (classDef.tooltip) {
|
||||
title.insert('title').text(classDef.tooltip);
|
||||
}
|
||||
|
||||
membersLine.attr('x2', rectWidth);
|
||||
methodsLine.attr('x2', rectWidth);
|
||||
|
||||
classInfo.width = rectWidth;
|
||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
||||
|
||||
return classInfo;
|
||||
};
|
||||
|
||||
export const parseMember = function(text) {
|
||||
const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+)$/;
|
||||
const methodRegEx = /^(\+|-|~|#)?(\w+)\s?\(\s*(\w+(~\w+~|\[\])?\s*(\w+)?)?\s*\)\s?([*|$])?\s?(\w+(~\w+~|\[\])?)?\s*$/;
|
||||
|
||||
let fieldMatch = text.match(fieldRegEx);
|
||||
let methodMatch = text.match(methodRegEx);
|
||||
|
||||
if (fieldMatch) {
|
||||
return buildFieldDisplay(fieldMatch);
|
||||
} else if (methodMatch) {
|
||||
return buildMethodDisplay(methodMatch);
|
||||
} else {
|
||||
return buildLegacyDisplay(text);
|
||||
}
|
||||
};
|
||||
|
||||
const buildFieldDisplay = function(parsedText) {
|
||||
let visibility = parsedText[1] ? parsedText[1].trim() : '';
|
||||
let fieldType = parsedText[2] ? parsedText[2].trim() : '';
|
||||
let genericType = parsedText[3] ? parseGenericTypes(parsedText[3]) : '';
|
||||
let fieldName = parsedText[4] ? parsedText[4].trim() : '';
|
||||
|
||||
return {
|
||||
displayText: visibility + fieldType + genericType + ' ' + fieldName,
|
||||
cssStyle: ''
|
||||
};
|
||||
};
|
||||
|
||||
const buildMethodDisplay = function(parsedText) {
|
||||
let cssStyle = '';
|
||||
let displayText = parsedText;
|
||||
|
||||
let visibility = parsedText[1] ? parsedText[1].trim() : '';
|
||||
let methodName = parsedText[2] ? parsedText[2].trim() : '';
|
||||
let parameters = parsedText[3] ? parseGenericTypes(parsedText[3]) : '';
|
||||
let classifier = parsedText[6] ? parsedText[6].trim() : '';
|
||||
let returnType = parsedText[7] ? ' : ' + parseGenericTypes(parsedText[7]).trim() : '';
|
||||
|
||||
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
|
||||
|
||||
cssStyle = parseClassifier(classifier);
|
||||
|
||||
let member = {
|
||||
displayText: displayText,
|
||||
cssStyle: cssStyle
|
||||
};
|
||||
|
||||
return member;
|
||||
};
|
||||
|
||||
const buildLegacyDisplay = function(text) {
|
||||
// if for some reason we dont have any match, use old format to parse text
|
||||
let memberText = '';
|
||||
let cssStyle = '';
|
||||
let returnType = '';
|
||||
let methodStart = text.indexOf('(');
|
||||
let methodEnd = text.indexOf(')');
|
||||
|
||||
if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) {
|
||||
let parsedText = text.match(/(\+|-|~|#)?(\w+)/);
|
||||
let visibility = parsedText[1] ? parsedText[1].trim() : '';
|
||||
let methodName = parsedText[2];
|
||||
let parameters = text.substring(methodStart + 1, methodEnd);
|
||||
let classifier = text.substring(methodEnd, methodEnd + 1);
|
||||
cssStyle = parseClassifier(classifier);
|
||||
|
||||
memberText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
|
||||
|
||||
if (methodEnd < memberText.length) {
|
||||
returnType = text.substring(methodEnd + 2).trim();
|
||||
if (returnType !== '') {
|
||||
returnType = ' : ' + parseGenericTypes(returnType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// finally - if all else fails, just send the text back as written (other than parsing for generic types)
|
||||
memberText = parseGenericTypes(text);
|
||||
}
|
||||
|
||||
let member = {
|
||||
displayText: memberText + returnType,
|
||||
cssStyle: cssStyle
|
||||
};
|
||||
|
||||
return member;
|
||||
};
|
||||
|
||||
const addTspan = function(textEl, txt, isFirst, conf) {
|
||||
let member = parseMember(txt);
|
||||
|
||||
const tSpan = textEl
|
||||
.append('tspan')
|
||||
.attr('x', conf.padding)
|
||||
.text(member.displayText);
|
||||
|
||||
if (member.cssStyle !== '') {
|
||||
tSpan.attr('style', member.cssStyle);
|
||||
}
|
||||
|
||||
if (!isFirst) {
|
||||
tSpan.attr('dy', conf.textHeight);
|
||||
}
|
||||
};
|
||||
|
||||
const parseGenericTypes = function(text) {
|
||||
let cleanedText = text;
|
||||
|
||||
if (text.indexOf('~') != -1) {
|
||||
cleanedText = cleanedText.replace('~', '<');
|
||||
cleanedText = cleanedText.replace('~', '>');
|
||||
|
||||
return parseGenericTypes(cleanedText);
|
||||
} else {
|
||||
return cleanedText;
|
||||
}
|
||||
};
|
||||
|
||||
const parseClassifier = function(classifier) {
|
||||
switch (classifier) {
|
||||
case '*':
|
||||
return 'font-style:italic;';
|
||||
case '$':
|
||||
return 'text-decoration:underline;';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
drawClass,
|
||||
drawEdge,
|
||||
parseMember
|
||||
};
|
||||
152
src/diagrams/class/svgDraw.spec.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/* eslint-env jasmine */
|
||||
import svgDraw from './svgDraw';
|
||||
|
||||
describe('class member Renderer, ', function () {
|
||||
describe('when parsing text to build method display string', function () {
|
||||
it('should handle simple method declaration', function () {
|
||||
const str = 'foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = '+foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = '-foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('-foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = '#foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('#foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle package/internal visibility', function () {
|
||||
const str = '~foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('~foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for visibility', function () {
|
||||
const str = '!foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle abstract classifier', function () {
|
||||
const str = 'foo()*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle static classifier', function () {
|
||||
const str = 'foo()$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for classifier', function () {
|
||||
const str = 'foo()!';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with parameters', function () {
|
||||
const str = 'foo(int id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with single item in parameters', function () {
|
||||
const str = 'foo(id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with single item in parameters with extra spaces', function () {
|
||||
const str = ' foo ( id) ';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with return value', function () {
|
||||
const str = 'foo(id) int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic return value', function () {
|
||||
const str = 'foo(id) List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic parameter', function () {
|
||||
const str = 'foo(List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with all possible markup', function () {
|
||||
const str = '+foo ( List~int~ ids )* List~Item~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<int> ids) : List<Item>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing text to build field display string', function () {
|
||||
it('should handle simple field declaration', function () {
|
||||
const str = 'int[] ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('int[] ids');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field declaration with generic type', function () {
|
||||
const str = 'List~int~ ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<int> ids');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
42
src/diagrams/common/common.js
Normal file
@@ -0,0 +1,42 @@
|
||||
export const getRows = s => {
|
||||
if (!s) return 1;
|
||||
let str = breakToPlaceholder(s);
|
||||
str = str.replace(/\\n/g, '#br#');
|
||||
return str.split('#br#');
|
||||
};
|
||||
|
||||
export const sanitizeText = (text, config) => {
|
||||
let txt = text;
|
||||
let htmlLabels = true;
|
||||
if (
|
||||
config.flowchart &&
|
||||
(config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false')
|
||||
)
|
||||
htmlLabels = false;
|
||||
|
||||
if (config.securityLevel !== 'loose' && htmlLabels) {
|
||||
// eslint-disable-line
|
||||
txt = breakToPlaceholder(txt);
|
||||
txt = txt.replace(/</g, '<').replace(/>/g, '>');
|
||||
txt = txt.replace(/=/g, '=');
|
||||
txt = placeholderToBreak(txt);
|
||||
}
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
const breakToPlaceholder = s => {
|
||||
return s.replace(lineBreakRegex, '#br#');
|
||||
};
|
||||
|
||||
const placeholderToBreak = s => {
|
||||
return s.replace(/#br#/g, '<br/>');
|
||||
};
|
||||
|
||||
export default {
|
||||
getRows,
|
||||
sanitizeText,
|
||||
lineBreakRegex
|
||||
};
|
||||
77
src/diagrams/er/erDb.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
import { logger } from '../../logger';
|
||||
|
||||
let entities = {};
|
||||
let relationships = [];
|
||||
let title = '';
|
||||
|
||||
const Cardinality = {
|
||||
ZERO_OR_ONE: 'ZERO_OR_ONE',
|
||||
ZERO_OR_MORE: 'ZERO_OR_MORE',
|
||||
ONE_OR_MORE: 'ONE_OR_MORE',
|
||||
ONLY_ONE: 'ONLY_ONE'
|
||||
};
|
||||
|
||||
const Identification = {
|
||||
NON_IDENTIFYING: 'NON_IDENTIFYING',
|
||||
IDENTIFYING: 'IDENTIFYING'
|
||||
};
|
||||
|
||||
const addEntity = function(name) {
|
||||
if (typeof entities[name] === 'undefined') {
|
||||
entities[name] = name;
|
||||
logger.debug('Added new entity :', name);
|
||||
}
|
||||
};
|
||||
|
||||
const getEntities = () => entities;
|
||||
|
||||
/**
|
||||
* Add a relationship
|
||||
* @param entA The first entity in the relationship
|
||||
* @param rolA The role played by the first entity in relation to the second
|
||||
* @param entB The second entity in the relationship
|
||||
* @param rSpec The details of the relationship between the two entities
|
||||
*/
|
||||
const addRelationship = function(entA, rolA, entB, rSpec) {
|
||||
let rel = {
|
||||
entityA: entA,
|
||||
roleA: rolA,
|
||||
entityB: entB,
|
||||
relSpec: rSpec
|
||||
};
|
||||
|
||||
relationships.push(rel);
|
||||
logger.debug('Added new relationship :', rel);
|
||||
};
|
||||
|
||||
const getRelationships = () => relationships;
|
||||
|
||||
// Keep this - TODO: revisit...allow the diagram to have a title
|
||||
const setTitle = function(txt) {
|
||||
title = txt;
|
||||
};
|
||||
|
||||
const getTitle = function() {
|
||||
return title;
|
||||
};
|
||||
|
||||
const clear = function() {
|
||||
entities = {};
|
||||
relationships = [];
|
||||
title = '';
|
||||
};
|
||||
|
||||
export default {
|
||||
Cardinality,
|
||||
Identification,
|
||||
addEntity,
|
||||
getEntities,
|
||||
addRelationship,
|
||||
getRelationships,
|
||||
clear,
|
||||
setTitle,
|
||||
getTitle
|
||||
};
|
||||
168
src/diagrams/er/erMarkers.js
Normal file
@@ -0,0 +1,168 @@
|
||||
const ERMarkers = {
|
||||
ONLY_ONE_START: 'ONLY_ONE_START',
|
||||
ONLY_ONE_END: 'ONLY_ONE_END',
|
||||
ZERO_OR_ONE_START: 'ZERO_OR_ONE_START',
|
||||
ZERO_OR_ONE_END: 'ZERO_OR_ONE_END',
|
||||
ONE_OR_MORE_START: 'ONE_OR_MORE_START',
|
||||
ONE_OR_MORE_END: 'ONE_OR_MORE_END',
|
||||
ZERO_OR_MORE_START: 'ZERO_OR_MORE_START',
|
||||
ZERO_OR_MORE_END: 'ZERO_OR_MORE_END'
|
||||
};
|
||||
|
||||
/**
|
||||
* Put the markers into the svg DOM for later use with edge paths
|
||||
*/
|
||||
const insertMarkers = function(elem, conf) {
|
||||
let marker;
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ONLY_ONE_START)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 9)
|
||||
.attr('markerWidth', 18)
|
||||
.attr('markerHeight', 18)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M9,0 L9,18 M15,0 L15,18');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ONLY_ONE_END)
|
||||
.attr('refX', 18)
|
||||
.attr('refY', 9)
|
||||
.attr('markerWidth', 18)
|
||||
.attr('markerHeight', 18)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M3,0 L3,18 M9,0 L9,18');
|
||||
|
||||
marker = elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ZERO_OR_ONE_START)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 9)
|
||||
.attr('markerWidth', 30)
|
||||
.attr('markerHeight', 18)
|
||||
.attr('orient', 'auto');
|
||||
marker
|
||||
.append('circle')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'white')
|
||||
.attr('cx', 21)
|
||||
.attr('cy', 9)
|
||||
.attr('r', 6);
|
||||
marker
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M9,0 L9,18');
|
||||
|
||||
marker = elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ZERO_OR_ONE_END)
|
||||
.attr('refX', 30)
|
||||
.attr('refY', 9)
|
||||
.attr('markerWidth', 30)
|
||||
.attr('markerHeight', 18)
|
||||
.attr('orient', 'auto');
|
||||
marker
|
||||
.append('circle')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'white')
|
||||
.attr('cx', 9)
|
||||
.attr('cy', 9)
|
||||
.attr('r', 6);
|
||||
marker
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M21,0 L21,18');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ONE_OR_MORE_START)
|
||||
.attr('refX', 18)
|
||||
.attr('refY', 18)
|
||||
.attr('markerWidth', 45)
|
||||
.attr('markerHeight', 36)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ONE_OR_MORE_END)
|
||||
.attr('refX', 27)
|
||||
.attr('refY', 18)
|
||||
.attr('markerWidth', 45)
|
||||
.attr('markerHeight', 36)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18');
|
||||
|
||||
marker = elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ZERO_OR_MORE_START)
|
||||
.attr('refX', 18)
|
||||
.attr('refY', 18)
|
||||
.attr('markerWidth', 57)
|
||||
.attr('markerHeight', 36)
|
||||
.attr('orient', 'auto');
|
||||
marker
|
||||
.append('circle')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'white')
|
||||
.attr('cx', 48)
|
||||
.attr('cy', 18)
|
||||
.attr('r', 6);
|
||||
marker
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M0,18 Q18,0 36,18 Q18,36 0,18');
|
||||
|
||||
marker = elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.ZERO_OR_MORE_END)
|
||||
.attr('refX', 39)
|
||||
.attr('refY', 18)
|
||||
.attr('markerWidth', 57)
|
||||
.attr('markerHeight', 36)
|
||||
.attr('orient', 'auto');
|
||||
marker
|
||||
.append('circle')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'white')
|
||||
.attr('cx', 9)
|
||||
.attr('cy', 18)
|
||||
.attr('r', 6);
|
||||
marker
|
||||
.append('path')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none')
|
||||
.attr('d', 'M21,18 Q39,0 57,18 Q39,36 21,18');
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
export default {
|
||||
ERMarkers,
|
||||
insertMarkers
|
||||
};
|
||||
351
src/diagrams/er/erRenderer.js
Normal file
@@ -0,0 +1,351 @@
|
||||
import graphlib from 'graphlib';
|
||||
import { line, curveBasis, select } from 'd3';
|
||||
import erDb from './erDb';
|
||||
import erParser from './parser/erDiagram';
|
||||
import dagre from 'dagre';
|
||||
import { getConfig } from '../../config';
|
||||
import { logger } from '../../logger';
|
||||
import erMarkers from './erMarkers';
|
||||
|
||||
const conf = {};
|
||||
|
||||
/**
|
||||
* Allows the top-level API module to inject config specific to this renderer,
|
||||
* storing it in the local conf object. Note that generic config still needs to be
|
||||
* retrieved using getConfig() imported from the config module
|
||||
*/
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
conf[keys[i]] = cnf[keys[i]];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Use D3 to construct the svg elements for the entities
|
||||
* @param svgNode the svg node that contains the diagram
|
||||
* @param entities The entities to be drawn
|
||||
* @param graph The graph that contains the vertex and edge definitions post-layout
|
||||
* @return The first entity that was inserted
|
||||
*/
|
||||
const drawEntities = function(svgNode, entities, graph) {
|
||||
const keys = Object.keys(entities);
|
||||
let firstOne;
|
||||
|
||||
keys.forEach(function(id) {
|
||||
// Create a group for each entity
|
||||
const groupNode = svgNode.append('g').attr('id', id);
|
||||
|
||||
firstOne = firstOne === undefined ? id : firstOne;
|
||||
|
||||
// Label the entity - this is done first so that we can get the bounding box
|
||||
// which then determines the size of the rectangle
|
||||
const textId = 'entity-' + id;
|
||||
const textNode = groupNode
|
||||
.append('text')
|
||||
.attr('id', textId)
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr(
|
||||
'style',
|
||||
'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
|
||||
)
|
||||
.text(id);
|
||||
|
||||
// Calculate the width and height of the entity
|
||||
const textBBox = textNode.node().getBBox();
|
||||
const entityWidth = Math.max(conf.minEntityWidth, textBBox.width + conf.entityPadding * 2);
|
||||
const entityHeight = Math.max(conf.minEntityHeight, textBBox.height + conf.entityPadding * 2);
|
||||
|
||||
// Make sure the text gets centred relative to the entity box
|
||||
textNode.attr('transform', 'translate(' + entityWidth / 2 + ',' + entityHeight / 2 + ')');
|
||||
|
||||
// Draw the rectangle - insert it before the text so that the text is not obscured
|
||||
const rectNode = groupNode
|
||||
.insert('rect', '#' + textId)
|
||||
.attr('fill', conf.fill)
|
||||
.attr('fill-opacity', '100%')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('width', entityWidth)
|
||||
.attr('height', entityHeight);
|
||||
|
||||
const rectBBox = rectNode.node().getBBox();
|
||||
|
||||
// Add the entity to the graph
|
||||
graph.setNode(id, {
|
||||
width: rectBBox.width,
|
||||
height: rectBBox.height,
|
||||
shape: 'rect',
|
||||
id: id
|
||||
});
|
||||
});
|
||||
return firstOne;
|
||||
}; // drawEntities
|
||||
|
||||
const adjustEntities = function(svgNode, graph) {
|
||||
graph.nodes().forEach(function(v) {
|
||||
if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') {
|
||||
svgNode
|
||||
.select('#' + v)
|
||||
.attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
(graph.node(v).x - graph.node(v).width / 2) +
|
||||
',' +
|
||||
(graph.node(v).y - graph.node(v).height / 2) +
|
||||
' )'
|
||||
);
|
||||
}
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
const getEdgeName = function(rel) {
|
||||
return (rel.entityA + rel.roleA + rel.entityB).replace(/\s/g, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Add each relationship to the graph
|
||||
* @param relationships the relationships to be added
|
||||
* @param g the graph
|
||||
* @return {Array} The array of relationships
|
||||
*/
|
||||
const addRelationships = function(relationships, g) {
|
||||
relationships.forEach(function(r) {
|
||||
g.setEdge(r.entityA, r.entityB, { relationship: r }, getEdgeName(r));
|
||||
});
|
||||
return relationships;
|
||||
}; // addRelationships
|
||||
|
||||
let relCnt = 0;
|
||||
/**
|
||||
* Draw a relationship using edge information from the graph
|
||||
* @param svg the svg node
|
||||
* @param rel the relationship to draw in the svg
|
||||
* @param g the graph containing the edge information
|
||||
* @param insert the insertion point in the svg DOM (because relationships have markers that need to sit 'behind' opaque entity boxes)
|
||||
*/
|
||||
const drawRelationshipFromLayout = function(svg, rel, g, insert) {
|
||||
relCnt++;
|
||||
|
||||
// Find the edge relating to this relationship
|
||||
const edge = g.edge(rel.entityA, rel.entityB, getEdgeName(rel));
|
||||
|
||||
// Get a function that will generate the line path
|
||||
const lineFunction = line()
|
||||
.x(function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.curve(curveBasis);
|
||||
|
||||
// Insert the line at the right place
|
||||
const svgPath = svg
|
||||
.insert('path', '#' + insert)
|
||||
.attr('d', lineFunction(edge.points))
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('fill', 'none');
|
||||
|
||||
// ...and with dashes if necessary
|
||||
if (rel.relSpec.relType === erDb.Identification.NON_IDENTIFYING) {
|
||||
svgPath.attr('stroke-dasharray', '8,8');
|
||||
}
|
||||
|
||||
// TODO: Understand this better
|
||||
let url = '';
|
||||
if (conf.arrowMarkerAbsolute) {
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
// Decide which start and end markers it needs. It may be possible to be more concise here
|
||||
// by reversing a start marker to make an end marker...but this will do for now
|
||||
|
||||
// Note that the 'A' entity's marker is at the end of the relationship and the 'B' entity's marker is at the start
|
||||
switch (rel.relSpec.cardA) {
|
||||
case erDb.Cardinality.ZERO_OR_ONE:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');
|
||||
break;
|
||||
case erDb.Cardinality.ZERO_OR_MORE:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')');
|
||||
break;
|
||||
case erDb.Cardinality.ONE_OR_MORE:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')');
|
||||
break;
|
||||
case erDb.Cardinality.ONLY_ONE:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rel.relSpec.cardB) {
|
||||
case erDb.Cardinality.ZERO_OR_ONE:
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'
|
||||
);
|
||||
break;
|
||||
case erDb.Cardinality.ZERO_OR_MORE:
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'
|
||||
);
|
||||
break;
|
||||
case erDb.Cardinality.ONE_OR_MORE:
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'
|
||||
);
|
||||
break;
|
||||
case erDb.Cardinality.ONLY_ONE:
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
|
||||
break;
|
||||
}
|
||||
|
||||
// Now label the relationship
|
||||
|
||||
// Find the half-way point
|
||||
const len = svgPath.node().getTotalLength();
|
||||
const labelPoint = svgPath.node().getPointAtLength(len * 0.5);
|
||||
|
||||
// Append a text node containing the label
|
||||
const labelId = 'rel' + relCnt;
|
||||
|
||||
const labelNode = svg
|
||||
.append('text')
|
||||
.attr('id', labelId)
|
||||
.attr('x', labelPoint.x)
|
||||
.attr('y', labelPoint.y)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr(
|
||||
'style',
|
||||
'font-family: ' + getConfig().fontFamily + '; font-size: ' + conf.fontSize + 'px'
|
||||
)
|
||||
.text(rel.roleA);
|
||||
|
||||
// Figure out how big the opaque 'container' rectangle needs to be
|
||||
const labelBBox = labelNode.node().getBBox();
|
||||
|
||||
// Insert the opaque rectangle before the text label
|
||||
svg
|
||||
.insert('rect', '#' + labelId)
|
||||
.attr('x', labelPoint.x - labelBBox.width / 2)
|
||||
.attr('y', labelPoint.y - labelBBox.height / 2)
|
||||
.attr('width', labelBBox.width)
|
||||
.attr('height', labelBBox.height)
|
||||
.attr('fill', 'white')
|
||||
.attr('fill-opacity', '85%');
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw en E-R diagram in the tag with id: id based on the text definition of the diagram
|
||||
* @param text the text of the diagram
|
||||
* @param id the unique id of the DOM node that contains the diagram
|
||||
*/
|
||||
export const draw = function(text, id) {
|
||||
logger.info('Drawing ER diagram');
|
||||
erDb.clear();
|
||||
const parser = erParser.parser;
|
||||
parser.yy = erDb;
|
||||
|
||||
// Parse the text to populate erDb
|
||||
try {
|
||||
parser.parse(text);
|
||||
} catch (err) {
|
||||
logger.debug('Parsing failed');
|
||||
}
|
||||
|
||||
// Get a reference to the svg node that contains the text
|
||||
const svg = select(`[id='${id}']`);
|
||||
|
||||
// Add cardinality marker definitions to the svg
|
||||
erMarkers.insertMarkers(svg, conf);
|
||||
|
||||
// Now we have to construct the diagram in a specific way:
|
||||
// ---
|
||||
// 1. Create all the entities in the svg node at 0,0, but with the correct dimensions (allowing for text content)
|
||||
// 2. Make sure they are all added to the graph
|
||||
// 3. Add all the edges (relationships) to the graph aswell
|
||||
// 4. Let dagre do its magic to layout the graph. This assigns:
|
||||
// - the centre co-ordinates for each node, bearing in mind the dimensions and edge relationships
|
||||
// - the path co-ordinates for each edge
|
||||
// But it has no impact on the svg child nodes - the diagram remains with every entity rooted at 0,0
|
||||
// 5. Now assign a transform to each entity in the svg node so that it gets drawn in the correct place, as determined by
|
||||
// its centre point, which is obtained from the graph, and it's width and height
|
||||
// 6. And finally, create all the edges in the svg node using information from the graph
|
||||
// ---
|
||||
|
||||
// Create the graph
|
||||
let g;
|
||||
|
||||
// TODO: Explore directed vs undirected graphs, and how the layout is affected
|
||||
// An E-R diagram could be said to be undirected, but there is merit in setting
|
||||
// the direction from parent to child in a one-to-many as this influences graphlib to
|
||||
// put the parent above the child (does it?), which is intuitive. Most relationships
|
||||
// in ER diagrams are one-to-many.
|
||||
g = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
directed: true,
|
||||
compound: false
|
||||
})
|
||||
.setGraph({
|
||||
rankdir: conf.layoutDirection,
|
||||
marginx: 20,
|
||||
marginy: 20,
|
||||
nodesep: 100,
|
||||
edgesep: 100,
|
||||
ranksep: 100
|
||||
})
|
||||
.setDefaultEdgeLabel(function() {
|
||||
return {};
|
||||
});
|
||||
|
||||
// Draw the entities (at 0,0), returning the first svg node that got
|
||||
// inserted - this represents the insertion point for relationship paths
|
||||
const firstEntity = drawEntities(svg, erDb.getEntities(), g);
|
||||
|
||||
// TODO: externalise the addition of entities to the graph - it's a bit 'buried' in the above
|
||||
|
||||
// Add all the relationships to the graph
|
||||
const relationships = addRelationships(erDb.getRelationships(), g);
|
||||
|
||||
dagre.layout(g); // Node and edge positions will be updated
|
||||
|
||||
// Adjust the positions of the entities so that they adhere to the layout
|
||||
adjustEntities(svg, g);
|
||||
|
||||
// Draw the relationships
|
||||
relationships.forEach(function(rel) {
|
||||
drawRelationshipFromLayout(svg, rel, g, firstEntity);
|
||||
});
|
||||
|
||||
const padding = conf.diagramPadding;
|
||||
|
||||
const svgBounds = svg.node().getBBox();
|
||||
const width = svgBounds.width + padding * 2;
|
||||
const height = svgBounds.height + padding * 2;
|
||||
|
||||
svg.attr('height', height);
|
||||
svg.attr('width', '100%');
|
||||
svg.attr('style', `max-width: ${width}px;`);
|
||||
svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`);
|
||||
}; // draw
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw
|
||||
};
|
||||
79
src/diagrams/er/parser/erDiagram.jison
Normal file
@@ -0,0 +1,79 @@
|
||||
%lex
|
||||
|
||||
%x string
|
||||
%options case-insensitive
|
||||
|
||||
%%
|
||||
\s+ /* skip whitespace */
|
||||
[\s]+ return 'SPACE';
|
||||
["] { this.begin("string");}
|
||||
<string>["] { this.popState(); }
|
||||
<string>[^"]* { return 'STR'; }
|
||||
"erDiagram" return 'ER_DIAGRAM';
|
||||
\|o return 'ZERO_OR_ONE';
|
||||
\}o return 'ZERO_OR_MORE';
|
||||
\}\| return 'ONE_OR_MORE';
|
||||
\|\| return 'ONLY_ONE';
|
||||
o\| return 'ZERO_OR_ONE';
|
||||
o\{ return 'ZERO_OR_MORE';
|
||||
\|\{ return 'ONE_OR_MORE';
|
||||
\.\. return 'NON_IDENTIFYING';
|
||||
\-\- return 'IDENTIFYING';
|
||||
\.\- return 'NON_IDENTIFYING';
|
||||
\-\. return 'NON_IDENTIFYING';
|
||||
[A-Za-z][A-Za-z0-9\-]* return 'ALPHANUM';
|
||||
. return yytext[0];
|
||||
<<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
%start start
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
: 'ER_DIAGRAM' document 'EOF' { /*console.log('finished parsing');*/ }
|
||||
;
|
||||
|
||||
document
|
||||
: /* empty */
|
||||
| document statement
|
||||
;
|
||||
|
||||
statement
|
||||
: entityName relSpec entityName ':' role
|
||||
{
|
||||
yy.addEntity($1);
|
||||
yy.addEntity($3);
|
||||
yy.addRelationship($1, $5, $3, $2);
|
||||
/*console.log($1 + $2 + $3 + ':' + $5);*/
|
||||
};
|
||||
|
||||
entityName
|
||||
: 'ALPHANUM' { $$ = $1; /*console.log('Entity: ' + $1);*/ }
|
||||
;
|
||||
|
||||
relSpec
|
||||
: cardinality relType cardinality
|
||||
{
|
||||
$$ = { cardA: $3, relType: $2, cardB: $1 };
|
||||
/*console.log('relSpec: ' + $3 + $2 + $1);*/
|
||||
}
|
||||
;
|
||||
|
||||
cardinality
|
||||
: 'ZERO_OR_ONE' { $$ = yy.Cardinality.ZERO_OR_ONE; }
|
||||
| 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; }
|
||||
| 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; }
|
||||
| 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; }
|
||||
;
|
||||
|
||||
relType
|
||||
: 'NON_IDENTIFYING' { $$ = yy.Identification.NON_IDENTIFYING; }
|
||||
| 'IDENTIFYING' { $$ = yy.Identification.IDENTIFYING; }
|
||||
;
|
||||
|
||||
role
|
||||
: 'STR' { $$ = $1; }
|
||||
| 'ALPHANUM' { $$ = $1; }
|
||||
;
|
||||
%%
|
||||
255
src/diagrams/er/parser/erDiagram.spec.js
Normal file
@@ -0,0 +1,255 @@
|
||||
import erDb from '../erDb';
|
||||
import erDiagram from './erDiagram';
|
||||
import { setConfig } from '../../../config';
|
||||
import logger from '../../../logger';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict'
|
||||
});
|
||||
|
||||
describe('when parsing ER diagram it...', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
erDiagram.parser.yy = erDb;
|
||||
erDiagram.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should associate two entities correctly', function() {
|
||||
erDiagram.parser.parse('erDiagram\nCAR ||--o{ DRIVER : "insured for"');
|
||||
const entities = erDb.getEntities();
|
||||
const relationships = erDb.getRelationships();
|
||||
const carEntity = entities.CAR;
|
||||
const driverEntity = entities.DRIVER;
|
||||
|
||||
expect(carEntity).toBe('CAR');
|
||||
expect(driverEntity).toBe('DRIVER');
|
||||
expect(relationships.length).toBe(1);
|
||||
expect(relationships[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
expect(relationships[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
expect(relationships[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
|
||||
});
|
||||
|
||||
it('should not create duplicate entities', function() {
|
||||
const line1 = 'CAR ||--o{ DRIVER : "insured for"';
|
||||
const line2 = 'DRIVER ||--|| LICENSE : has';
|
||||
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
|
||||
const entities = erDb.getEntities();
|
||||
|
||||
expect(Object.keys(entities).length).toBe(3);
|
||||
});
|
||||
|
||||
it('should create the role specified', function() {
|
||||
const teacherRole = 'is teacher of';
|
||||
const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`;
|
||||
erDiagram.parser.parse(`erDiagram\n${line1}`);
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(rels[0].roleA).toBe(`${teacherRole}`);
|
||||
});
|
||||
|
||||
it('should allow recursive relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nNODE ||--o{ NODE : "leads to"');
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow more than one relationship between the same two entities', function() {
|
||||
const line1 = 'CAR ||--o{ PERSON : "insured for"';
|
||||
const line2 = 'CAR }o--|| PERSON : "owned by"';
|
||||
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
|
||||
const entities = erDb.getEntities();
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(entities).length).toBe(2);
|
||||
expect(rels.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should limit the number of relationships between the same two entities', function() {
|
||||
/* TODO */
|
||||
});
|
||||
|
||||
it ('should not allow multiple relationships between the same two entities unless the roles are different', function() {
|
||||
/* TODO */
|
||||
});
|
||||
|
||||
|
||||
it('should handle only-one-to-one-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA ||--|{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
});
|
||||
|
||||
it('should handle only-one-to-zero-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA ||..o{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
|
||||
});
|
||||
|
||||
it('should handle zero-or-one-to-zero-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA |o..o{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-one-to-one-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA |o--|{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
});
|
||||
|
||||
it('should handle one-or-more-to-only-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }|--|| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-more-to-only-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }o--|| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-more-to-zero-or-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }o..o| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle one-or-more-to-zero-or-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }|..o| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-one-to-only-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA |o..|| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
});
|
||||
|
||||
it('should handle only-one-to-only-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA ||..|| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
});
|
||||
|
||||
it('should handle only-one-to-zero-or-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA ||--o| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-one-to-zero-or-one relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA |o..o| B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-more-to-zero-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }o--o{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle one-or-more-to-one-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }|..|{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle zero-or-more-to-one-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }o--|{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
});
|
||||
|
||||
it('should handle one-or-more-to-zero-or-more relationships', function() {
|
||||
erDiagram.parser.parse('erDiagram\nA }|..o{ B : has');
|
||||
const rels = erDb.getRelationships();
|
||||
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
|
||||
});
|
||||
|
||||
it('should represent identifying relationships properly', function() {
|
||||
erDiagram.parser.parse('erDiagram\nHOUSE ||--|{ ROOM : contains');
|
||||
const rels = erDb.getRelationships();
|
||||
expect(rels[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
|
||||
});
|
||||
|
||||
it('should represent non-identifying relationships properly', function() {
|
||||
erDiagram.parser.parse('erDiagram\n PERSON ||..o{ POSSESSION : owns');
|
||||
const rels = erDb.getRelationships();
|
||||
expect(rels[0].relSpec.relType).toBe(erDb.Identification.NON_IDENTIFYING);
|
||||
});
|
||||
|
||||
it('should not accept a syntax error', function() {
|
||||
const doc = 'erDiagram\nA xxx B : has';
|
||||
expect(() => {
|
||||
erDiagram.parser.parse(doc);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -135,9 +135,116 @@ function rect_right_inv_arrow(parent, bbox, node) {
|
||||
return shapeSvg;
|
||||
}
|
||||
|
||||
function stadium(parent, bbox, node) {
|
||||
const h = bbox.height;
|
||||
const w = bbox.width + h / 4;
|
||||
|
||||
const shapeSvg = parent
|
||||
.insert('rect', ':first-child')
|
||||
.attr('rx', h / 2)
|
||||
.attr('ry', h / 2)
|
||||
.attr('x', -w / 2)
|
||||
.attr('y', -h / 2)
|
||||
.attr('width', w)
|
||||
.attr('height', h);
|
||||
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.rect(node, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
}
|
||||
|
||||
function subroutine(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w, y: -h },
|
||||
{ x: 0, y: -h },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: -8, y: 0 },
|
||||
{ x: w + 8, y: 0 },
|
||||
{ x: w + 8, y: -h },
|
||||
{ x: -8, y: -h },
|
||||
{ x: -8, y: 0 }
|
||||
];
|
||||
const shapeSvg = insertPolygonShape(parent, w, h, points);
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
}
|
||||
|
||||
function cylinder(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const rx = w / 2;
|
||||
const ry = rx / (2.5 + w / 50);
|
||||
const h = bbox.height + ry;
|
||||
|
||||
const shape =
|
||||
'M 0,' +
|
||||
ry +
|
||||
' a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
w +
|
||||
' 0 a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
-w +
|
||||
' 0 l 0,' +
|
||||
h +
|
||||
' a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
w +
|
||||
' 0 l 0,' +
|
||||
-h;
|
||||
|
||||
const shapeSvg = parent
|
||||
.attr('label-offset-y', ry)
|
||||
.insert('path', ':first-child')
|
||||
.attr('d', shape)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2 + ry) + ')');
|
||||
|
||||
node.intersect = function(point) {
|
||||
const pos = dagreD3.intersect.rect(node, point);
|
||||
const x = pos.x - node.x;
|
||||
|
||||
if (
|
||||
rx != 0 &&
|
||||
(Math.abs(x) < node.width / 2 ||
|
||||
(Math.abs(x) == node.width / 2 && Math.abs(pos.y - node.y) > node.height / 2 - ry))
|
||||
) {
|
||||
// ellipsis equation: x*x / a*a + y*y / b*b = 1
|
||||
// solve for y to get adjustion value for pos.y
|
||||
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) y = -y;
|
||||
|
||||
pos.y += y;
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
||||
|
||||
export function addToRender(render) {
|
||||
render.shapes().question = question;
|
||||
render.shapes().hexagon = hexagon;
|
||||
render.shapes().stadium = stadium;
|
||||
render.shapes().subroutine = subroutine;
|
||||
render.shapes().cylinder = cylinder;
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().rect_left_inv_arrow = rect_left_inv_arrow;
|
||||
@@ -158,6 +265,32 @@ export function addToRender(render) {
|
||||
render.shapes().rect_right_inv_arrow = rect_right_inv_arrow;
|
||||
}
|
||||
|
||||
export function addToRenderV2(addShape) {
|
||||
addShape({ question });
|
||||
addShape({ hexagon });
|
||||
addShape({ stadium });
|
||||
addShape({ subroutine });
|
||||
addShape({ cylinder });
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
addShape({ rect_left_inv_arrow });
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
addShape({ lean_right });
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
addShape({ lean_left });
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
addShape({ trapezoid });
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
addShape({ inv_trapezoid });
|
||||
|
||||
// Add custom shape for box with inverted arrow on right side
|
||||
addShape({ rect_right_inv_arrow });
|
||||
}
|
||||
|
||||
function insertPolygonShape(parent, w, h, points) {
|
||||
return parent
|
||||
.insert('polygon', ':first-child')
|
||||
@@ -173,5 +306,6 @@ function insertPolygonShape(parent, w, h, points) {
|
||||
}
|
||||
|
||||
export default {
|
||||
addToRender
|
||||
addToRender,
|
||||
addToRenderV2
|
||||
};
|
||||
|
||||
@@ -1,6 +1,46 @@
|
||||
import { addToRender } from './flowChartShapes';
|
||||
|
||||
describe('flowchart shapes', function() {
|
||||
// rect-based shapes
|
||||
[
|
||||
['stadium', useWidth, useHeight]
|
||||
].forEach(function([shapeType, getW, getH]) {
|
||||
it(`should add a ${shapeType} shape that renders a properly positioned rect element`, function() {
|
||||
const mockRender = MockRender();
|
||||
const mockSvg = MockSvg();
|
||||
addToRender(mockRender);
|
||||
|
||||
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
|
||||
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
|
||||
const w = width + height / 4;
|
||||
const h = height;
|
||||
const dx = -getW(w, h) / 2;
|
||||
const dy = -getH(w, h) / 2;
|
||||
expect(shape.__tag).toEqual('rect');
|
||||
expect(shape.__attrs).toHaveProperty('x', dx);
|
||||
expect(shape.__attrs).toHaveProperty('y', dy);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// path-based shapes
|
||||
[
|
||||
['cylinder', useWidth, useHeight]
|
||||
].forEach(function([shapeType, getW, getH]) {
|
||||
it(`should add a ${shapeType} shape that renders a properly positioned path element`, function() {
|
||||
const mockRender = MockRender();
|
||||
const mockSvg = MockSvg();
|
||||
addToRender(mockRender);
|
||||
|
||||
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
|
||||
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
|
||||
expect(shape.__tag).toEqual('path');
|
||||
expect(shape.__attrs).toHaveProperty('d');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// polygon-based shapes
|
||||
[
|
||||
[
|
||||
'question',
|
||||
@@ -25,7 +65,8 @@ describe('flowchart shapes', function() {
|
||||
['lean_right', 4, useWidth, useHeight],
|
||||
['lean_left', 4, useWidth, useHeight],
|
||||
['trapezoid', 4, useWidth, useHeight],
|
||||
['inv_trapezoid', 4, useWidth, useHeight]
|
||||
['inv_trapezoid', 4, useWidth, useHeight],
|
||||
['subroutine', 10, useWidth, useHeight],
|
||||
].forEach(function([shapeType, expectedPointCount, getW, getH]) {
|
||||
it(`should add a ${shapeType} shape that renders a properly translated polygon element`, function() {
|
||||
const mockRender = MockRender();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as d3 from 'd3';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import { logger } from '../../logger';
|
||||
import { select } from 'd3';
|
||||
import { logger } from '../../logger'; // eslint-disable-line
|
||||
import utils from '../../utils';
|
||||
import { getConfig } from '../../config';
|
||||
import common from '../common/common';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-';
|
||||
// const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-';
|
||||
const MERMAID_DOM_ID_PREFIX = '';
|
||||
|
||||
const config = getConfig();
|
||||
let vertices = {};
|
||||
@@ -19,19 +20,6 @@ let direction;
|
||||
// Functions to be run after graph rendering
|
||||
let funs = [];
|
||||
|
||||
const sanitize = text => {
|
||||
let txt = text;
|
||||
if (config.securityLevel !== 'loose') {
|
||||
txt = txt.replace(/<br>/g, '#br#');
|
||||
txt = txt.replace(/<br\S*?\/>/g, '#br#');
|
||||
txt = txt.replace(/</g, '<').replace(/>/g, '>');
|
||||
txt = txt.replace(/=/g, '=');
|
||||
txt = txt.replace(/#br#/g, '<br/>');
|
||||
}
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a node definition has been found
|
||||
* @param id
|
||||
@@ -56,16 +44,16 @@ export const addVertex = function(_id, text, type, style, classes) {
|
||||
vertices[id] = { id: id, styles: [], classes: [] };
|
||||
}
|
||||
if (typeof text !== 'undefined') {
|
||||
txt = sanitize(text.trim());
|
||||
txt = common.sanitizeText(text.trim(), config);
|
||||
|
||||
// strip quotes if string starts and exnds with a quote
|
||||
// strip quotes if string starts and ends with a quote
|
||||
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
|
||||
txt = txt.substring(1, txt.length - 1);
|
||||
}
|
||||
|
||||
vertices[id].text = txt;
|
||||
} else {
|
||||
if (!vertices[id].text) {
|
||||
if (typeof vertices[id].text === 'undefined') {
|
||||
vertices[id].text = _id;
|
||||
}
|
||||
}
|
||||
@@ -95,18 +83,18 @@ export const addVertex = function(_id, text, type, style, classes) {
|
||||
* @param type
|
||||
* @param linktext
|
||||
*/
|
||||
export const addLink = function(_start, _end, type, linktext) {
|
||||
export const addSingleLink = function(_start, _end, type, linktext) {
|
||||
let start = _start;
|
||||
let end = _end;
|
||||
if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
|
||||
if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
|
||||
logger.info('Got edge...', start, end);
|
||||
// logger.info('Got edge...', start, end);
|
||||
|
||||
const edge = { start: start, end: end, type: undefined, text: '' };
|
||||
linktext = type.text;
|
||||
|
||||
if (typeof linktext !== 'undefined') {
|
||||
edge.text = sanitize(linktext.trim());
|
||||
edge.text = common.sanitizeText(linktext.trim(), config);
|
||||
|
||||
// strip quotes if string starts and exnds with a quote
|
||||
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
|
||||
@@ -120,6 +108,14 @@ export const addLink = function(_start, _end, type, linktext) {
|
||||
}
|
||||
edges.push(edge);
|
||||
};
|
||||
export const addLink = function(_start, _end, type, linktext) {
|
||||
let i, j;
|
||||
for (i = 0; i < _start.length; i++) {
|
||||
for (j = 0; j < _end.length; j++) {
|
||||
addSingleLink(_start[i], _end[j], type, linktext);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a link's line interpolation algorithm
|
||||
@@ -156,12 +152,17 @@ export const updateLink = function(positions, style) {
|
||||
|
||||
export const addClass = function(id, style) {
|
||||
if (typeof classes[id] === 'undefined') {
|
||||
classes[id] = { id: id, styles: [] };
|
||||
classes[id] = { id: id, styles: [], textStyles: [] };
|
||||
}
|
||||
|
||||
if (typeof style !== 'undefined') {
|
||||
if (style !== null) {
|
||||
style.forEach(function(s) {
|
||||
if (s.match('color')) {
|
||||
const newStyle1 = s.replace('fill', 'bgFill');
|
||||
const newStyle2 = newStyle1.replace('color', 'fill');
|
||||
classes[id].textStyles.push(newStyle2);
|
||||
}
|
||||
classes[id].styles.push(s);
|
||||
});
|
||||
}
|
||||
@@ -210,7 +211,7 @@ export const setClass = function(ids, className) {
|
||||
const setTooltip = function(ids, tooltip) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
if (typeof tooltip !== 'undefined') {
|
||||
tooltips[id] = sanitize(tooltip);
|
||||
tooltips[id] = common.sanitizeText(tooltip, config);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -251,11 +252,7 @@ export const setLink = function(ids, linkStr, tooltip) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
if (typeof vertices[id] !== 'undefined') {
|
||||
if (config.securityLevel !== 'loose') {
|
||||
vertices[id].link = sanitizeUrl(linkStr); // .replace(/javascript:.*/g, '')
|
||||
} else {
|
||||
vertices[id].link = linkStr;
|
||||
}
|
||||
vertices[id].link = utils.formatUrl(linkStr, config);
|
||||
}
|
||||
});
|
||||
setTooltip(ids, tooltip);
|
||||
@@ -312,21 +309,20 @@ export const getClasses = function() {
|
||||
};
|
||||
|
||||
const setupToolTips = function(element) {
|
||||
let tooltipElem = d3.select('.mermaidTooltip');
|
||||
let tooltipElem = select('.mermaidTooltip');
|
||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
||||
tooltipElem = d3
|
||||
.select('body')
|
||||
tooltipElem = select('body')
|
||||
.append('div')
|
||||
.attr('class', 'mermaidTooltip')
|
||||
.style('opacity', 0);
|
||||
}
|
||||
|
||||
const svg = d3.select(element).select('svg');
|
||||
const svg = select(element).select('svg');
|
||||
|
||||
const nodes = svg.selectAll('g.node');
|
||||
nodes
|
||||
.on('mouseover', function() {
|
||||
const el = d3.select(this);
|
||||
const el = select(this);
|
||||
const title = el.attr('title');
|
||||
// Dont try to draw a tooltip if no data is provided
|
||||
if (title === null) {
|
||||
@@ -349,7 +345,7 @@ const setupToolTips = function(element) {
|
||||
.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 0);
|
||||
const el = d3.select(this);
|
||||
const el = select(this);
|
||||
el.classed('hover', false);
|
||||
});
|
||||
};
|
||||
@@ -382,7 +378,7 @@ export const defaultStyle = function() {
|
||||
* Clears the internal graph db so that a new graph can be parsed.
|
||||
*/
|
||||
export const addSubGraph = function(_id, list, _title) {
|
||||
let id = _id;
|
||||
let id = _id.trim();
|
||||
let title = _title;
|
||||
if (_id === _title && _title.match(/\s/)) {
|
||||
id = undefined;
|
||||
@@ -414,7 +410,7 @@ export const addSubGraph = function(_id, list, _title) {
|
||||
id = id || 'subGraph' + subCount;
|
||||
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
title = title || '';
|
||||
title = sanitize(title);
|
||||
title = common.sanitizeText(title, config);
|
||||
subCount = subCount + 1;
|
||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
|
||||
subGraphs.push(subGraph);
|
||||
@@ -494,6 +490,130 @@ export const firstGraph = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const destructStartLink = _str => {
|
||||
const str = _str.trim();
|
||||
|
||||
switch (str) {
|
||||
case '<--':
|
||||
return { type: 'arrow_point', stroke: 'normal' };
|
||||
case 'x--':
|
||||
return { type: 'arrow_cross', stroke: 'normal' };
|
||||
case 'o--':
|
||||
return { type: 'arrow_circle', stroke: 'normal' };
|
||||
case '<-.':
|
||||
return { type: 'arrow_point', stroke: 'dotted' };
|
||||
case 'x-.':
|
||||
return { type: 'arrow_cross', stroke: 'dotted' };
|
||||
case 'o-.':
|
||||
return { type: 'arrow_circle', stroke: 'dotted' };
|
||||
case '<==':
|
||||
return { type: 'arrow_point', stroke: 'thick' };
|
||||
case 'x==':
|
||||
return { type: 'arrow_cross', stroke: 'thick' };
|
||||
case 'o==':
|
||||
return { type: 'arrow_circle', stroke: 'thick' };
|
||||
case '--':
|
||||
return { type: 'arrow_open', stroke: 'normal' };
|
||||
case '==':
|
||||
return { type: 'arrow_open', stroke: 'thick' };
|
||||
case '-.':
|
||||
return { type: 'arrow_open', stroke: 'dotted' };
|
||||
}
|
||||
};
|
||||
|
||||
const destructEndLink = _str => {
|
||||
const str = _str.trim();
|
||||
|
||||
switch (str) {
|
||||
case '--x':
|
||||
return { type: 'arrow_cross', stroke: 'normal' };
|
||||
case '-->':
|
||||
return { type: 'arrow_point', stroke: 'normal' };
|
||||
case '<-->':
|
||||
return { type: 'double_arrow_point', stroke: 'normal' };
|
||||
case 'x--x':
|
||||
return { type: 'double_arrow_cross', stroke: 'normal' };
|
||||
case 'o--o':
|
||||
return { type: 'double_arrow_circle', stroke: 'normal' };
|
||||
case 'o.-o':
|
||||
return { type: 'double_arrow_circle', stroke: 'dotted' };
|
||||
case '<==>':
|
||||
return { type: 'double_arrow_point', stroke: 'thick' };
|
||||
case 'o==o':
|
||||
return { type: 'double_arrow_circle', stroke: 'thick' };
|
||||
case 'x==x':
|
||||
return { type: 'double_arrow_cross', stroke: 'thick' };
|
||||
case 'x.-x':
|
||||
return { type: 'double_arrow_cross', stroke: 'dotted' };
|
||||
case 'x-.-x':
|
||||
return { type: 'double_arrow_cross', stroke: 'dotted' };
|
||||
case '<.->':
|
||||
return { type: 'double_arrow_point', stroke: 'dotted' };
|
||||
case '<-.->':
|
||||
return { type: 'double_arrow_point', stroke: 'dotted' };
|
||||
case 'o-.-o':
|
||||
return { type: 'double_arrow_circle', stroke: 'dotted' };
|
||||
case '--o':
|
||||
return { type: 'arrow_circle', stroke: 'normal' };
|
||||
case '---':
|
||||
return { type: 'arrow_open', stroke: 'normal' };
|
||||
case '-.-x':
|
||||
return { type: 'arrow_cross', stroke: 'dotted' };
|
||||
case '-.->':
|
||||
return { type: 'arrow_point', stroke: 'dotted' };
|
||||
case '-.-o':
|
||||
return { type: 'arrow_circle', stroke: 'dotted' };
|
||||
case '-.-':
|
||||
return { type: 'arrow_open', stroke: 'dotted' };
|
||||
case '.-x':
|
||||
return { type: 'arrow_cross', stroke: 'dotted' };
|
||||
case '.->':
|
||||
return { type: 'arrow_point', stroke: 'dotted' };
|
||||
case '.-o':
|
||||
return { type: 'arrow_circle', stroke: 'dotted' };
|
||||
case '.-':
|
||||
return { type: 'arrow_open', stroke: 'dotted' };
|
||||
case '==x':
|
||||
return { type: 'arrow_cross', stroke: 'thick' };
|
||||
case '==>':
|
||||
return { type: 'arrow_point', stroke: 'thick' };
|
||||
case '==o':
|
||||
return { type: 'arrow_circle', stroke: 'thick' };
|
||||
case '===':
|
||||
return { type: 'arrow_open', stroke: 'thick' };
|
||||
}
|
||||
};
|
||||
|
||||
const destructLink = (_str, _startStr) => {
|
||||
const info = destructEndLink(_str);
|
||||
let startInfo;
|
||||
if (_startStr) {
|
||||
startInfo = destructStartLink(_startStr);
|
||||
|
||||
if (startInfo.stroke !== info.stroke) {
|
||||
return { type: 'INVALID', stroke: 'INVALID' };
|
||||
}
|
||||
|
||||
if (startInfo.type === 'arrow_open') {
|
||||
// -- xyz --> - take arrow type form ending
|
||||
startInfo.type = info.type;
|
||||
} else {
|
||||
// x-- xyz --> - not supported
|
||||
if (startInfo.type !== info.type) return { type: 'INVALID', stroke: 'INVALID' };
|
||||
|
||||
startInfo.type = 'double_' + startInfo.type;
|
||||
}
|
||||
|
||||
if (startInfo.type === 'double_arrow') {
|
||||
startInfo.type = 'double_arrow_point';
|
||||
}
|
||||
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
export default {
|
||||
addVertex,
|
||||
addLink,
|
||||
@@ -516,6 +636,7 @@ export default {
|
||||
getDepthFirstPos,
|
||||
indexNodes,
|
||||
getSubGraphs,
|
||||
destructLink,
|
||||
lex: {
|
||||
firstGraph
|
||||
}
|
||||
|
||||
471
src/diagrams/flowchart/flowRenderer-v2.js
Normal file
@@ -0,0 +1,471 @@
|
||||
import graphlib from 'graphlib';
|
||||
import { select, curveLinear, selectAll } from 'd3';
|
||||
|
||||
import flowDb from './flowDb';
|
||||
import flow from './parser/flow';
|
||||
import { getConfig } from '../../config';
|
||||
|
||||
import { render } from '../../dagre-wrapper/index.js';
|
||||
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
|
||||
import { logger } from '../../logger';
|
||||
import common from '../common/common';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
|
||||
const conf = {};
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
conf[keys[i]] = cnf[keys[i]];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
* @param vert Object containing the vertices.
|
||||
* @param g The graph that is to be drawn.
|
||||
*/
|
||||
export const addVertices = function(vert, g, svgId) {
|
||||
const svg = select(`[id="${svgId}"]`);
|
||||
const keys = Object.keys(vert);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function(id) {
|
||||
const vertex = vert[id];
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
* @type {string}
|
||||
*/
|
||||
let classStr = 'default';
|
||||
if (vertex.classes.length > 0) {
|
||||
classStr = vertex.classes.join(' ');
|
||||
}
|
||||
|
||||
const styles = getStylesFromArray(vertex.styles);
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
|
||||
|
||||
// We create a SVG label, either by delegating to addHtmlLabel or manually
|
||||
let vertexNode;
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||
const node = {
|
||||
label: vertexText.replace(
|
||||
/fa[lrsb]?:fa-[\w-]+/g,
|
||||
s => `<i class='${s.replace(':', ' ')}'></i>`
|
||||
)
|
||||
};
|
||||
vertexNode = addHtmlLabel(svg, node).node();
|
||||
vertexNode.parentNode.removeChild(vertexNode);
|
||||
} else {
|
||||
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
|
||||
|
||||
const rows = vertexText.split(common.lineBreakRegex);
|
||||
|
||||
for (let j = 0; j < rows.length; j++) {
|
||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||
tspan.setAttribute('dy', '1em');
|
||||
tspan.setAttribute('x', '1');
|
||||
tspan.textContent = rows[j];
|
||||
svgLabel.appendChild(tspan);
|
||||
}
|
||||
vertexNode = svgLabel;
|
||||
}
|
||||
|
||||
let radious = 0;
|
||||
let _shape = '';
|
||||
// Set the shape based parameters
|
||||
switch (vertex.type) {
|
||||
case 'round':
|
||||
radious = 5;
|
||||
_shape = 'rect';
|
||||
break;
|
||||
case 'square':
|
||||
_shape = 'rect';
|
||||
break;
|
||||
case 'diamond':
|
||||
_shape = 'question';
|
||||
break;
|
||||
case 'hexagon':
|
||||
_shape = 'hexagon';
|
||||
break;
|
||||
case 'odd':
|
||||
_shape = 'rect_left_inv_arrow';
|
||||
break;
|
||||
case 'lean_right':
|
||||
_shape = 'lean_right';
|
||||
break;
|
||||
case 'lean_left':
|
||||
_shape = 'lean_left';
|
||||
break;
|
||||
case 'trapezoid':
|
||||
_shape = 'trapezoid';
|
||||
break;
|
||||
case 'inv_trapezoid':
|
||||
_shape = 'inv_trapezoid';
|
||||
break;
|
||||
case 'odd_right':
|
||||
_shape = 'rect_left_inv_arrow';
|
||||
break;
|
||||
case 'circle':
|
||||
_shape = 'circle';
|
||||
break;
|
||||
case 'ellipse':
|
||||
_shape = 'ellipse';
|
||||
break;
|
||||
case 'stadium':
|
||||
_shape = 'stadium';
|
||||
break;
|
||||
case 'subroutine':
|
||||
_shape = 'subroutine';
|
||||
break;
|
||||
case 'cylinder':
|
||||
_shape = 'cylinder';
|
||||
break;
|
||||
case 'group':
|
||||
_shape = 'rect';
|
||||
break;
|
||||
default:
|
||||
_shape = 'rect';
|
||||
}
|
||||
// Add the node
|
||||
g.setNode(vertex.id, {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: classStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
type: vertex.type,
|
||||
padding: getConfig().flowchart.padding
|
||||
});
|
||||
|
||||
logger.info('setNode', {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: classStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
type: vertex.type,
|
||||
padding: getConfig().flowchart.padding
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add edges to graph based on parsed graph defninition
|
||||
* @param {Object} edges The edges to add to the graph
|
||||
* @param {Object} g The graph object
|
||||
*/
|
||||
export const addEdges = function(edges, g) {
|
||||
let cnt = 0;
|
||||
|
||||
let defaultStyle;
|
||||
let defaultLabelStyle;
|
||||
|
||||
if (typeof edges.defaultStyle !== 'undefined') {
|
||||
const defaultStyles = getStylesFromArray(edges.defaultStyle);
|
||||
defaultStyle = defaultStyles.style;
|
||||
defaultLabelStyle = defaultStyles.labelStyle;
|
||||
}
|
||||
|
||||
edges.forEach(function(edge) {
|
||||
cnt++;
|
||||
const edgeData = {};
|
||||
edgeData.id = 'id' + cnt;
|
||||
// Set link type for rendering
|
||||
if (edge.type === 'arrow_open') {
|
||||
edgeData.arrowhead = 'none';
|
||||
} else {
|
||||
edgeData.arrowhead = 'normal';
|
||||
}
|
||||
|
||||
logger.info(edgeData, edge);
|
||||
edgeData.arrowType = edge.type;
|
||||
|
||||
let style = '';
|
||||
let labelStyle = '';
|
||||
|
||||
if (typeof edge.style !== 'undefined') {
|
||||
const styles = getStylesFromArray(edge.style);
|
||||
style = styles.style;
|
||||
labelStyle = styles.labelStyle;
|
||||
} else {
|
||||
switch (edge.stroke) {
|
||||
case 'normal':
|
||||
style = 'fill:none';
|
||||
if (typeof defaultStyle !== 'undefined') {
|
||||
style = defaultStyle;
|
||||
}
|
||||
if (typeof defaultLabelStyle !== 'undefined') {
|
||||
labelStyle = defaultLabelStyle;
|
||||
}
|
||||
break;
|
||||
case 'dotted':
|
||||
style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
|
||||
break;
|
||||
case 'thick':
|
||||
style = ' stroke-width: 3.5px;fill:none';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
edgeData.style = style;
|
||||
edgeData.labelStyle = labelStyle;
|
||||
|
||||
if (typeof edge.interpolate !== 'undefined') {
|
||||
edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
|
||||
} else if (typeof edges.defaultInterpolate !== 'undefined') {
|
||||
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
|
||||
} else {
|
||||
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
|
||||
}
|
||||
|
||||
if (typeof edge.text === 'undefined') {
|
||||
if (typeof edge.style !== 'undefined') {
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
}
|
||||
} else {
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
edgeData.labelpos = 'c';
|
||||
|
||||
if (getConfig().flowchart.htmlLabels && false) { // eslint-disable-line
|
||||
edgeData.labelType = 'html';
|
||||
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
|
||||
} else {
|
||||
edgeData.labelType = 'text';
|
||||
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
|
||||
|
||||
if (typeof edge.style === 'undefined') {
|
||||
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
|
||||
}
|
||||
|
||||
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
|
||||
}
|
||||
}
|
||||
// Add the edge to the graph
|
||||
g.setEdge(edge.start, edge.end, edgeData, cnt);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the all the styles from classDef statements in the graph definition.
|
||||
* @returns {object} classDef styles
|
||||
*/
|
||||
export const getClasses = function(text) {
|
||||
logger.info('Extracting classes');
|
||||
flowDb.clear();
|
||||
const parser = flow.parser;
|
||||
parser.yy = flowDb;
|
||||
|
||||
// Parse the graph definition
|
||||
parser.parse(text);
|
||||
return flowDb.getClasses();
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
export const draw = function(text, id) {
|
||||
logger.info('Drawing flowchart');
|
||||
flowDb.clear();
|
||||
const parser = flow.parser;
|
||||
parser.yy = flowDb;
|
||||
|
||||
// Parse the graph definition
|
||||
try {
|
||||
parser.parse(text);
|
||||
} catch (err) {
|
||||
logger.debug('Parsing failed');
|
||||
}
|
||||
|
||||
// Fetch the default direction, use TD if none was found
|
||||
let dir = flowDb.getDirection();
|
||||
if (typeof dir === 'undefined') {
|
||||
dir = 'TD';
|
||||
}
|
||||
|
||||
const conf = getConfig().flowchart;
|
||||
const nodeSpacing = conf.nodeSpacing || 50;
|
||||
const rankSpacing = conf.rankSpacing || 50;
|
||||
|
||||
// Create the input mermaid.graph
|
||||
const g = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true
|
||||
})
|
||||
.setGraph({
|
||||
rankdir: dir,
|
||||
nodesep: nodeSpacing,
|
||||
ranksep: rankSpacing,
|
||||
marginx: 8,
|
||||
marginy: 8
|
||||
})
|
||||
.setDefaultEdgeLabel(function() {
|
||||
return {};
|
||||
});
|
||||
|
||||
let subG;
|
||||
const subGraphs = flowDb.getSubGraphs();
|
||||
logger.info('Subgraphs - ', subGraphs);
|
||||
for (let i = subGraphs.length - 1; i >= 0; i--) {
|
||||
subG = subGraphs[i];
|
||||
logger.info('Subgraph - ', subG);
|
||||
flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
|
||||
}
|
||||
|
||||
// Fetch the verices/nodes and edges/links from the parsed graph definition
|
||||
const vert = flowDb.getVertices();
|
||||
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
logger.info(edges);
|
||||
let i = 0;
|
||||
for (i = subGraphs.length - 1; i >= 0; i--) {
|
||||
subG = subGraphs[i];
|
||||
|
||||
selectAll('cluster').append('text');
|
||||
|
||||
for (let j = 0; j < subG.nodes.length; j++) {
|
||||
g.setParent(subG.nodes[j], subG.id);
|
||||
}
|
||||
}
|
||||
addVertices(vert, g, id);
|
||||
addEdges(edges, g);
|
||||
|
||||
// Add custom shapes
|
||||
// flowChartShapes.addToRenderV2(addShape);
|
||||
|
||||
// Set up an SVG group so that we can translate the final graph.
|
||||
const svg = select(`[id="${id}"]`);
|
||||
|
||||
// Run the renderer. This is what draws the final graph.
|
||||
const element = select('#' + id + ' g');
|
||||
render(element, g, ['point', 'circle', 'cross'], 'flowchart', id);
|
||||
// dagre.layout(g);
|
||||
|
||||
element.selectAll('g.node').attr('title', function() {
|
||||
return flowDb.getTooltip(this.id);
|
||||
});
|
||||
|
||||
const padding = 8;
|
||||
const svgBounds = svg.node().getBBox();
|
||||
const width = svgBounds.width + padding * 2;
|
||||
const height = svgBounds.height + padding * 2;
|
||||
logger.debug(
|
||||
`new ViewBox 0 0 ${width} ${height}`,
|
||||
`translate(${padding - g._label.marginx}, ${padding - g._label.marginy})`
|
||||
);
|
||||
|
||||
if (conf.useMaxWidth) {
|
||||
svg.attr('width', '100%');
|
||||
svg.attr('style', `max-width: ${width}px;`);
|
||||
} else {
|
||||
svg.attr('height', height);
|
||||
svg.attr('width', width);
|
||||
}
|
||||
|
||||
svg.attr('viewBox', `0 0 ${width} ${height}`);
|
||||
svg
|
||||
.select('g')
|
||||
.attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`);
|
||||
|
||||
// Index nodes
|
||||
flowDb.indexNodes('subGraph' + i);
|
||||
|
||||
// // reposition labels
|
||||
// for (i = 0; i < subGraphs.length; i++) {
|
||||
// subG = subGraphs[i];
|
||||
|
||||
// if (subG.title !== 'undefined') {
|
||||
// const clusterRects = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"] rect');
|
||||
// const clusterEl = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"]');
|
||||
|
||||
// const xPos = clusterRects[0].x.baseVal.value;
|
||||
// const yPos = clusterRects[0].y.baseVal.value;
|
||||
// const width = clusterRects[0].width.baseVal.value;
|
||||
// const cluster = d3.select(clusterEl[0]);
|
||||
// const te = cluster.select('.label');
|
||||
// te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`);
|
||||
// te.attr('id', id + 'Text');
|
||||
|
||||
// for (let j = 0; j < subG.classes.length; j++) {
|
||||
// clusterEl[0].classList.add(subG.classes[j]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Add label rects for non html labels
|
||||
if (!conf.htmlLabels) {
|
||||
const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
|
||||
for (let k = 0; k < labels.length; k++) {
|
||||
const label = labels[k];
|
||||
|
||||
// Get dimensions of label
|
||||
const dim = label.getBBox();
|
||||
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.setAttribute('rx', 0);
|
||||
rect.setAttribute('ry', 0);
|
||||
rect.setAttribute('width', dim.width);
|
||||
rect.setAttribute('height', dim.height);
|
||||
rect.setAttribute('style', 'fill:#e8e8e8;');
|
||||
|
||||
label.insertBefore(rect, label.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// If node has a link, wrap it in an anchor SVG object.
|
||||
const keys = Object.keys(vert);
|
||||
keys.forEach(function(key) {
|
||||
const vertex = vert[key];
|
||||
|
||||
if (vertex.link) {
|
||||
const node = select('#' + id + ' [id="' + key + '"]');
|
||||
if (node) {
|
||||
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
|
||||
|
||||
const linkNode = node.insert(function() {
|
||||
return link;
|
||||
}, ':first-child');
|
||||
|
||||
const shape = node.select('.label-container');
|
||||
if (shape) {
|
||||
linkNode.append(function() {
|
||||
return shape.node();
|
||||
});
|
||||
}
|
||||
|
||||
const label = node.select('.label');
|
||||
if (label) {
|
||||
linkNode.append(function() {
|
||||
return label.node();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
addVertices,
|
||||
addEdges,
|
||||
getClasses,
|
||||
draw
|
||||
};
|
||||