mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge pull request #5880 from yari-dewalt/update-class-diagram
Update class diagram to v3 using new renderer
This commit is contained in:
commit
bdf145ffe3
1037
cypress/integration/rendering/classDiagram-elk-v3.spec.js
Normal file
1037
cypress/integration/rendering/classDiagram-elk-v3.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
1041
cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js
Normal file
1041
cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
1031
cypress/integration/rendering/classDiagram-v3.spec.js
Normal file
1031
cypress/integration/rendering/classDiagram-v3.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
663
cypress/platform/yari.html
Normal file
663
cypress/platform/yari.html
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1 class="header">Class Nodes</h1>
|
||||||
|
<div class="node-showcase">
|
||||||
|
<div class="test">
|
||||||
|
<h2>Basic Class</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
htmlLabels: false
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class _Duck_ {
|
||||||
|
+String beakColor
|
||||||
|
_+_swim_()a_
|
||||||
|
__+quack() test__
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Basic Class</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
htmlLabels: false
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Class10:::exClass2 {
|
||||||
|
int[] id
|
||||||
|
List~int~ ids
|
||||||
|
test(List~int~ ids) List~bool~
|
||||||
|
testArray() bool[]
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Basic Class</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
Start --> Stop
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Complex Class</h2>
|
||||||
|
<pre class="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~
|
||||||
|
Square : +getDistanceMatrix() List~List~int~~
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>No Attributes</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>No Methods</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Only Class Name</h2>
|
||||||
|
<p>Empty line as attribute</p>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
class:
|
||||||
|
hideEmptyMembersBox: false
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Visibility and Types</h2>
|
||||||
|
<p>(Further tilde testing)</p>
|
||||||
|
<div class="mermaid">
|
||||||
|
classDiagram class Duck { ~interface~~~ +String beakColor #swim() ~quack()~~~
|
||||||
|
-test()~~~~~~~ +deposit(amount) bool }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Additional Classifiers</h2>
|
||||||
|
<p>(* Abstract | $ Static)</p>
|
||||||
|
<div class="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~ Square :
|
||||||
|
+getDistanceMatrix() List~List~int~~$
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Label</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Animal~test~["Animal with a label"]
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Spacing</h2>
|
||||||
|
<p>(Fix ensures consistent spacing rules)</p>
|
||||||
|
<p>(No space or single space?)</p>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class ClassName {
|
||||||
|
-attribute:type
|
||||||
|
- attribute : type
|
||||||
|
test
|
||||||
|
|
||||||
|
+ GetAttribute() type
|
||||||
|
+ GetAttribute() type
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Annotation</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
<<interface>> Shape
|
||||||
|
Shape : noOfVertices
|
||||||
|
Shape : draw()
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Long Class Name Text</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class ThisIsATestForALongClassName {
|
||||||
|
<<interface>>
|
||||||
|
noOfLetters
|
||||||
|
delete()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Long Annotation Text</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
<<superlongannotationtext>> Shape
|
||||||
|
Shape : noOfVertices
|
||||||
|
Shape : draw()
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Long Member Text</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
<<interface>> Shape
|
||||||
|
Shape : noOfVertices
|
||||||
|
Shape : longtexttestkeepgoingandgoing
|
||||||
|
Shape : draw()
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Link</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
link Shape "https://www.github.com" "This is a tooltip for a link"
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Click</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
click Shape href "https://www.github.com" "This is a tooltip for a link"
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Hand Drawn</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
look: handDrawn
|
||||||
|
htmlLabels: true
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Hand {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
style Hand fill:#f9f,stroke:#29f,stroke-width:2px
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Neutral Theme</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
theme: neutral
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Dark Theme</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
theme: dark
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Forest Theme</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
theme: forest
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Base Theme</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
theme: base
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Custom Theme</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
%%{
|
||||||
|
init: {
|
||||||
|
'theme': 'base',
|
||||||
|
'themeVariables': {
|
||||||
|
'primaryColor': '#BB2528',
|
||||||
|
'primaryTextColor': '#fff',
|
||||||
|
'primaryBorderColor': '#7C0000',
|
||||||
|
'lineColor': '#F83d29',
|
||||||
|
'secondaryColor': '#006100',
|
||||||
|
'tertiaryColor': '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}%%
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
Duck--Dog
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Styling within Diagram</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
style Duck fill:#f9f,stroke:#333,stroke-width:8px
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Styling with classDef Statement</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Duck:::bold {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dog {
|
||||||
|
+int numTeeth
|
||||||
|
+bark()
|
||||||
|
}
|
||||||
|
|
||||||
|
cssClass "Duck,Dog" pink
|
||||||
|
|
||||||
|
classDef pink fill:#f9f
|
||||||
|
classDef default color:#f1e
|
||||||
|
classDef bold stroke:#333,stroke-width:6px,color:#fff
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Styling with Class in Stylesheet</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Duck {
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
class Duck:::styleClass
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 class="header">Diagram Testing</h1>
|
||||||
|
<div class="diagram-showcase">
|
||||||
|
<div class="test">
|
||||||
|
<h2>Class Nodes Only</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
title: Animal example
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Class Nodes LR</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
title: Animal example
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
direction LR
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Relations</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
classA <|-- classB
|
||||||
|
classC *-- classD
|
||||||
|
classE o-- classF
|
||||||
|
classG <-- classH
|
||||||
|
classI -- classJ
|
||||||
|
classK <.. classL
|
||||||
|
classM <|.. classN
|
||||||
|
classO .. classP
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Two Way Relation</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Animal {
|
||||||
|
int size
|
||||||
|
walk()
|
||||||
|
}
|
||||||
|
class Zebra {
|
||||||
|
int size
|
||||||
|
walk()
|
||||||
|
}
|
||||||
|
Animal o--|> Zebra
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Relations with Labels</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
classA <|-- classB : implements
|
||||||
|
classC *-- classD : composition
|
||||||
|
classE o-- classF : aggregation
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Cardinality / Multiplicity</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
Customer "1" --> "*" Ticket
|
||||||
|
Student "1" --> "1..*" Course
|
||||||
|
Galaxy --> "many" Star : Contains
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Complex Relations with Theme</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
theme: forest
|
||||||
|
look: handDrawns
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
direction RL
|
||||||
|
class Student {
|
||||||
|
-idCard : IdCard
|
||||||
|
}
|
||||||
|
class IdCard{
|
||||||
|
-id : int
|
||||||
|
-name : string
|
||||||
|
}
|
||||||
|
class Bike{
|
||||||
|
-id : int
|
||||||
|
-name : string
|
||||||
|
}
|
||||||
|
Student "1" o--o "1" IdCard : carries
|
||||||
|
Student "1" o--o "1" Bike : rides
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Notes</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
note "This is a general note"
|
||||||
|
note for MyClass "This is a note for a class"
|
||||||
|
class MyClass
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Namespaces</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
namespace BaseShapes {
|
||||||
|
class Triangle
|
||||||
|
class Rectangle {
|
||||||
|
double width
|
||||||
|
double height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Namespaces</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
namespace Namespace1 {
|
||||||
|
class C1
|
||||||
|
class C2
|
||||||
|
}
|
||||||
|
C1 --> C2
|
||||||
|
class C3
|
||||||
|
class C4
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Full Example</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
title: Animal example
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
note "From Duck till Zebra"
|
||||||
|
Animal <|--|> Duck
|
||||||
|
note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
cssClass "Duck" test
|
||||||
|
classDef test fill:#f71
|
||||||
|
%%classDef default fill:#f93
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<h2>Full Example</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
theme: forest
|
||||||
|
look: handDrawn
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
note for Outside "Note testing"
|
||||||
|
namespace Test {
|
||||||
|
class Outside
|
||||||
|
}
|
||||||
|
namespace BaseShapes {
|
||||||
|
class Triangle
|
||||||
|
class Rectangle {
|
||||||
|
double width
|
||||||
|
double height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Outside <|--|> Rectangle
|
||||||
|
style Triangle fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
look: handDrawn
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
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
|
||||||
|
NewClass ()--() Class04
|
||||||
|
Class09 <|--|> AveryLongClass
|
||||||
|
Class07 : equals()
|
||||||
|
Class07 : Object[] elementData
|
||||||
|
Class01 : size()
|
||||||
|
Class01 : int chimp
|
||||||
|
Class01 : int gorilla
|
||||||
|
Class08 "1" <--> "*" C2: Cool label
|
||||||
|
class Class10 {
|
||||||
|
<<service>>
|
||||||
|
int id
|
||||||
|
test()
|
||||||
|
}
|
||||||
|
Class10 o--o AveryLongClass
|
||||||
|
Class10 <--> Class07
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<pre class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
test ()--() test2
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
mermaid.initialize();
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('In parse error:');
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.node-showcase {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
.test {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.test > h2 {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.test > p {
|
||||||
|
margin-top: -6px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram-showcase {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styleClass > * > path {
|
||||||
|
fill: #ff0000 !important;
|
||||||
|
stroke: #ffff00 !important;
|
||||||
|
stroke-width: 4px !important;
|
||||||
|
stroke-dasharray: 2 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/rendering-util/types.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L125)
|
[packages/mermaid/src/rendering-util/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L128)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/rendering-util/types.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L124)
|
[packages/mermaid/src/rendering-util/types.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L127)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -40,4 +40,4 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/rendering-util/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L123)
|
[packages/mermaid/src/rendering-util/types.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L126)
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/defaultConfig.ts:267](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L267)
|
[packages/mermaid/src/defaultConfig.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L270)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -427,6 +427,51 @@ And `Link` can be one of:
|
|||||||
| -- | Solid |
|
| -- | Solid |
|
||||||
| .. | Dashed |
|
| .. | Dashed |
|
||||||
|
|
||||||
|
### Lollipop Interfaces
|
||||||
|
|
||||||
|
Classes can also be given a special relation type that defines a lollipop interface on the class. A lollipop interface is defined using the following syntax:
|
||||||
|
|
||||||
|
- `bar ()-- foo`
|
||||||
|
- `foo --() bar`
|
||||||
|
|
||||||
|
The interface (bar) with the lollipop connects to the class (foo).
|
||||||
|
|
||||||
|
Note: Each interface that is defined is unique and is meant to not be shared between classes / have multiple edges connecting to it.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
bar ()-- foo
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
bar ()-- foo
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Class01 {
|
||||||
|
int amount
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
Class01 --() bar
|
||||||
|
Class02 --() bar
|
||||||
|
|
||||||
|
foo ()-- Class01
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Class01 {
|
||||||
|
int amount
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
Class01 --() bar
|
||||||
|
Class02 --() bar
|
||||||
|
|
||||||
|
foo ()-- Class01
|
||||||
|
```
|
||||||
|
|
||||||
## Define Namespace
|
## Define Namespace
|
||||||
|
|
||||||
A namespace groups classes.
|
A namespace groups classes.
|
||||||
@ -776,10 +821,12 @@ Beginner's tip—a full example using interactive links in an HTML page:
|
|||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
### Styling a node (v10.7.0+)
|
### Styling a node
|
||||||
|
|
||||||
It is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.
|
It is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.
|
||||||
|
|
||||||
|
Note that notes and namespaces cannot be styled individually but do support themes.
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
classDiagram
|
classDiagram
|
||||||
class Animal
|
class Animal
|
||||||
@ -799,11 +846,102 @@ classDiagram
|
|||||||
#### Classes
|
#### Classes
|
||||||
|
|
||||||
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
|
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
|
||||||
should have a different look. This is done by predefining classes in css styles that can be applied from the graph definition using the `cssClass` statement or the `:::` short hand.
|
should have a different look.
|
||||||
|
|
||||||
|
A class definition looks like the example below:
|
||||||
|
|
||||||
|
```
|
||||||
|
classDef className fill:#f9f,stroke:#333,stroke-width:4px;
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, it is possible to define style to multiple classes in one statement:
|
||||||
|
|
||||||
|
```
|
||||||
|
classDef firstClassName,secondClassName font-size:12pt;
|
||||||
|
```
|
||||||
|
|
||||||
|
Attachment of a class to a node is done as per below:
|
||||||
|
|
||||||
|
```
|
||||||
|
cssClass "nodeId1" className;
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to attach a class to a list of nodes in one statement:
|
||||||
|
|
||||||
|
```
|
||||||
|
cssClass "nodeId1,nodeId2" className;
|
||||||
|
```
|
||||||
|
|
||||||
|
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Animal:::someclass
|
||||||
|
classDef someclass fill:#f96
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Animal:::someclass
|
||||||
|
classDef someclass fill:#f96
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Animal:::someclass {
|
||||||
|
-int sizeInFeet
|
||||||
|
-canEat()
|
||||||
|
}
|
||||||
|
classDef someclass fill:#f96
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Animal:::someclass {
|
||||||
|
-int sizeInFeet
|
||||||
|
-canEat()
|
||||||
|
}
|
||||||
|
classDef someclass fill:#f96
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default class
|
||||||
|
|
||||||
|
If a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.
|
||||||
|
|
||||||
|
```
|
||||||
|
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Animal:::pink
|
||||||
|
class Mineral
|
||||||
|
|
||||||
|
classDef default fill:#f96,color:red
|
||||||
|
classDef pink color:#f9f
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Animal:::pink
|
||||||
|
class Mineral
|
||||||
|
|
||||||
|
classDef default fill:#f96,color:red
|
||||||
|
classDef pink color:#f9f
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Classes
|
||||||
|
|
||||||
|
It is also possible to predefine classes in CSS styles that can be applied from the graph definition as in the example
|
||||||
|
below:
|
||||||
|
|
||||||
|
**Example style**
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<style>
|
<style>
|
||||||
.styleClass > rect {
|
.styleClass > * > g {
|
||||||
fill: #ff0000;
|
fill: #ff0000;
|
||||||
stroke: #ffff00;
|
stroke: #ffff00;
|
||||||
stroke-width: 4px;
|
stroke-width: 4px;
|
||||||
@ -811,19 +949,7 @@ should have a different look. This is done by predefining classes in css styles
|
|||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then attaching that class to a specific node:
|
**Example definition**
|
||||||
|
|
||||||
```
|
|
||||||
cssClass "nodeId1" styleClass;
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also possible to attach a class to a list of nodes in one statement:
|
|
||||||
|
|
||||||
```
|
|
||||||
cssClass "nodeId1,nodeId2" styleClass;
|
|
||||||
```
|
|
||||||
|
|
||||||
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
|
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
classDiagram
|
classDiagram
|
||||||
@ -835,136 +961,32 @@ classDiagram
|
|||||||
class Animal:::styleClass
|
class Animal:::styleClass
|
||||||
```
|
```
|
||||||
|
|
||||||
Or:
|
> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
|
||||||
|
|
||||||
```mermaid-example
|
|
||||||
classDiagram
|
|
||||||
class Animal:::styleClass {
|
|
||||||
-int sizeInFeet
|
|
||||||
-canEat()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
classDiagram
|
|
||||||
class Animal:::styleClass {
|
|
||||||
-int sizeInFeet
|
|
||||||
-canEat()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
?> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
|
|
||||||
|
|
||||||
?> Due to limitations with existing markup for class diagrams, it is not currently possible to define css classes within the diagram itself. **_Coming soon!_**
|
|
||||||
|
|
||||||
### Default Styles
|
|
||||||
|
|
||||||
The main styling of the class diagram is done with a preset number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss. The classes used here are described below:
|
|
||||||
|
|
||||||
| 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 line | Styles for class diagram line |
|
|
||||||
| .classLabel .box | Styles for class label box |
|
|
||||||
| .classLabel .label | Styles for class label text |
|
|
||||||
| composition | Styles for composition arrow head and arrow line |
|
|
||||||
| aggregation | Styles for aggregation arrow head and arrow line(dashed or solid) |
|
|
||||||
| dependency | Styles for dependency arrow head and arrow line |
|
|
||||||
|
|
||||||
#### Sample stylesheet
|
|
||||||
|
|
||||||
```scss
|
|
||||||
body {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.classGroup text {
|
|
||||||
fill: $nodeBorder;
|
|
||||||
stroke: none;
|
|
||||||
font-family: 'trebuchet ms', verdana, arial;
|
|
||||||
font-family: var(--mermaid-font-family);
|
|
||||||
font-size: 10px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.classGroup rect {
|
|
||||||
fill: $nodeBkg;
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.classGroup line {
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classLabel .box {
|
|
||||||
stroke: none;
|
|
||||||
stroke-width: 0;
|
|
||||||
fill: $nodeBkg;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classLabel .label {
|
|
||||||
fill: $nodeBorder;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relation {
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin composition {
|
|
||||||
fill: $nodeBorder;
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#compositionStart {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#compositionEnd {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin aggregation {
|
|
||||||
fill: $nodeBkg;
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#aggregationStart {
|
|
||||||
@include aggregation;
|
|
||||||
}
|
|
||||||
|
|
||||||
#aggregationEnd {
|
|
||||||
@include aggregation;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dependencyStart {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dependencyEnd {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#extensionStart {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#extensionEnd {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`Coming soon!`
|
### Members Box
|
||||||
|
|
||||||
|
It is possible to hide the empty members box of a class node.
|
||||||
|
|
||||||
|
This is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html)
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
class:
|
||||||
|
hideEmptyMembersBox: true
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
class:
|
||||||
|
hideEmptyMembersBox: true
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck
|
||||||
|
```
|
||||||
|
@ -319,6 +319,7 @@ Below is a comprehensive list of the newly introduced shapes and their correspon
|
|||||||
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||||
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||||
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
||||||
|
| Class Box | Class Box | `classBox` | Class Box | `class-box` |
|
||||||
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
|
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
|
||||||
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
||||||
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
|
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
|
||||||
|
@ -149,6 +149,7 @@ export const render = async (
|
|||||||
const clusterNode = JSON.parse(JSON.stringify(node));
|
const clusterNode = JSON.parse(JSON.stringify(node));
|
||||||
clusterNode.x = node.offset.posX + node.width / 2;
|
clusterNode.x = node.offset.posX + node.width / 2;
|
||||||
clusterNode.y = node.offset.posY + node.height / 2;
|
clusterNode.y = node.offset.posY + node.height / 2;
|
||||||
|
clusterNode.width = Math.max(clusterNode.width, node.labelData.width);
|
||||||
await insertCluster(subgraphEl, clusterNode);
|
await insertCluster(subgraphEl, clusterNode);
|
||||||
|
|
||||||
log.debug('Id (UIO)= ', node.id, node.width, node.shape, node.labels);
|
log.debug('Id (UIO)= ', node.id, node.width, node.shape, node.labels);
|
||||||
@ -275,6 +276,8 @@ export const render = async (
|
|||||||
interpolate: undefined;
|
interpolate: undefined;
|
||||||
style: undefined;
|
style: undefined;
|
||||||
labelType: any;
|
labelType: any;
|
||||||
|
startLabelRight?: string;
|
||||||
|
endLabelLeft?: string;
|
||||||
}) {
|
}) {
|
||||||
// Identify Link
|
// Identify Link
|
||||||
const linkIdBase = edge.id; // 'L-' + edge.start + '-' + edge.end;
|
const linkIdBase = edge.id; // 'L-' + edge.start + '-' + edge.end;
|
||||||
@ -328,6 +331,9 @@ export const render = async (
|
|||||||
let style = '';
|
let style = '';
|
||||||
let labelStyle = '';
|
let labelStyle = '';
|
||||||
|
|
||||||
|
edgeData.startLabelRight = edge.startLabelRight;
|
||||||
|
edgeData.endLabelLeft = edge.endLabelLeft;
|
||||||
|
|
||||||
switch (edge.stroke) {
|
switch (edge.stroke) {
|
||||||
case 'normal':
|
case 'normal':
|
||||||
style = 'fill:none;';
|
style = 'fill:none;';
|
||||||
|
@ -172,6 +172,7 @@ This Markdown should be kept.
|
|||||||
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||||
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||||
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
|
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
|
||||||
|
| Class Box | Class Box | \`classBox\` | Class Box | \`class-box\` |
|
||||||
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
|
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
|
||||||
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
|
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
|
||||||
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
|
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
|
||||||
|
@ -717,6 +717,7 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
|
|||||||
*/
|
*/
|
||||||
diagramPadding?: number;
|
diagramPadding?: number;
|
||||||
htmlLabels?: boolean;
|
htmlLabels?: boolean;
|
||||||
|
hideEmptyMembersBox?: boolean;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* The object containing configurations specific for entity relationship diagrams
|
* The object containing configurations specific for entity relationship diagrams
|
||||||
|
@ -53,6 +53,9 @@ const config: RequiredDeep<MermaidConfig> = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
class: {
|
||||||
|
hideEmptyMembersBox: false,
|
||||||
|
},
|
||||||
gantt: {
|
gantt: {
|
||||||
...defaultConfigJson.gantt,
|
...defaultConfigJson.gantt,
|
||||||
tickInterval: undefined,
|
tickInterval: undefined,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import type { Selection } from 'd3';
|
import { select, type Selection } from 'd3';
|
||||||
import { select } from 'd3';
|
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
import common from '../common/common.js';
|
import common from '../common/common.js';
|
||||||
import utils from '../../utils.js';
|
import utils, { getEdgeId } from '../../utils.js';
|
||||||
import {
|
import {
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
getAccTitle,
|
getAccTitle,
|
||||||
@ -21,13 +20,18 @@ import type {
|
|||||||
ClassMap,
|
ClassMap,
|
||||||
NamespaceMap,
|
NamespaceMap,
|
||||||
NamespaceNode,
|
NamespaceNode,
|
||||||
|
StyleClass,
|
||||||
|
Interface,
|
||||||
} from './classTypes.js';
|
} from './classTypes.js';
|
||||||
|
import type { Node, Edge } from '../../rendering-util/types.js';
|
||||||
|
|
||||||
const MERMAID_DOM_ID_PREFIX = 'classId-';
|
const MERMAID_DOM_ID_PREFIX = 'classId-';
|
||||||
|
|
||||||
let relations: ClassRelation[] = [];
|
let relations: ClassRelation[] = [];
|
||||||
let classes = new Map<string, ClassNode>();
|
let classes = new Map<string, ClassNode>();
|
||||||
|
const styleClasses = new Map<string, StyleClass>();
|
||||||
let notes: ClassNote[] = [];
|
let notes: ClassNote[] = [];
|
||||||
|
let interfaces: Interface[] = [];
|
||||||
let classCounter = 0;
|
let classCounter = 0;
|
||||||
let namespaces = new Map<string, NamespaceNode>();
|
let namespaces = new Map<string, NamespaceNode>();
|
||||||
let namespaceCounter = 0;
|
let namespaceCounter = 0;
|
||||||
@ -58,6 +62,8 @@ export const setClassLabel = function (_id: string, label: string) {
|
|||||||
|
|
||||||
const { className } = splitClassNameAndType(id);
|
const { className } = splitClassNameAndType(id);
|
||||||
classes.get(className)!.label = label;
|
classes.get(className)!.label = label;
|
||||||
|
classes.get(className)!.text =
|
||||||
|
`${label}${classes.get(className)!.type ? `<${classes.get(className)!.type}>` : ''}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,7 +86,9 @@ export const addClass = function (_id: string) {
|
|||||||
id: name,
|
id: name,
|
||||||
type: type,
|
type: type,
|
||||||
label: name,
|
label: name,
|
||||||
cssClasses: [],
|
text: `${name}${type ? `<${type}>` : ''}`,
|
||||||
|
shape: 'classBox',
|
||||||
|
cssClasses: 'default',
|
||||||
methods: [],
|
methods: [],
|
||||||
members: [],
|
members: [],
|
||||||
annotations: [],
|
annotations: [],
|
||||||
@ -91,6 +99,16 @@ export const addClass = function (_id: string) {
|
|||||||
classCounter++;
|
classCounter++;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addInterface = function (label: string, classId: string) {
|
||||||
|
const classInterface: Interface = {
|
||||||
|
id: `interface${interfaces.length}`,
|
||||||
|
label,
|
||||||
|
classId,
|
||||||
|
};
|
||||||
|
|
||||||
|
interfaces.push(classInterface);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to lookup domId from id in the graph definition.
|
* Function to lookup domId from id in the graph definition.
|
||||||
*
|
*
|
||||||
@ -109,6 +127,7 @@ export const clear = function () {
|
|||||||
relations = [];
|
relations = [];
|
||||||
classes = new Map();
|
classes = new Map();
|
||||||
notes = [];
|
notes = [];
|
||||||
|
interfaces = [];
|
||||||
functions = [];
|
functions = [];
|
||||||
functions.push(setupToolTips);
|
functions.push(setupToolTips);
|
||||||
namespaces = new Map();
|
namespaces = new Map();
|
||||||
@ -133,19 +152,50 @@ export const getNotes = function () {
|
|||||||
return notes;
|
return notes;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addRelation = function (relation: ClassRelation) {
|
export const addRelation = function (classRelation: ClassRelation) {
|
||||||
log.debug('Adding relation: ' + JSON.stringify(relation));
|
log.debug('Adding relation: ' + JSON.stringify(classRelation));
|
||||||
addClass(relation.id1);
|
// Due to relationType cannot just check if it is equal to 'none' or it complains, can fix this later
|
||||||
addClass(relation.id2);
|
const invalidTypes = [
|
||||||
|
relationType.LOLLIPOP,
|
||||||
|
relationType.AGGREGATION,
|
||||||
|
relationType.COMPOSITION,
|
||||||
|
relationType.DEPENDENCY,
|
||||||
|
relationType.EXTENSION,
|
||||||
|
];
|
||||||
|
|
||||||
relation.id1 = splitClassNameAndType(relation.id1).className;
|
if (
|
||||||
relation.id2 = splitClassNameAndType(relation.id2).className;
|
classRelation.relation.type1 === relationType.LOLLIPOP &&
|
||||||
|
!invalidTypes.includes(classRelation.relation.type2)
|
||||||
|
) {
|
||||||
|
addClass(classRelation.id2);
|
||||||
|
addInterface(classRelation.id1, classRelation.id2);
|
||||||
|
classRelation.id1 = `interface${interfaces.length - 1}`;
|
||||||
|
} else if (
|
||||||
|
classRelation.relation.type2 === relationType.LOLLIPOP &&
|
||||||
|
!invalidTypes.includes(classRelation.relation.type1)
|
||||||
|
) {
|
||||||
|
addClass(classRelation.id1);
|
||||||
|
addInterface(classRelation.id2, classRelation.id1);
|
||||||
|
classRelation.id2 = `interface${interfaces.length - 1}`;
|
||||||
|
} else {
|
||||||
|
addClass(classRelation.id1);
|
||||||
|
addClass(classRelation.id2);
|
||||||
|
}
|
||||||
|
|
||||||
relation.relationTitle1 = common.sanitizeText(relation.relationTitle1.trim(), getConfig());
|
classRelation.id1 = splitClassNameAndType(classRelation.id1).className;
|
||||||
|
classRelation.id2 = splitClassNameAndType(classRelation.id2).className;
|
||||||
|
|
||||||
relation.relationTitle2 = common.sanitizeText(relation.relationTitle2.trim(), getConfig());
|
classRelation.relationTitle1 = common.sanitizeText(
|
||||||
|
classRelation.relationTitle1.trim(),
|
||||||
|
getConfig()
|
||||||
|
);
|
||||||
|
|
||||||
relations.push(relation);
|
classRelation.relationTitle2 = common.sanitizeText(
|
||||||
|
classRelation.relationTitle2.trim(),
|
||||||
|
getConfig()
|
||||||
|
);
|
||||||
|
|
||||||
|
relations.push(classRelation);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -229,11 +279,37 @@ export const setCssClass = function (ids: string, className: string) {
|
|||||||
}
|
}
|
||||||
const classNode = classes.get(id);
|
const classNode = classes.get(id);
|
||||||
if (classNode) {
|
if (classNode) {
|
||||||
classNode.cssClasses.push(className);
|
classNode.cssClasses += ' ' + className;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defineClass = function (ids: string[], style: string[]) {
|
||||||
|
for (const id of ids) {
|
||||||
|
let styleClass = styleClasses.get(id);
|
||||||
|
if (styleClass === undefined) {
|
||||||
|
styleClass = { id, styles: [], textStyles: [] };
|
||||||
|
styleClasses.set(id, styleClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style) {
|
||||||
|
style.forEach(function (s) {
|
||||||
|
if (/color/.exec(s)) {
|
||||||
|
const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
|
||||||
|
styleClass.textStyles.push(newStyle);
|
||||||
|
}
|
||||||
|
styleClass.styles.push(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
classes.forEach((value) => {
|
||||||
|
if (value.cssClasses.includes(id)) {
|
||||||
|
value.styles.push(...style.flatMap((s) => s.split(',')));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by parser when a tooltip is found, e.g. a clickable element.
|
* Called by parser when a tooltip is found, e.g. a clickable element.
|
||||||
*
|
*
|
||||||
@ -472,6 +548,152 @@ export const setCssStyle = function (id: string, styles: string[]) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the arrow marker for a type index
|
||||||
|
*
|
||||||
|
* @param type - The type to look for
|
||||||
|
* @returns The arrow marker
|
||||||
|
*/
|
||||||
|
function getArrowMarker(type: number) {
|
||||||
|
let marker;
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
marker = 'aggregation';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
marker = 'extension';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
marker = 'composition';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
marker = 'dependency';
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
marker = 'lollipop';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
marker = 'none';
|
||||||
|
}
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getData = () => {
|
||||||
|
const nodes: Node[] = [];
|
||||||
|
const edges: Edge[] = [];
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
for (const namespaceKey of namespaces.keys()) {
|
||||||
|
const namespace = namespaces.get(namespaceKey);
|
||||||
|
if (namespace) {
|
||||||
|
const node: Node = {
|
||||||
|
id: namespace.id,
|
||||||
|
label: namespace.id,
|
||||||
|
isGroup: true,
|
||||||
|
padding: config.class!.padding ?? 16,
|
||||||
|
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
||||||
|
shape: 'rect',
|
||||||
|
cssStyles: ['fill: none', 'stroke: black'],
|
||||||
|
look: config.look,
|
||||||
|
};
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const classKey of classes.keys()) {
|
||||||
|
const classNode = classes.get(classKey);
|
||||||
|
if (classNode) {
|
||||||
|
const node = classNode as unknown as Node;
|
||||||
|
node.parentId = classNode.parent;
|
||||||
|
node.look = config.look;
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cnt = 0;
|
||||||
|
for (const note of notes) {
|
||||||
|
cnt++;
|
||||||
|
const noteNode: Node = {
|
||||||
|
id: note.id,
|
||||||
|
label: note.text,
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'note',
|
||||||
|
padding: config.class!.padding ?? 6,
|
||||||
|
cssStyles: [
|
||||||
|
'text-align: left',
|
||||||
|
'white-space: nowrap',
|
||||||
|
`fill: ${config.themeVariables.noteBkgColor}`,
|
||||||
|
`stroke: ${config.themeVariables.noteBorderColor}`,
|
||||||
|
],
|
||||||
|
look: config.look,
|
||||||
|
};
|
||||||
|
nodes.push(noteNode);
|
||||||
|
|
||||||
|
const noteClassId = classes.get(note.class)?.id ?? '';
|
||||||
|
|
||||||
|
if (noteClassId) {
|
||||||
|
const edge: Edge = {
|
||||||
|
id: `edgeNote${cnt}`,
|
||||||
|
start: note.id,
|
||||||
|
end: noteClassId,
|
||||||
|
type: 'normal',
|
||||||
|
thickness: 'normal',
|
||||||
|
classes: 'relation',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
arrowTypeEnd: 'none',
|
||||||
|
arrowheadStyle: '',
|
||||||
|
labelStyle: [''],
|
||||||
|
style: ['fill: none'],
|
||||||
|
pattern: 'dotted',
|
||||||
|
look: config.look,
|
||||||
|
};
|
||||||
|
edges.push(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const _interface of interfaces) {
|
||||||
|
const interfaceNode: Node = {
|
||||||
|
id: _interface.id,
|
||||||
|
label: _interface.label,
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect',
|
||||||
|
cssStyles: ['opacity: 0;'],
|
||||||
|
look: config.look,
|
||||||
|
};
|
||||||
|
nodes.push(interfaceNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = 0;
|
||||||
|
for (const classRelation of relations) {
|
||||||
|
cnt++;
|
||||||
|
const edge: Edge = {
|
||||||
|
id: getEdgeId(classRelation.id1, classRelation.id2, {
|
||||||
|
prefix: 'id',
|
||||||
|
counter: cnt,
|
||||||
|
}),
|
||||||
|
start: classRelation.id1,
|
||||||
|
end: classRelation.id2,
|
||||||
|
type: 'normal',
|
||||||
|
label: classRelation.title,
|
||||||
|
labelpos: 'c',
|
||||||
|
thickness: 'normal',
|
||||||
|
classes: 'relation',
|
||||||
|
arrowTypeStart: getArrowMarker(classRelation.relation.type1),
|
||||||
|
arrowTypeEnd: getArrowMarker(classRelation.relation.type2),
|
||||||
|
startLabelRight: classRelation.relationTitle1 === 'none' ? '' : classRelation.relationTitle1,
|
||||||
|
endLabelLeft: classRelation.relationTitle2 === 'none' ? '' : classRelation.relationTitle2,
|
||||||
|
arrowheadStyle: '',
|
||||||
|
labelStyle: ['display: inline-block'],
|
||||||
|
style: classRelation.style || '',
|
||||||
|
pattern: classRelation.relation.lineType == 1 ? 'dashed' : 'solid',
|
||||||
|
look: config.look,
|
||||||
|
};
|
||||||
|
edges.push(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nodes, edges, other: {}, config, direction: getDirection() };
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
getAccTitle,
|
getAccTitle,
|
||||||
@ -497,6 +719,7 @@ export default {
|
|||||||
relationType,
|
relationType,
|
||||||
setClickEvent,
|
setClickEvent,
|
||||||
setCssClass,
|
setCssClass,
|
||||||
|
defineClass,
|
||||||
setLink,
|
setLink,
|
||||||
getTooltip,
|
getTooltip,
|
||||||
setTooltip,
|
setTooltip,
|
||||||
@ -509,4 +732,5 @@ export default {
|
|||||||
getNamespace,
|
getNamespace,
|
||||||
getNamespaces,
|
getNamespaces,
|
||||||
setCssStyle,
|
setCssStyle,
|
||||||
|
getData,
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ describe('class diagram, ', function () {
|
|||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be possible to apply a css class to a class directly with struct', function () {
|
it('should be possible to apply a css class to a class directly with struct', function () {
|
||||||
@ -28,7 +28,7 @@ describe('class diagram, ', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.cssClasses[0]).toBe('exClass');
|
expect(testClass.cssClasses).toBe('default exClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be possible to apply a css class to a class with relations', function () {
|
it('should be possible to apply a css class to a class with relations', function () {
|
||||||
@ -36,7 +36,7 @@ describe('class diagram, ', function () {
|
|||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be possible to apply a cssClass to a class', function () {
|
it('should be possible to apply a cssClass to a class', function () {
|
||||||
@ -44,7 +44,7 @@ describe('class diagram, ', function () {
|
|||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be possible to apply a cssClass to a comma separated list of classes', function () {
|
it('should be possible to apply a cssClass to a comma separated list of classes', function () {
|
||||||
@ -53,8 +53,8 @@ describe('class diagram, ', function () {
|
|||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
|
|
||||||
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
|
||||||
expect(parser.yy.getClass('Class02').cssClasses[0]).toBe('exClass');
|
expect(parser.yy.getClass('Class02').cssClasses).toBe('default exClass');
|
||||||
});
|
});
|
||||||
it('should be possible to apply a style to an individual node', function () {
|
it('should be possible to apply a style to an individual node', function () {
|
||||||
const str =
|
const str =
|
||||||
@ -69,5 +69,47 @@ describe('class diagram, ', function () {
|
|||||||
expect(styleElements[1]).toBe('stroke:#333');
|
expect(styleElements[1]).toBe('stroke:#333');
|
||||||
expect(styleElements[2]).toBe('stroke-width:4px');
|
expect(styleElements[2]).toBe('stroke-width:4px');
|
||||||
});
|
});
|
||||||
|
it('should be possible to define and assign a class inside the diagram', function () {
|
||||||
|
const str =
|
||||||
|
'classDiagram\n' + 'class Class01\n cssClass "Class01" pink\n classDef pink fill:#f9f';
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
|
||||||
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default pink');
|
||||||
|
});
|
||||||
|
it('should be possible to define and assign a class using shorthand inside the diagram', function () {
|
||||||
|
const str = 'classDiagram\n' + 'class Class01:::pink\n classDef pink fill:#f9f';
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
|
||||||
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default pink');
|
||||||
|
});
|
||||||
|
it('should properly assign styles from a class defined inside the diagram', function () {
|
||||||
|
const str =
|
||||||
|
'classDiagram\n' +
|
||||||
|
'class Class01:::pink\n classDef pink fill:#f9f,stroke:#333,stroke-width:6px';
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
|
||||||
|
expect(parser.yy.getClass('Class01').styles).toStrictEqual([
|
||||||
|
'fill:#f9f',
|
||||||
|
'stroke:#333',
|
||||||
|
'stroke-width:6px',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('should properly assign multiple classes and styles from classes defined inside the diagram', function () {
|
||||||
|
const str =
|
||||||
|
'classDiagram\n' +
|
||||||
|
'class Class01:::pink\n cssClass "Class01" bold\n classDef pink fill:#f9f\n classDef bold stroke:#333,stroke-width:6px';
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
|
||||||
|
expect(parser.yy.getClass('Class01').styles).toStrictEqual([
|
||||||
|
'fill:#f9f',
|
||||||
|
'stroke:#333',
|
||||||
|
'stroke-width:6px',
|
||||||
|
]);
|
||||||
|
expect(parser.yy.getClass('Class01').cssClasses).toBe('default pink bold');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
|
|||||||
import parser from './parser/classDiagram.jison';
|
import parser from './parser/classDiagram.jison';
|
||||||
import db from './classDb.js';
|
import db from './classDb.js';
|
||||||
import styles from './styles.js';
|
import styles from './styles.js';
|
||||||
import renderer from './classRenderer-v2.js';
|
import renderer from './classRenderer-v3-unified.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
parser,
|
parser,
|
||||||
|
@ -246,7 +246,7 @@ describe('given a basic class diagram, ', function () {
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a class with text label and css class', () => {
|
it('should parse a class with text label and css class', () => {
|
||||||
@ -261,7 +261,7 @@ describe('given a basic class diagram, ', function () {
|
|||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
|
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse two classes with text labels and css classes', () => {
|
it('should parse two classes with text labels and css classes', () => {
|
||||||
@ -276,11 +276,11 @@ describe('given a basic class diagram, ', function () {
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
|
|
||||||
const c2 = classDb.getClass('C2');
|
const c2 = classDb.getClass('C2');
|
||||||
expect(c2.label).toBe('Long long long long long long long long long long label');
|
expect(c2.label).toBe('Long long long long long long long long long long label');
|
||||||
expect(c2.cssClasses[0]).toBe('styleClass');
|
expect(c2.cssClasses).toBe('default styleClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse two classes with text labels and css class shorthands', () => {
|
it('should parse two classes with text labels and css class shorthands', () => {
|
||||||
@ -293,11 +293,11 @@ describe('given a basic class diagram, ', function () {
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass1');
|
expect(c1.cssClasses).toBe('default styleClass1');
|
||||||
|
|
||||||
const c2 = classDb.getClass('C2');
|
const c2 = classDb.getClass('C2');
|
||||||
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
||||||
expect(c2.cssClasses[0]).toBe('styleClass2');
|
expect(c2.cssClasses).toBe('default styleClass2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse multiple classes with same text labels', () => {
|
it('should parse multiple classes with same text labels', () => {
|
||||||
@ -494,10 +494,32 @@ class C13["With Città foreign language"]
|
|||||||
],
|
],
|
||||||
methods: [],
|
methods: [],
|
||||||
annotations: [],
|
annotations: [],
|
||||||
cssClasses: [],
|
cssClasses: 'default',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(classDb.getClasses().size).toBe(3);
|
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"annotations": [],
|
||||||
|
"cssClasses": "default",
|
||||||
|
"domId": "classId-Student-141",
|
||||||
|
"id": "Student",
|
||||||
|
"label": "Student",
|
||||||
|
"members": [
|
||||||
|
ClassMember {
|
||||||
|
"classifier": "",
|
||||||
|
"id": "idCard : IdCard",
|
||||||
|
"memberType": "attribute",
|
||||||
|
"text": "\\-idCard : IdCard",
|
||||||
|
"visibility": "-",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"methods": [],
|
||||||
|
"shape": "classBox",
|
||||||
|
"styles": [],
|
||||||
|
"text": "Student",
|
||||||
|
"type": "",
|
||||||
|
}
|
||||||
|
`);
|
||||||
expect(classDb.getRelations().length).toBe(2);
|
expect(classDb.getRelations().length).toBe(2);
|
||||||
expect(classDb.getRelations()).toMatchInlineSnapshot(`
|
expect(classDb.getRelations()).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
@ -738,7 +760,7 @@ foo()
|
|||||||
|
|
||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.link).toBe('google.com');
|
expect(actual.link).toBe('google.com');
|
||||||
expect(actual.cssClasses[0]).toBe('clickable');
|
expect(actual.cssClasses).toBe('default clickable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle href link with tooltip', function () {
|
it('should handle href link with tooltip', function () {
|
||||||
@ -754,7 +776,7 @@ foo()
|
|||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.link).toBe('google.com');
|
expect(actual.link).toBe('google.com');
|
||||||
expect(actual.tooltip).toBe('A Tooltip');
|
expect(actual.tooltip).toBe('A Tooltip');
|
||||||
expect(actual.cssClasses[0]).toBe('clickable');
|
expect(actual.cssClasses).toBe('default clickable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle href link with tooltip and target', function () {
|
it('should handle href link with tooltip and target', function () {
|
||||||
@ -773,7 +795,7 @@ foo()
|
|||||||
const actual = parser.yy.getClass('Class1');
|
const actual = parser.yy.getClass('Class1');
|
||||||
expect(actual.link).toBe('google.com');
|
expect(actual.link).toBe('google.com');
|
||||||
expect(actual.tooltip).toBe('A tooltip');
|
expect(actual.tooltip).toBe('A tooltip');
|
||||||
expect(actual.cssClasses[0]).toBe('clickable');
|
expect(actual.cssClasses).toBe('default clickable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle function call', function () {
|
it('should handle function call', function () {
|
||||||
@ -1468,8 +1490,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.link).toBe('google.com');
|
expect(testClass.link).toBe('google.com');
|
||||||
expect(testClass.cssClasses.length).toBe(1);
|
expect(testClass.cssClasses).toBe('default clickable');
|
||||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should associate click and href link and css appropriately', function () {
|
it('should associate click and href link and css appropriately', function () {
|
||||||
@ -1482,8 +1503,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
|
|
||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.link).toBe('google.com');
|
expect(testClass.link).toBe('google.com');
|
||||||
expect(testClass.cssClasses.length).toBe(1);
|
expect(testClass.cssClasses).toBe('default clickable');
|
||||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should associate link with tooltip', function () {
|
it('should associate link with tooltip', function () {
|
||||||
@ -1497,8 +1517,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.link).toBe('google.com');
|
expect(testClass.link).toBe('google.com');
|
||||||
expect(testClass.tooltip).toBe('A tooltip');
|
expect(testClass.tooltip).toBe('A tooltip');
|
||||||
expect(testClass.cssClasses.length).toBe(1);
|
expect(testClass.cssClasses).toBe('default clickable');
|
||||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should associate click and href link with tooltip', function () {
|
it('should associate click and href link with tooltip', function () {
|
||||||
@ -1512,8 +1531,7 @@ describe('given a class diagram with relationships, ', function () {
|
|||||||
const testClass = parser.yy.getClass('Class1');
|
const testClass = parser.yy.getClass('Class1');
|
||||||
expect(testClass.link).toBe('google.com');
|
expect(testClass.link).toBe('google.com');
|
||||||
expect(testClass.tooltip).toBe('A tooltip');
|
expect(testClass.tooltip).toBe('A tooltip');
|
||||||
expect(testClass.cssClasses.length).toBe(1);
|
expect(testClass.cssClasses).toBe('default clickable');
|
||||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should associate click and href link with tooltip and target appropriately', function () {
|
it('should associate click and href link with tooltip and target appropriately', function () {
|
||||||
@ -1770,8 +1788,7 @@ C1 --> C2
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses.length).toBe(1);
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
|
||||||
const member = c1.members[0];
|
const member = c1.members[0];
|
||||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
||||||
});
|
});
|
||||||
@ -1787,8 +1804,7 @@ cssClass "C1" styleClass
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses.length).toBe(1);
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
|
||||||
const member = c1.members[0];
|
const member = c1.members[0];
|
||||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
||||||
});
|
});
|
||||||
@ -1805,13 +1821,11 @@ cssClass "C1,C2" styleClass
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses.length).toBe(1);
|
expect(c1.cssClasses).toBe('default styleClass');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
|
||||||
|
|
||||||
const c2 = classDb.getClass('C2');
|
const c2 = classDb.getClass('C2');
|
||||||
expect(c2.label).toBe('Long long long long long long long long long long label');
|
expect(c2.label).toBe('Long long long long long long long long long long label');
|
||||||
expect(c2.cssClasses.length).toBe(1);
|
expect(c2.cssClasses).toBe('default styleClass');
|
||||||
expect(c2.cssClasses[0]).toBe('styleClass');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse two classes with text labels and css class shorthands', () => {
|
it('should parse two classes with text labels and css class shorthands', () => {
|
||||||
@ -1825,13 +1839,11 @@ C1 --> C2
|
|||||||
|
|
||||||
const c1 = classDb.getClass('C1');
|
const c1 = classDb.getClass('C1');
|
||||||
expect(c1.label).toBe('Class 1 with text label');
|
expect(c1.label).toBe('Class 1 with text label');
|
||||||
expect(c1.cssClasses.length).toBe(1);
|
expect(c1.cssClasses).toBe('default styleClass1');
|
||||||
expect(c1.cssClasses[0]).toBe('styleClass1');
|
|
||||||
|
|
||||||
const c2 = classDb.getClass('C2');
|
const c2 = classDb.getClass('C2');
|
||||||
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
||||||
expect(c2.cssClasses.length).toBe(1);
|
expect(c2.cssClasses).toBe('default styleClass2');
|
||||||
expect(c2.cssClasses[0]).toBe('styleClass2');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse multiple classes with same text labels', () => {
|
it('should parse multiple classes with same text labels', () => {
|
||||||
|
@ -3,7 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
|
|||||||
import parser from './parser/classDiagram.jison';
|
import parser from './parser/classDiagram.jison';
|
||||||
import db from './classDb.js';
|
import db from './classDb.js';
|
||||||
import styles from './styles.js';
|
import styles from './styles.js';
|
||||||
import renderer from './classRenderer.js';
|
import renderer from './classRenderer-v3-unified.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
parser,
|
parser,
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
|
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
||||||
|
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
|
||||||
|
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||||
|
import type { LayoutData } from '../../rendering-util/types.js';
|
||||||
|
import utils from '../../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the direction from the statement items.
|
||||||
|
* Look through all of the documents (docs) in the parsedItems
|
||||||
|
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
|
||||||
|
* @param parsedItem - the parsed statement item to look through
|
||||||
|
* @param defaultDir - the direction to use if none is found
|
||||||
|
* @returns The direction to use
|
||||||
|
*/
|
||||||
|
export const getDir = (parsedItem: any, defaultDir = 'TB') => {
|
||||||
|
if (!parsedItem.doc) {
|
||||||
|
return defaultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = defaultDir;
|
||||||
|
|
||||||
|
for (const parsedItemDoc of parsedItem.doc) {
|
||||||
|
if (parsedItemDoc.stmt === 'dir') {
|
||||||
|
dir = parsedItemDoc.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClasses = function (
|
||||||
|
text: string,
|
||||||
|
diagramObj: any
|
||||||
|
): Map<string, DiagramStyleClassDef> {
|
||||||
|
return diagramObj.db.getClasses();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draw = async function (text: string, id: string, _version: string, diag: any) {
|
||||||
|
log.info('REF0:');
|
||||||
|
log.info('Drawing class diagram (v3)', id);
|
||||||
|
const { securityLevel, state: conf, layout } = getConfig();
|
||||||
|
// Extracting the data from the parsed structure into a more usable form
|
||||||
|
// Not related to the refactoring, but this is the first step in the rendering process
|
||||||
|
// diag.db.extract(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
|
||||||
|
// into the Layout data format
|
||||||
|
const data4Layout = diag.db.getData() as LayoutData;
|
||||||
|
|
||||||
|
// Create the root SVG - the element is the div containing the SVG element
|
||||||
|
const svg = getDiagramElement(id, securityLevel);
|
||||||
|
|
||||||
|
data4Layout.type = diag.type;
|
||||||
|
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout);
|
||||||
|
|
||||||
|
data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
|
||||||
|
data4Layout.rankSpacing = conf?.rankSpacing || 50;
|
||||||
|
data4Layout.markers = ['aggregation', 'extension', 'composition', 'dependency', 'lollipop'];
|
||||||
|
data4Layout.diagramId = id;
|
||||||
|
await render(data4Layout, svg);
|
||||||
|
const padding = 8;
|
||||||
|
utils.insertTitle(
|
||||||
|
svg,
|
||||||
|
'classDiagramTitleText',
|
||||||
|
conf?.titleTopMargin ?? 25,
|
||||||
|
diag.db.getDiagramTitle()
|
||||||
|
);
|
||||||
|
|
||||||
|
setupViewPortForSVG(svg, padding, 'classDiagram', conf?.useMaxWidth ?? true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getClasses,
|
||||||
|
draw,
|
||||||
|
getDir,
|
||||||
|
};
|
@ -5,7 +5,9 @@ export interface ClassNode {
|
|||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label: string;
|
||||||
cssClasses: string[];
|
shape: string;
|
||||||
|
text: string;
|
||||||
|
cssClasses: string;
|
||||||
methods: ClassMember[];
|
methods: ClassMember[];
|
||||||
members: ClassMember[];
|
members: ClassMember[];
|
||||||
annotations: string[];
|
annotations: string[];
|
||||||
@ -16,6 +18,7 @@ export interface ClassNode {
|
|||||||
linkTarget?: string;
|
linkTarget?: string;
|
||||||
haveCallback?: boolean;
|
haveCallback?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
|
look?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Visibility = '#' | '+' | '~' | '-' | '';
|
export type Visibility = '#' | '+' | '~' | '-' | '';
|
||||||
@ -30,6 +33,7 @@ export class ClassMember {
|
|||||||
cssStyle!: string;
|
cssStyle!: string;
|
||||||
memberType!: 'method' | 'attribute';
|
memberType!: 'method' | 'attribute';
|
||||||
visibility!: Visibility;
|
visibility!: Visibility;
|
||||||
|
text: string;
|
||||||
/**
|
/**
|
||||||
* denote if static or to determine which css class to apply to the node
|
* denote if static or to determine which css class to apply to the node
|
||||||
* @defaultValue ''
|
* @defaultValue ''
|
||||||
@ -50,6 +54,7 @@ export class ClassMember {
|
|||||||
this.memberType = memberType;
|
this.memberType = memberType;
|
||||||
this.visibility = '';
|
this.visibility = '';
|
||||||
this.classifier = '';
|
this.classifier = '';
|
||||||
|
this.text = '';
|
||||||
const sanitizedInput = sanitizeText(input, getConfig());
|
const sanitizedInput = sanitizeText(input, getConfig());
|
||||||
this.parseMember(sanitizedInput);
|
this.parseMember(sanitizedInput);
|
||||||
}
|
}
|
||||||
@ -85,7 +90,7 @@ export class ClassMember {
|
|||||||
this.visibility = detectedVisibility as Visibility;
|
this.visibility = detectedVisibility as Visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.id = match[2].trim();
|
this.id = match[2];
|
||||||
this.parameters = match[3] ? match[3].trim() : '';
|
this.parameters = match[3] ? match[3].trim() : '';
|
||||||
potentialClassifier = match[4] ? match[4].trim() : '';
|
potentialClassifier = match[4] ? match[4].trim() : '';
|
||||||
this.returnType = match[5] ? match[5].trim() : '';
|
this.returnType = match[5] ? match[5].trim() : '';
|
||||||
@ -118,6 +123,14 @@ export class ClassMember {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.classifier = potentialClassifier;
|
this.classifier = potentialClassifier;
|
||||||
|
// Preserve one space only
|
||||||
|
this.id = this.id.startsWith(' ') ? ' ' + this.id.trim() : this.id.trim();
|
||||||
|
|
||||||
|
const combinedText = `${this.visibility ? '\\' + this.visibility : ''}${parseGenericTypes(this.id)}${this.memberType === 'method' ? `(${parseGenericTypes(this.parameters)})${this.returnType ? ' : ' + parseGenericTypes(this.returnType) : ''}` : ''}`;
|
||||||
|
this.text = combinedText.replaceAll('<', '<').replaceAll('>', '>');
|
||||||
|
if (this.text.startsWith('\\<')) {
|
||||||
|
this.text = this.text.replace('\\<', '~');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseClassifier() {
|
parseClassifier() {
|
||||||
@ -154,6 +167,12 @@ export interface ClassRelation {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Interface {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
classId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NamespaceNode {
|
export interface NamespaceNode {
|
||||||
id: string;
|
id: string;
|
||||||
domId: string;
|
domId: string;
|
||||||
@ -161,5 +180,11 @@ export interface NamespaceNode {
|
|||||||
children: NamespaceMap;
|
children: NamespaceMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StyleClass {
|
||||||
|
id: string;
|
||||||
|
styles: string[];
|
||||||
|
textStyles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export type ClassMap = Map<string, ClassNode>;
|
export type ClassMap = Map<string, ClassNode>;
|
||||||
export type NamespaceMap = Map<string, NamespaceNode>;
|
export type NamespaceMap = Map<string, NamespaceNode>;
|
||||||
|
@ -61,6 +61,7 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
|||||||
<string>[^"]* return "STR";
|
<string>[^"]* return "STR";
|
||||||
<*>["] this.begin("string");
|
<*>["] this.begin("string");
|
||||||
"style" return 'STYLE';
|
"style" return 'STYLE';
|
||||||
|
"classDef" return 'CLASSDEF';
|
||||||
|
|
||||||
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
|
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
|
||||||
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||||
@ -265,6 +266,7 @@ statement
|
|||||||
| styleStatement
|
| styleStatement
|
||||||
| cssClassStatement
|
| cssClassStatement
|
||||||
| noteStatement
|
| noteStatement
|
||||||
|
| classDefStatement
|
||||||
| direction
|
| direction
|
||||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||||
@ -326,6 +328,15 @@ noteStatement
|
|||||||
| NOTE noteText { yy.addNote($2); }
|
| NOTE noteText { yy.addNote($2); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
classDefStatement
|
||||||
|
: CLASSDEF classList stylesOpt {$$ = $CLASSDEF;yy.defineClass($classList,$stylesOpt);}
|
||||||
|
;
|
||||||
|
|
||||||
|
classList
|
||||||
|
: ALPHA { $$ = [$ALPHA]; }
|
||||||
|
| classList COMMA ALPHA = { $$ = $classList.concat([$ALPHA]); }
|
||||||
|
;
|
||||||
|
|
||||||
direction
|
direction
|
||||||
: direction_tb
|
: direction_tb
|
||||||
{ yy.setDirection('TB');}
|
{ yy.setDirection('TB');}
|
||||||
|
223
packages/mermaid/src/diagrams/class/shapeUtil.ts
Normal file
223
packages/mermaid/src/diagrams/class/shapeUtil.ts
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
import { select } from 'd3';
|
||||||
|
import { getConfig } from '../../config.js';
|
||||||
|
import { getNodeClasses } from '../../rendering-util/rendering-elements/shapes/util.js';
|
||||||
|
import { calculateTextWidth, decodeEntities } from '../../utils.js';
|
||||||
|
import type { ClassMember, ClassNode } from './classTypes.js';
|
||||||
|
import { sanitizeText } from '../../diagram-api/diagramAPI.js';
|
||||||
|
import { createText } from '../../rendering-util/createText.js';
|
||||||
|
import { evaluate, hasKatex } from '../common/common.js';
|
||||||
|
import type { Node } from '../../rendering-util/types.js';
|
||||||
|
import type { MermaidConfig } from '../../config.type.js';
|
||||||
|
import type { D3Selection } from '../../types.js';
|
||||||
|
|
||||||
|
// Creates the shapeSvg and inserts text
|
||||||
|
export async function textHelper<T extends SVGGraphicsElement>(
|
||||||
|
parent: D3Selection<T>,
|
||||||
|
node: any,
|
||||||
|
config: MermaidConfig,
|
||||||
|
useHtmlLabels: boolean,
|
||||||
|
GAP = config.class!.padding ?? 12
|
||||||
|
) {
|
||||||
|
const TEXT_PADDING = !useHtmlLabels ? 3 : 0;
|
||||||
|
const shapeSvg = parent
|
||||||
|
// @ts-ignore: Ignore error for using .insert on SVGAElement
|
||||||
|
.insert('g')
|
||||||
|
.attr('class', getNodeClasses(node))
|
||||||
|
.attr('id', node.domId || node.id);
|
||||||
|
|
||||||
|
let annotationGroup = null;
|
||||||
|
let labelGroup = null;
|
||||||
|
let membersGroup = null;
|
||||||
|
let methodsGroup = null;
|
||||||
|
|
||||||
|
let annotationGroupHeight = 0;
|
||||||
|
let labelGroupHeight = 0;
|
||||||
|
let membersGroupHeight = 0;
|
||||||
|
|
||||||
|
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
|
||||||
|
if (node.annotations.length > 0) {
|
||||||
|
const annotation = node.annotations[0];
|
||||||
|
await addText(annotationGroup, { text: `«${annotation}»` } as unknown as ClassMember, 0);
|
||||||
|
|
||||||
|
const annotationGroupBBox = annotationGroup.node()!.getBBox();
|
||||||
|
annotationGroupHeight = annotationGroupBBox.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
labelGroup = shapeSvg.insert('g').attr('class', 'label-group text');
|
||||||
|
await addText(labelGroup, node, 0, ['font-weight: bolder']);
|
||||||
|
const labelGroupBBox = labelGroup.node()!.getBBox();
|
||||||
|
labelGroupHeight = labelGroupBBox.height;
|
||||||
|
|
||||||
|
membersGroup = shapeSvg.insert('g').attr('class', 'members-group text');
|
||||||
|
let yOffset = 0;
|
||||||
|
for (const member of node.members) {
|
||||||
|
const height = await addText(membersGroup, member, yOffset, [member.parseClassifier()]);
|
||||||
|
yOffset += height + TEXT_PADDING;
|
||||||
|
}
|
||||||
|
membersGroupHeight = membersGroup.node()!.getBBox().height;
|
||||||
|
if (membersGroupHeight <= 0) {
|
||||||
|
membersGroupHeight = GAP / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
methodsGroup = shapeSvg.insert('g').attr('class', 'methods-group text');
|
||||||
|
let methodsYOffset = 0;
|
||||||
|
for (const method of node.methods) {
|
||||||
|
const height = await addText(methodsGroup, method, methodsYOffset, [method.parseClassifier()]);
|
||||||
|
methodsYOffset += height + TEXT_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bbox = shapeSvg.node()!.getBBox();
|
||||||
|
|
||||||
|
// Center annotation
|
||||||
|
if (annotationGroup !== null) {
|
||||||
|
const annotationGroupBBox = annotationGroup.node()!.getBBox();
|
||||||
|
annotationGroup.attr('transform', `translate(${-annotationGroupBBox.width / 2})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust label
|
||||||
|
labelGroup.attr('transform', `translate(${-labelGroupBBox.width / 2}, ${annotationGroupHeight})`);
|
||||||
|
|
||||||
|
bbox = shapeSvg.node()!.getBBox();
|
||||||
|
|
||||||
|
membersGroup.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + GAP * 2})`
|
||||||
|
);
|
||||||
|
bbox = shapeSvg.node()!.getBBox();
|
||||||
|
methodsGroup.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + (membersGroupHeight ? membersGroupHeight + GAP * 4 : GAP * 2)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
bbox = shapeSvg.node()!.getBBox();
|
||||||
|
|
||||||
|
return { shapeSvg, bbox };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified version of labelHelper() to help create and place text for classes
|
||||||
|
async function addText<T extends SVGGraphicsElement>(
|
||||||
|
parentGroup: D3Selection<T>,
|
||||||
|
node: Node | ClassNode | ClassMember,
|
||||||
|
yOffset: number,
|
||||||
|
styles: string[] = []
|
||||||
|
) {
|
||||||
|
const textEl = parentGroup.insert('g').attr('class', 'label').attr('style', styles.join('; '));
|
||||||
|
const config = getConfig();
|
||||||
|
let useHtmlLabels =
|
||||||
|
'useHtmlLabels' in node ? node.useHtmlLabels : (evaluate(config.htmlLabels) ?? true);
|
||||||
|
|
||||||
|
let textContent = '';
|
||||||
|
// Support regular node type (.label) and classNodes (.text)
|
||||||
|
if ('text' in node) {
|
||||||
|
textContent = node.text;
|
||||||
|
} else {
|
||||||
|
textContent = node.label!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// createText() will cause unwanted behavior because of classDiagram syntax so workarounds are needed
|
||||||
|
|
||||||
|
if (!useHtmlLabels && textContent.startsWith('\\')) {
|
||||||
|
textContent = textContent.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasKatex(textContent)) {
|
||||||
|
useHtmlLabels = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await createText(
|
||||||
|
textEl,
|
||||||
|
sanitizeText(decodeEntities(textContent)),
|
||||||
|
{
|
||||||
|
width: calculateTextWidth(textContent, config) + 50, // Add room for error when splitting text into multiple lines
|
||||||
|
classes: 'markdown-node-label',
|
||||||
|
useHtmlLabels,
|
||||||
|
},
|
||||||
|
config
|
||||||
|
);
|
||||||
|
let bbox;
|
||||||
|
let numberOfLines = 1;
|
||||||
|
|
||||||
|
if (!useHtmlLabels) {
|
||||||
|
// Undo font-weight normal
|
||||||
|
if (styles.includes('font-weight: bolder')) {
|
||||||
|
select(text).selectAll('tspan').attr('font-weight', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
numberOfLines = text.children.length;
|
||||||
|
|
||||||
|
const textChild = text.children[0];
|
||||||
|
if (text.textContent === '' || text.textContent.includes('>')) {
|
||||||
|
textChild.textContent =
|
||||||
|
textContent[0] +
|
||||||
|
textContent.substring(1).replaceAll('>', '>').replaceAll('<', '<').trim();
|
||||||
|
|
||||||
|
// Text was improperly removed due to spaces (preserve one space if present)
|
||||||
|
const preserveSpace = textContent[1] === ' ';
|
||||||
|
if (preserveSpace) {
|
||||||
|
textChild.textContent = textChild.textContent[0] + ' ' + textChild.textContent.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To support empty boxes
|
||||||
|
if (textChild.textContent === 'undefined') {
|
||||||
|
textChild.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the bounding box after the text update
|
||||||
|
bbox = text.getBBox();
|
||||||
|
} else {
|
||||||
|
const div = text.children[0];
|
||||||
|
const dv = select(text);
|
||||||
|
|
||||||
|
numberOfLines = div.innerHTML.split('<br>').length;
|
||||||
|
// Katex math support
|
||||||
|
if (div.innerHTML.includes('</math>')) {
|
||||||
|
numberOfLines += div.innerHTML.split('<mrow>').length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support images
|
||||||
|
const images = div.getElementsByTagName('img');
|
||||||
|
if (images) {
|
||||||
|
const noImgText = textContent.replace(/<img[^>]*>/g, '').trim() === '';
|
||||||
|
await Promise.all(
|
||||||
|
[...images].map(
|
||||||
|
(img) =>
|
||||||
|
new Promise((res) => {
|
||||||
|
function setupImage() {
|
||||||
|
img.style.display = 'flex';
|
||||||
|
img.style.flexDirection = 'column';
|
||||||
|
|
||||||
|
if (noImgText) {
|
||||||
|
// default size if no text
|
||||||
|
const bodyFontSize =
|
||||||
|
config.fontSize?.toString() ?? window.getComputedStyle(document.body).fontSize;
|
||||||
|
const enlargingFactor = 5;
|
||||||
|
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
|
||||||
|
img.style.minWidth = width;
|
||||||
|
img.style.maxWidth = width;
|
||||||
|
} else {
|
||||||
|
img.style.width = '100%';
|
||||||
|
}
|
||||||
|
res(img);
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (img.complete) {
|
||||||
|
setupImage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
img.addEventListener('error', setupImage);
|
||||||
|
img.addEventListener('load', setupImage);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bbox = div.getBoundingClientRect();
|
||||||
|
dv.attr('width', bbox.width);
|
||||||
|
dv.attr('height', bbox.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center text and offset by yOffset
|
||||||
|
textEl.attr('transform', 'translate(0,' + (-bbox.height / (2 * numberOfLines) + yOffset) + ')');
|
||||||
|
return bbox.height;
|
||||||
|
}
|
@ -20,6 +20,10 @@ const getStyles = (options) =>
|
|||||||
.label text {
|
.label text {
|
||||||
fill: ${options.classText};
|
fill: ${options.classText};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.labelBkg {
|
||||||
|
background: ${options.mainBkg};
|
||||||
|
}
|
||||||
.edgeLabel .label span {
|
.edgeLabel .label span {
|
||||||
background: ${options.mainBkg};
|
background: ${options.mainBkg};
|
||||||
}
|
}
|
||||||
|
@ -277,6 +277,34 @@ And `Link` can be one of:
|
|||||||
| -- | Solid |
|
| -- | Solid |
|
||||||
| .. | Dashed |
|
| .. | Dashed |
|
||||||
|
|
||||||
|
### Lollipop Interfaces
|
||||||
|
|
||||||
|
Classes can also be given a special relation type that defines a lollipop interface on the class. A lollipop interface is defined using the following syntax:
|
||||||
|
|
||||||
|
- `bar ()-- foo`
|
||||||
|
- `foo --() bar`
|
||||||
|
|
||||||
|
The interface (bar) with the lollipop connects to the class (foo).
|
||||||
|
|
||||||
|
Note: Each interface that is defined is unique and is meant to not be shared between classes / have multiple edges connecting to it.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
bar ()-- foo
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Class01 {
|
||||||
|
int amount
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
Class01 --() bar
|
||||||
|
Class02 --() bar
|
||||||
|
|
||||||
|
foo ()-- Class01
|
||||||
|
```
|
||||||
|
|
||||||
## Define Namespace
|
## Define Namespace
|
||||||
|
|
||||||
A namespace groups classes.
|
A namespace groups classes.
|
||||||
@ -518,10 +546,12 @@ Beginner's tip—a full example using interactive links in an HTML page:
|
|||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
### Styling a node (v10.7.0+)
|
### Styling a node
|
||||||
|
|
||||||
It is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.
|
It is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.
|
||||||
|
|
||||||
|
Note that notes and namespaces cannot be styled individually but do support themes.
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
classDiagram
|
classDiagram
|
||||||
class Animal
|
class Animal
|
||||||
@ -533,11 +563,78 @@ classDiagram
|
|||||||
#### Classes
|
#### Classes
|
||||||
|
|
||||||
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
|
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
|
||||||
should have a different look. This is done by predefining classes in css styles that can be applied from the graph definition using the `cssClass` statement or the `:::` short hand.
|
should have a different look.
|
||||||
|
|
||||||
|
A class definition looks like the example below:
|
||||||
|
|
||||||
|
```
|
||||||
|
classDef className fill:#f9f,stroke:#333,stroke-width:4px;
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, it is possible to define style to multiple classes in one statement:
|
||||||
|
|
||||||
|
```
|
||||||
|
classDef firstClassName,secondClassName font-size:12pt;
|
||||||
|
```
|
||||||
|
|
||||||
|
Attachment of a class to a node is done as per below:
|
||||||
|
|
||||||
|
```
|
||||||
|
cssClass "nodeId1" className;
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to attach a class to a list of nodes in one statement:
|
||||||
|
|
||||||
|
```
|
||||||
|
cssClass "nodeId1,nodeId2" className;
|
||||||
|
```
|
||||||
|
|
||||||
|
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Animal:::someclass
|
||||||
|
classDef someclass fill:#f96
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Animal:::someclass {
|
||||||
|
-int sizeInFeet
|
||||||
|
-canEat()
|
||||||
|
}
|
||||||
|
classDef someclass fill:#f96
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default class
|
||||||
|
|
||||||
|
If a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.
|
||||||
|
|
||||||
|
```
|
||||||
|
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
classDiagram
|
||||||
|
class Animal:::pink
|
||||||
|
class Mineral
|
||||||
|
|
||||||
|
classDef default fill:#f96,color:red
|
||||||
|
classDef pink color:#f9f
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Classes
|
||||||
|
|
||||||
|
It is also possible to predefine classes in CSS styles that can be applied from the graph definition as in the example
|
||||||
|
below:
|
||||||
|
|
||||||
|
**Example style**
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<style>
|
<style>
|
||||||
.styleClass > rect {
|
.styleClass > * > g {
|
||||||
fill: #ff0000;
|
fill: #ff0000;
|
||||||
stroke: #ffff00;
|
stroke: #ffff00;
|
||||||
stroke-width: 4px;
|
stroke-width: 4px;
|
||||||
@ -545,147 +642,29 @@ should have a different look. This is done by predefining classes in css styles
|
|||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then attaching that class to a specific node:
|
**Example definition**
|
||||||
|
|
||||||
```
|
|
||||||
cssClass "nodeId1" styleClass;
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also possible to attach a class to a list of nodes in one statement:
|
|
||||||
|
|
||||||
```
|
|
||||||
cssClass "nodeId1,nodeId2" styleClass;
|
|
||||||
```
|
|
||||||
|
|
||||||
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
|
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
classDiagram
|
classDiagram
|
||||||
class Animal:::styleClass
|
class Animal:::styleClass
|
||||||
```
|
```
|
||||||
|
|
||||||
Or:
|
> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
|
||||||
|
|
||||||
```mermaid-example
|
|
||||||
classDiagram
|
|
||||||
class Animal:::styleClass {
|
|
||||||
-int sizeInFeet
|
|
||||||
-canEat()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
?> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
|
|
||||||
|
|
||||||
?> Due to limitations with existing markup for class diagrams, it is not currently possible to define css classes within the diagram itself. **_Coming soon!_**
|
|
||||||
|
|
||||||
### Default Styles
|
|
||||||
|
|
||||||
The main styling of the class diagram is done with a preset number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss. The classes used here are described below:
|
|
||||||
|
|
||||||
| 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 line | Styles for class diagram line |
|
|
||||||
| .classLabel .box | Styles for class label box |
|
|
||||||
| .classLabel .label | Styles for class label text |
|
|
||||||
| composition | Styles for composition arrow head and arrow line |
|
|
||||||
| aggregation | Styles for aggregation arrow head and arrow line(dashed or solid) |
|
|
||||||
| dependency | Styles for dependency arrow head and arrow line |
|
|
||||||
|
|
||||||
#### Sample stylesheet
|
|
||||||
|
|
||||||
```scss
|
|
||||||
body {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.classGroup text {
|
|
||||||
fill: $nodeBorder;
|
|
||||||
stroke: none;
|
|
||||||
font-family: 'trebuchet ms', verdana, arial;
|
|
||||||
font-family: var(--mermaid-font-family);
|
|
||||||
font-size: 10px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.classGroup rect {
|
|
||||||
fill: $nodeBkg;
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.classGroup line {
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classLabel .box {
|
|
||||||
stroke: none;
|
|
||||||
stroke-width: 0;
|
|
||||||
fill: $nodeBkg;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classLabel .label {
|
|
||||||
fill: $nodeBorder;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relation {
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin composition {
|
|
||||||
fill: $nodeBorder;
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#compositionStart {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#compositionEnd {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin aggregation {
|
|
||||||
fill: $nodeBkg;
|
|
||||||
stroke: $nodeBorder;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#aggregationStart {
|
|
||||||
@include aggregation;
|
|
||||||
}
|
|
||||||
|
|
||||||
#aggregationEnd {
|
|
||||||
@include aggregation;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dependencyStart {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dependencyEnd {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#extensionStart {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#extensionEnd {
|
|
||||||
@include composition;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`Coming soon!`
|
### Members Box
|
||||||
|
|
||||||
|
It is possible to hide the empty members box of a class node.
|
||||||
|
|
||||||
|
This is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html)
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
class:
|
||||||
|
hideEmptyMembersBox: true
|
||||||
|
---
|
||||||
|
classDiagram
|
||||||
|
class Duck
|
||||||
|
```
|
||||||
|
@ -779,7 +779,7 @@ graph TD;A--x|text including URL space|B;`)
|
|||||||
// We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams)
|
// We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams)
|
||||||
const diagramTypesAndExpectations = [
|
const diagramTypesAndExpectations = [
|
||||||
{ textDiagramType: 'C4Context', expectedType: 'c4' },
|
{ textDiagramType: 'C4Context', expectedType: 'c4' },
|
||||||
{ textDiagramType: 'classDiagram', expectedType: 'classDiagram' },
|
{ textDiagramType: 'classDiagram', expectedType: 'class' },
|
||||||
{ textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' },
|
{ textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' },
|
||||||
{ textDiagramType: 'erDiagram', expectedType: 'er' },
|
{ textDiagramType: 'erDiagram', expectedType: 'er' },
|
||||||
{ textDiagramType: 'graph', expectedType: 'flowchart-v2' },
|
{ textDiagramType: 'graph', expectedType: 'flowchart-v2' },
|
||||||
|
@ -85,6 +85,8 @@ export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidCo
|
|||||||
return '';
|
return '';
|
||||||
} else if (node.type === 'html') {
|
} else if (node.type === 'html') {
|
||||||
return `${node.text}`;
|
return `${node.text}`;
|
||||||
|
} else if (node.type === 'escape') {
|
||||||
|
return node.text;
|
||||||
}
|
}
|
||||||
return `Unsupported markdown: ${node.type}`;
|
return `Unsupported markdown: ${node.type}`;
|
||||||
}
|
}
|
||||||
|
@ -463,15 +463,6 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
|
|||||||
|
|
||||||
let lineData = points.filter((p) => !Number.isNaN(p.y));
|
let lineData = points.filter((p) => !Number.isNaN(p.y));
|
||||||
lineData = fixCorners(lineData);
|
lineData = fixCorners(lineData);
|
||||||
let lastPoint = lineData[lineData.length - 1];
|
|
||||||
if (lineData.length > 1) {
|
|
||||||
lastPoint = lineData[lineData.length - 1];
|
|
||||||
const secondLastPoint = lineData[lineData.length - 2];
|
|
||||||
const diffX = (lastPoint.x - secondLastPoint.x) / 2;
|
|
||||||
const diffY = (lastPoint.y - secondLastPoint.y) / 2;
|
|
||||||
const midPoint = { x: secondLastPoint.x + diffX, y: secondLastPoint.y + diffY };
|
|
||||||
lineData.splice(-1, 0, midPoint);
|
|
||||||
}
|
|
||||||
let curve = curveBasis;
|
let curve = curveBasis;
|
||||||
if (edge.curve) {
|
if (edge.curve) {
|
||||||
curve = edge.curve;
|
curve = edge.curve;
|
||||||
|
@ -57,6 +57,7 @@ import { triangle } from './shapes/triangle.js';
|
|||||||
import { waveEdgedRectangle } from './shapes/waveEdgedRectangle.js';
|
import { waveEdgedRectangle } from './shapes/waveEdgedRectangle.js';
|
||||||
import { waveRectangle } from './shapes/waveRectangle.js';
|
import { waveRectangle } from './shapes/waveRectangle.js';
|
||||||
import { windowPane } from './shapes/windowPane.js';
|
import { windowPane } from './shapes/windowPane.js';
|
||||||
|
import { classBox } from './shapes/classBox.js';
|
||||||
import { kanbanItem } from './shapes/kanbanItem.js';
|
import { kanbanItem } from './shapes/kanbanItem.js';
|
||||||
|
|
||||||
type ShapeHandler = <T extends SVGGraphicsElement>(
|
type ShapeHandler = <T extends SVGGraphicsElement>(
|
||||||
@ -448,6 +449,14 @@ export const shapesDefs = [
|
|||||||
aliases: ['lined-document'],
|
aliases: ['lined-document'],
|
||||||
handler: linedWaveEdgedRect,
|
handler: linedWaveEdgedRect,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
semanticName: 'Class Box',
|
||||||
|
name: 'Class Box',
|
||||||
|
shortName: 'classBox',
|
||||||
|
description: 'Class Box',
|
||||||
|
aliases: ['class-box'],
|
||||||
|
handler: classBox,
|
||||||
|
},
|
||||||
] as const satisfies ShapeDefinition[];
|
] as const satisfies ShapeDefinition[];
|
||||||
|
|
||||||
const generateShapeMap = () => {
|
const generateShapeMap = () => {
|
||||||
|
@ -0,0 +1,207 @@
|
|||||||
|
import { updateNodeBounds } from './util.js';
|
||||||
|
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||||
|
import { select } from 'd3';
|
||||||
|
import type { Node } from '../../types.js';
|
||||||
|
import type { ClassNode } from '../../../diagrams/class/classTypes.js';
|
||||||
|
import rough from 'roughjs';
|
||||||
|
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||||
|
import intersect from '../intersect/index.js';
|
||||||
|
import { textHelper } from '../../../diagrams/class/shapeUtil.js';
|
||||||
|
import { evaluate } from '../../../diagrams/common/common.js';
|
||||||
|
import type { D3Selection } from '../../../types.js';
|
||||||
|
|
||||||
|
export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
||||||
|
const config = getConfig();
|
||||||
|
const PADDING = config.class!.padding ?? 12;
|
||||||
|
const GAP = PADDING;
|
||||||
|
const useHtmlLabels = node.useHtmlLabels ?? evaluate(config.htmlLabels) ?? true;
|
||||||
|
// Treat node as classNode
|
||||||
|
const classNode = node as unknown as ClassNode;
|
||||||
|
classNode.annotations = classNode.annotations ?? [];
|
||||||
|
classNode.members = classNode.members ?? [];
|
||||||
|
classNode.methods = classNode.methods ?? [];
|
||||||
|
|
||||||
|
const { shapeSvg, bbox } = await textHelper(parent, node, config, useHtmlLabels, GAP);
|
||||||
|
|
||||||
|
const { labelStyles, nodeStyles } = styles2String(node);
|
||||||
|
node.labelStyle = labelStyles;
|
||||||
|
|
||||||
|
node.cssStyles = classNode.styles || '';
|
||||||
|
|
||||||
|
const styles = classNode.styles?.join(';') || nodeStyles || '';
|
||||||
|
|
||||||
|
if (!node.cssStyles) {
|
||||||
|
node.cssStyles = styles.replaceAll('!important', '').split(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderExtraBox =
|
||||||
|
classNode.members.length === 0 &&
|
||||||
|
classNode.methods.length === 0 &&
|
||||||
|
!config.class?.hideEmptyMembersBox;
|
||||||
|
|
||||||
|
// Setup roughjs
|
||||||
|
// @ts-ignore TODO: Fix rough typings
|
||||||
|
const rc = rough.svg(shapeSvg);
|
||||||
|
const options = userNodeOverrides(node, {});
|
||||||
|
|
||||||
|
if (node.look !== 'handDrawn') {
|
||||||
|
options.roughness = 0;
|
||||||
|
options.fillStyle = 'solid';
|
||||||
|
}
|
||||||
|
|
||||||
|
const w = bbox.width;
|
||||||
|
let h = bbox.height;
|
||||||
|
if (classNode.members.length === 0 && classNode.methods.length === 0) {
|
||||||
|
h += GAP;
|
||||||
|
} else if (classNode.members.length > 0 && classNode.methods.length === 0) {
|
||||||
|
h += GAP * 2;
|
||||||
|
}
|
||||||
|
const x = -w / 2;
|
||||||
|
const y = -h / 2;
|
||||||
|
|
||||||
|
// Create and center rectangle
|
||||||
|
const roughRect = rc.rectangle(
|
||||||
|
x - PADDING,
|
||||||
|
y -
|
||||||
|
PADDING -
|
||||||
|
(renderExtraBox
|
||||||
|
? PADDING
|
||||||
|
: classNode.members.length === 0 && classNode.methods.length === 0
|
||||||
|
? -PADDING / 2
|
||||||
|
: 0),
|
||||||
|
w + 2 * PADDING,
|
||||||
|
h +
|
||||||
|
2 * PADDING +
|
||||||
|
(renderExtraBox
|
||||||
|
? PADDING * 2
|
||||||
|
: classNode.members.length === 0 && classNode.methods.length === 0
|
||||||
|
? -PADDING
|
||||||
|
: 0),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const rect = shapeSvg.insert(() => roughRect, ':first-child');
|
||||||
|
rect.attr('class', 'basic label-container');
|
||||||
|
const rectBBox = rect.node()!.getBBox();
|
||||||
|
|
||||||
|
// Rect is centered so now adjust labels.
|
||||||
|
// TODO: Fix types
|
||||||
|
shapeSvg.selectAll('.text').each((_: any, i: number, nodes: any) => {
|
||||||
|
const text = select<any, unknown>(nodes[i]);
|
||||||
|
// Get the current transform attribute
|
||||||
|
const transform = text.attr('transform');
|
||||||
|
// Initialize variables for the translation values
|
||||||
|
let translateY = 0;
|
||||||
|
// Check if the transform attribute exists
|
||||||
|
if (transform) {
|
||||||
|
const regex = RegExp(/translate\(([^,]+),([^)]+)\)/);
|
||||||
|
const translate = regex.exec(transform);
|
||||||
|
if (translate) {
|
||||||
|
translateY = parseFloat(translate[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add to the y value
|
||||||
|
let newTranslateY =
|
||||||
|
translateY +
|
||||||
|
y +
|
||||||
|
PADDING -
|
||||||
|
(renderExtraBox
|
||||||
|
? PADDING
|
||||||
|
: classNode.members.length === 0 && classNode.methods.length === 0
|
||||||
|
? -PADDING / 2
|
||||||
|
: 0);
|
||||||
|
if (!useHtmlLabels) {
|
||||||
|
// Fix so non html labels are better centered.
|
||||||
|
// BBox of text seems to be slightly different when calculated so we offset
|
||||||
|
newTranslateY -= 4;
|
||||||
|
}
|
||||||
|
let newTranslateX = x;
|
||||||
|
if (
|
||||||
|
text.attr('class').includes('label-group') ||
|
||||||
|
text.attr('class').includes('annotation-group')
|
||||||
|
) {
|
||||||
|
newTranslateX = -text.node()?.getBBox().width / 2 || 0;
|
||||||
|
shapeSvg.selectAll('text').each(function (_: any, i: number, nodes: any) {
|
||||||
|
if (window.getComputedStyle(nodes[i]).textAnchor === 'middle') {
|
||||||
|
newTranslateX = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Set the updated transform attribute
|
||||||
|
text.attr('transform', `translate(${newTranslateX}, ${newTranslateY})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render divider lines.
|
||||||
|
const annotationGroupHeight =
|
||||||
|
(shapeSvg.select('.annotation-group').node() as SVGGraphicsElement).getBBox().height -
|
||||||
|
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
||||||
|
const labelGroupHeight =
|
||||||
|
(shapeSvg.select('.label-group').node() as SVGGraphicsElement).getBBox().height -
|
||||||
|
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
||||||
|
const membersGroupHeight =
|
||||||
|
(shapeSvg.select('.members-group').node() as SVGGraphicsElement).getBBox().height -
|
||||||
|
(renderExtraBox ? PADDING / 2 : 0) || 0;
|
||||||
|
// First line (under label)
|
||||||
|
if (classNode.members.length > 0 || classNode.methods.length > 0 || renderExtraBox) {
|
||||||
|
const roughLine = rc.line(
|
||||||
|
rectBBox.x,
|
||||||
|
annotationGroupHeight + labelGroupHeight + y + PADDING,
|
||||||
|
rectBBox.x + rectBBox.width,
|
||||||
|
annotationGroupHeight + labelGroupHeight + y + PADDING,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const line = shapeSvg.insert(() => roughLine);
|
||||||
|
line.attr('class', 'divider').attr('style', styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second line (under members)
|
||||||
|
if (renderExtraBox || classNode.members.length > 0 || classNode.methods.length > 0) {
|
||||||
|
const roughLine = rc.line(
|
||||||
|
rectBBox.x,
|
||||||
|
annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + GAP * 2 + PADDING,
|
||||||
|
rectBBox.x + rectBBox.width,
|
||||||
|
annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + PADDING + GAP * 2,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const line = shapeSvg.insert(() => roughLine);
|
||||||
|
line.attr('class', 'divider').attr('style', styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply styles ///
|
||||||
|
if (classNode.look !== 'handDrawn') {
|
||||||
|
shapeSvg.selectAll('path').attr('style', styles);
|
||||||
|
}
|
||||||
|
// Apply other styles like stroke-width and stroke-dasharray to border (not background of shape)
|
||||||
|
rect.select(':nth-child(2)').attr('style', styles);
|
||||||
|
// Divider lines
|
||||||
|
shapeSvg.selectAll('.divider').select('path').attr('style', styles);
|
||||||
|
// Text elements
|
||||||
|
if (node.labelStyle) {
|
||||||
|
shapeSvg.selectAll('span').attr('style', node.labelStyle);
|
||||||
|
} else {
|
||||||
|
shapeSvg.selectAll('span').attr('style', styles);
|
||||||
|
}
|
||||||
|
// SVG text uses fill not color
|
||||||
|
if (!useHtmlLabels) {
|
||||||
|
// We just want to apply color to the text
|
||||||
|
const colorRegex = RegExp(/color\s*:\s*([^;]*)/);
|
||||||
|
const match = colorRegex.exec(styles);
|
||||||
|
if (match) {
|
||||||
|
const colorStyle = match[0].replace('color', 'fill');
|
||||||
|
shapeSvg.selectAll('tspan').attr('style', colorStyle);
|
||||||
|
} else if (labelStyles) {
|
||||||
|
const match = colorRegex.exec(labelStyles);
|
||||||
|
if (match) {
|
||||||
|
const colorStyle = match[0].replace('color', 'fill');
|
||||||
|
shapeSvg.selectAll('tspan').attr('style', colorStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNodeBounds(node, rect);
|
||||||
|
node.intersect = function (point) {
|
||||||
|
return intersect.rect(node, point);
|
||||||
|
};
|
||||||
|
|
||||||
|
return shapeSvg;
|
||||||
|
}
|
@ -95,6 +95,9 @@ export interface Edge {
|
|||||||
stroke?: string;
|
stroke?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
// Class Diagram specific properties
|
||||||
|
startLabelRight?: string;
|
||||||
|
endLabelLeft?: string;
|
||||||
// Rendering specific properties
|
// Rendering specific properties
|
||||||
curve?: string;
|
curve?: string;
|
||||||
labelpos?: string;
|
labelpos?: string;
|
||||||
|
@ -1448,6 +1448,9 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||||||
htmlLabels:
|
htmlLabels:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
hideEmptyMembersBox:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
JourneyDiagramConfig:
|
JourneyDiagramConfig:
|
||||||
title: Journey Diagram Config
|
title: Journey Diagram Config
|
||||||
|
@ -824,6 +824,7 @@ export const insertTitle = (
|
|||||||
parent
|
parent
|
||||||
.append('text')
|
.append('text')
|
||||||
.text(title)
|
.text(title)
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
.attr('x', bounds.x + bounds.width / 2)
|
.attr('x', bounds.x + bounds.width / 2)
|
||||||
.attr('y', -titleTopMargin)
|
.attr('y', -titleTopMargin)
|
||||||
.attr('class', cssClass);
|
.attr('class', cssClass);
|
||||||
|
@ -52,18 +52,15 @@ export const getLineFunctionsWithOffset = (
|
|||||||
data: (Point | [number, number])[]
|
data: (Point | [number, number])[]
|
||||||
) {
|
) {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
const DIRECTION =
|
||||||
|
pointTransformer(data[0]).x < pointTransformer(data[data.length - 1]).x ? 'left' : 'right';
|
||||||
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
||||||
// Handle first point
|
|
||||||
// Calculate the angle and delta between the first two points
|
|
||||||
const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
|
const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
|
||||||
// Calculate the offset based on the angle and the marker's dimensions
|
|
||||||
offset =
|
offset =
|
||||||
markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] *
|
markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] *
|
||||||
Math.cos(angle) *
|
Math.cos(angle) *
|
||||||
(deltaX >= 0 ? 1 : -1);
|
(deltaX >= 0 ? 1 : -1);
|
||||||
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
||||||
// Handle last point
|
|
||||||
// Calculate the angle and delta between the last two points
|
|
||||||
const { angle, deltaX } = calculateDeltaAndAngle(
|
const { angle, deltaX } = calculateDeltaAndAngle(
|
||||||
data[data.length - 1],
|
data[data.length - 1],
|
||||||
data[data.length - 2]
|
data[data.length - 2]
|
||||||
@ -73,6 +70,41 @@ export const getLineFunctionsWithOffset = (
|
|||||||
Math.cos(angle) *
|
Math.cos(angle) *
|
||||||
(deltaX >= 0 ? 1 : -1);
|
(deltaX >= 0 ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const differenceToEnd = Math.abs(
|
||||||
|
pointTransformer(d).x - pointTransformer(data[data.length - 1]).x
|
||||||
|
);
|
||||||
|
const differenceInYEnd = Math.abs(
|
||||||
|
pointTransformer(d).y - pointTransformer(data[data.length - 1]).y
|
||||||
|
);
|
||||||
|
const differenceToStart = Math.abs(pointTransformer(d).x - pointTransformer(data[0]).x);
|
||||||
|
const differenceInYStart = Math.abs(pointTransformer(d).y - pointTransformer(data[0]).y);
|
||||||
|
const startMarkerHeight = markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets];
|
||||||
|
const endMarkerHeight = markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets];
|
||||||
|
const extraRoom = 1;
|
||||||
|
|
||||||
|
// Adjust the offset if the difference is smaller than the marker height
|
||||||
|
if (
|
||||||
|
differenceToEnd < endMarkerHeight &&
|
||||||
|
differenceToEnd > 0 &&
|
||||||
|
differenceInYEnd < endMarkerHeight
|
||||||
|
) {
|
||||||
|
let adjustment = endMarkerHeight + extraRoom - differenceToEnd;
|
||||||
|
adjustment *= DIRECTION === 'right' ? -1 : 1;
|
||||||
|
// Adjust the offset by the amount needed to fit the marker
|
||||||
|
offset -= adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
differenceToStart < startMarkerHeight &&
|
||||||
|
differenceToStart > 0 &&
|
||||||
|
differenceInYStart < startMarkerHeight
|
||||||
|
) {
|
||||||
|
let adjustment = startMarkerHeight + extraRoom - differenceToStart;
|
||||||
|
adjustment *= DIRECTION === 'right' ? -1 : 1;
|
||||||
|
offset += adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
return pointTransformer(d).x + offset;
|
return pointTransformer(d).x + offset;
|
||||||
},
|
},
|
||||||
y: function (
|
y: function (
|
||||||
@ -81,8 +113,9 @@ export const getLineFunctionsWithOffset = (
|
|||||||
i: number,
|
i: number,
|
||||||
data: (Point | [number, number])[]
|
data: (Point | [number, number])[]
|
||||||
) {
|
) {
|
||||||
// Same handling as X above
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
const DIRECTION =
|
||||||
|
pointTransformer(data[0]).y < pointTransformer(data[data.length - 1]).y ? 'down' : 'up';
|
||||||
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
||||||
const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]);
|
const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]);
|
||||||
offset =
|
offset =
|
||||||
@ -99,6 +132,40 @@ export const getLineFunctionsWithOffset = (
|
|||||||
Math.abs(Math.sin(angle)) *
|
Math.abs(Math.sin(angle)) *
|
||||||
(deltaY >= 0 ? 1 : -1);
|
(deltaY >= 0 ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const differenceToEnd = Math.abs(
|
||||||
|
pointTransformer(d).y - pointTransformer(data[data.length - 1]).y
|
||||||
|
);
|
||||||
|
const differenceInXEnd = Math.abs(
|
||||||
|
pointTransformer(d).x - pointTransformer(data[data.length - 1]).x
|
||||||
|
);
|
||||||
|
const differenceToStart = Math.abs(pointTransformer(d).y - pointTransformer(data[0]).y);
|
||||||
|
const differenceInXStart = Math.abs(pointTransformer(d).x - pointTransformer(data[0]).x);
|
||||||
|
const startMarkerHeight = markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets];
|
||||||
|
const endMarkerHeight = markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets];
|
||||||
|
const extraRoom = 1;
|
||||||
|
|
||||||
|
// Adjust the offset if the difference is smaller than the marker height
|
||||||
|
if (
|
||||||
|
differenceToEnd < endMarkerHeight &&
|
||||||
|
differenceToEnd > 0 &&
|
||||||
|
differenceInXEnd < endMarkerHeight
|
||||||
|
) {
|
||||||
|
let adjustment = endMarkerHeight + extraRoom - differenceToEnd;
|
||||||
|
adjustment *= DIRECTION === 'up' ? -1 : 1;
|
||||||
|
// Adjust the offset by the amount needed to fit the marker
|
||||||
|
offset -= adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
differenceToStart < startMarkerHeight &&
|
||||||
|
differenceToStart > 0 &&
|
||||||
|
differenceInXStart < startMarkerHeight
|
||||||
|
) {
|
||||||
|
let adjustment = startMarkerHeight + extraRoom - differenceToStart;
|
||||||
|
adjustment *= DIRECTION === 'up' ? -1 : 1;
|
||||||
|
offset += adjustment;
|
||||||
|
}
|
||||||
return pointTransformer(d).y + offset;
|
return pointTransformer(d).y + offset;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"./src/**/*.ts",
|
"./src/**/*.ts",
|
||||||
"./package.json",
|
"./package.json",
|
||||||
"src/diagrams/gantt/ganttDb.js",
|
"src/diagrams/gantt/ganttDb.js",
|
||||||
"src/diagrams/git/gitGraphRenderer.js"
|
"src/diagrams/git/gitGraphRenderer.js",
|
||||||
|
"src/diagrams/class/classRenderer.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user