mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-04 12:54:08 +01:00 
			
		
		
		
	Compare commits
	
		
			57 Commits
		
	
	
		
			mermaid@11
			...
			chore/fix_
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3e57d35aee | ||
| 
						 | 
					801bdea300 | ||
| 
						 | 
					ef572bb859 | ||
| 
						 | 
					77a16fc3fa | ||
| 
						 | 
					a80171a7ce | ||
| 
						 | 
					c5106a1d1f | ||
| 
						 | 
					df636c6d0a | ||
| 
						 | 
					64554a6c60 | ||
| 
						 | 
					becadf0a7d | ||
| 
						 | 
					54d485f173 | ||
| 
						 | 
					b4f5b8ddaf | ||
| 
						 | 
					cb5c1ae367 | ||
| 
						 | 
					b29081d4e8 | ||
| 
						 | 
					654097c438 | ||
| 
						 | 
					1e672868c4 | ||
| 
						 | 
					bff32827b5 | ||
| 
						 | 
					65f9b29b86 | ||
| 
						 | 
					9868f3a4c3 | ||
| 
						 | 
					b4879d13b8 | ||
| 
						 | 
					95964b5487 | ||
| 
						 | 
					4e17da0a30 | ||
| 
						 | 
					d8bf155f0e | ||
| 
						 | 
					0b4f85230a | ||
| 
						 | 
					0dff4ca438 | ||
| 
						 | 
					cc29437ede | ||
| 
						 | 
					dfaaf361f3 | ||
| 
						 | 
					37538310d3 | ||
| 
						 | 
					c7ae08abc3 | ||
| 
						 | 
					69973eaa02 | ||
| 
						 | 
					d3b2c7ea18 | ||
| 
						 | 
					68f41f685d | ||
| 
						 | 
					255279eb22 | ||
| 
						 | 
					fe3cffbb67 | ||
| 
						 | 
					2a91849a38 | ||
| 
						 | 
					082de76eef | ||
| 
						 | 
					570ae78b15 | ||
| 
						 | 
					885ac6f947 | ||
| 
						 | 
					8328f74751 | ||
| 
						 | 
					01b5079562 | ||
| 
						 | 
					6b23647bec | ||
| 
						 | 
					193fdb225e | ||
| 
						 | 
					c8ce416aba | ||
| 
						 | 
					1388662132 | ||
| 
						 | 
					757627427b | ||
| 
						 | 
					7cbd80af33 | ||
| 
						 | 
					16c448b89b | ||
| 
						 | 
					c218e365bd | ||
| 
						 | 
					f507dbbe00 | ||
| 
						 | 
					9966beb99b | ||
| 
						 | 
					f4713332c0 | ||
| 
						 | 
					63ff5b1d98 | ||
| 
						 | 
					cb0a4703bd | ||
| 
						 | 
					7e71b85668 | ||
| 
						 | 
					1dd7bcb0a6 | ||
| 
						 | 
					bbf6ab1206 | ||
| 
						 | 
					8cb1c68166 | ||
| 
						 | 
					d752240efc | 
							
								
								
									
										5
									
								
								.changeset/angry-bags-brake.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/angry-bags-brake.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
'mermaid': patch
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
fix: architecture diagrams no longer grow to extreme heights due to conflicting alignments
 | 
			
		||||
							
								
								
									
										5
									
								
								.changeset/lucky-points-wave.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/lucky-points-wave.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
'mermaid': minor
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Rename internal fields for class diagrams to match documentation and usage
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
./packages/mermaid/src/docs/community/contributing.md
 | 
			
		||||
./packages/mermaid/src/docs/community/contributing.md
 | 
			
		||||
 
 | 
			
		||||
@@ -171,6 +171,58 @@ describe.skip('architecture diagram', () => {
 | 
			
		||||
            `
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should render an architecture diagram with a resonable height', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `architecture-beta
 | 
			
		||||
              group federated(cloud)[Federated Environment]
 | 
			
		||||
                  service server1(server)[System] in federated
 | 
			
		||||
                  service edge(server)[Edge Device] in federated
 | 
			
		||||
                  server1:R -- L:edge
 | 
			
		||||
 | 
			
		||||
              group on_prem(cloud)[Hub]
 | 
			
		||||
                  service firewall(server)[Firewall Device] in on_prem
 | 
			
		||||
                  service server(server)[Server] in on_prem
 | 
			
		||||
                  firewall:R -- L:server
 | 
			
		||||
 | 
			
		||||
                  service db1(database)[db1] in on_prem
 | 
			
		||||
                  service db2(database)[db2] in on_prem
 | 
			
		||||
                  service db3(database)[db3] in on_prem
 | 
			
		||||
                  service db4(database)[db4] in on_prem
 | 
			
		||||
                  service db5(database)[db5] in on_prem
 | 
			
		||||
                  service db6(database)[db6] in on_prem
 | 
			
		||||
 | 
			
		||||
                  junction mid in on_prem
 | 
			
		||||
                  server:B -- T:mid
 | 
			
		||||
 | 
			
		||||
                  junction 1Leftofmid in on_prem
 | 
			
		||||
                  1Leftofmid:R -- L:mid
 | 
			
		||||
                  1Leftofmid:B -- T:db1
 | 
			
		||||
 | 
			
		||||
                  junction 2Leftofmid in on_prem
 | 
			
		||||
                  2Leftofmid:R -- L:1Leftofmid
 | 
			
		||||
                  2Leftofmid:B -- T:db2
 | 
			
		||||
 | 
			
		||||
                  junction 3Leftofmid in on_prem
 | 
			
		||||
                  3Leftofmid:R -- L:2Leftofmid
 | 
			
		||||
                  3Leftofmid:B -- T:db3
 | 
			
		||||
 | 
			
		||||
                  junction 1RightOfMid in on_prem
 | 
			
		||||
                  mid:R -- L:1RightOfMid
 | 
			
		||||
                  1RightOfMid:B -- T:db4
 | 
			
		||||
                  
 | 
			
		||||
                  junction 2RightOfMid in on_prem
 | 
			
		||||
                  1RightOfMid:R -- L:2RightOfMid
 | 
			
		||||
                  2RightOfMid:B -- T:db5        
 | 
			
		||||
                  
 | 
			
		||||
                  junction 3RightOfMid in on_prem
 | 
			
		||||
                  2RightOfMid:R -- L:3RightOfMid
 | 
			
		||||
                  3RightOfMid:B -- T:db6         
 | 
			
		||||
 | 
			
		||||
                  edge:R -- L:firewall
 | 
			
		||||
      `
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
 | 
			
		||||
 
 | 
			
		||||
@@ -650,12 +650,12 @@ class Class10
 | 
			
		||||
      { logLevel: 1, htmlLabels: true, layout: 'elk' }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  it('ELK: should render a class with a text label, members and annotation', () => {
 | 
			
		||||
  it('ELK: should render a class with a text label, attribute and annotation', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `classDiagram
 | 
			
		||||
        class C1["Class 1 with text label"] {
 | 
			
		||||
            <<interface>>
 | 
			
		||||
            +member1
 | 
			
		||||
            +attribute1
 | 
			
		||||
        }
 | 
			
		||||
        C1 -->  C2`,
 | 
			
		||||
      { logLevel: 1, htmlLabels: true, layout: 'elk' }
 | 
			
		||||
 
 | 
			
		||||
@@ -650,12 +650,12 @@ class Class10
 | 
			
		||||
      { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  it('HD: should render a class with a text label, members and annotation', () => {
 | 
			
		||||
  it('HD: should render a class with a text label, attribute and annotation', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `classDiagram
 | 
			
		||||
  class C1["Class 1 with text label"] {
 | 
			
		||||
    <<interface>>
 | 
			
		||||
    +member1
 | 
			
		||||
    +attribute1
 | 
			
		||||
  }
 | 
			
		||||
  C1 -->  C2`,
 | 
			
		||||
      { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
 | 
			
		||||
 
 | 
			
		||||
@@ -500,12 +500,12 @@ class Class10
 | 
			
		||||
  C1 -->  C2`
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  it('should render a class with a text label, members and annotation', () => {
 | 
			
		||||
  it('should render a class with a text label, attribute and annotation', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `classDiagram
 | 
			
		||||
  class C1["Class 1 with text label"] {
 | 
			
		||||
    <<interface>>
 | 
			
		||||
    +member1
 | 
			
		||||
    +attribute1
 | 
			
		||||
  }
 | 
			
		||||
  C1 -->  C2`
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -647,12 +647,12 @@ class Class10
 | 
			
		||||
  C1 -->  C2`
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  it('should render a class with a text label, members and annotation', () => {
 | 
			
		||||
  it('should render a class with a text label, attribute and annotation', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `classDiagram
 | 
			
		||||
  class C1["Class 1 with text label"] {
 | 
			
		||||
    <<interface>>
 | 
			
		||||
    +member1
 | 
			
		||||
    +attribute1
 | 
			
		||||
  }
 | 
			
		||||
  C1 -->  C2`
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -430,7 +430,7 @@ describe('Class diagram', () => {
 | 
			
		||||
      class \`This\nTitle\nHas\nMany\nNewlines\` {
 | 
			
		||||
        +String Also
 | 
			
		||||
        -Stirng Many
 | 
			
		||||
        #int Members
 | 
			
		||||
        #int attribute
 | 
			
		||||
        +And()
 | 
			
		||||
        -Many()
 | 
			
		||||
        #Methods()
 | 
			
		||||
@@ -444,7 +444,7 @@ describe('Class diagram', () => {
 | 
			
		||||
      class \`This\nTitle\nHas\nMany\nNewlines\` {
 | 
			
		||||
        +String Also
 | 
			
		||||
        -Stirng Many
 | 
			
		||||
        #int Members
 | 
			
		||||
        #int attribute
 | 
			
		||||
        +And()
 | 
			
		||||
        -Many()
 | 
			
		||||
        #Methods()
 | 
			
		||||
@@ -460,7 +460,7 @@ describe('Class diagram', () => {
 | 
			
		||||
      class \`This\nTitle\nHas\nMany\nNewlines\` {
 | 
			
		||||
        +String Also
 | 
			
		||||
        -Stirng Many
 | 
			
		||||
        #int Members
 | 
			
		||||
        #int attribute
 | 
			
		||||
        +And()
 | 
			
		||||
        -Many()
 | 
			
		||||
        #Methods()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
 | 
			
		||||
 | 
			
		||||
describe.skip('Flowchart ELK', () => {
 | 
			
		||||
describe('Flowchart ELK', () => {
 | 
			
		||||
  it('1-elk: should render a simple flowchart', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `flowchart-elk TD
 | 
			
		||||
@@ -857,6 +857,196 @@ flowchart LR
 | 
			
		||||
    D --> E
 | 
			
		||||
      A["A"]
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
      it('6080: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
 subgraph s1["Untitled subgraph"]
 | 
			
		||||
        n1["Evaluate"]
 | 
			
		||||
        n2["Option 1"]
 | 
			
		||||
        n3["Option 2"]
 | 
			
		||||
        n4["fa:fa-car Option 3"]
 | 
			
		||||
  end
 | 
			
		||||
 subgraph s2["Untitled subgraph"]
 | 
			
		||||
        n5["Evaluate"]
 | 
			
		||||
        n6["Option 1"]
 | 
			
		||||
        n7["Option 2"]
 | 
			
		||||
        n8["fa:fa-car Option 3"]
 | 
			
		||||
  end
 | 
			
		||||
    A["Start"] -- Some text --> B("Continue")
 | 
			
		||||
    B --> C{"Evaluate"}
 | 
			
		||||
    C -- One --> D["Option 1"]
 | 
			
		||||
    C -- Two --> E["Option 2"]
 | 
			
		||||
    C -- Three --> F["fa:fa-car Option 3"]
 | 
			
		||||
    n1 -- One --> n2
 | 
			
		||||
    n1 -- Two --> n3
 | 
			
		||||
    n1 -- Three --> n4
 | 
			
		||||
    n5 -- One --> n6
 | 
			
		||||
    n5 -- Two --> n7
 | 
			
		||||
    n5 -- Three --> n8
 | 
			
		||||
    n1@{ shape: diam}
 | 
			
		||||
    n2@{ shape: rect}
 | 
			
		||||
    n3@{ shape: rect}
 | 
			
		||||
    n4@{ shape: rect}
 | 
			
		||||
    n5@{ shape: diam}
 | 
			
		||||
    n6@{ shape: rect}
 | 
			
		||||
    n7@{ shape: rect}
 | 
			
		||||
    n8@{ shape: rect}
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('6088-1: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
      flowchart LR
 | 
			
		||||
      subgraph S2
 | 
			
		||||
      subgraph s1["APA"]
 | 
			
		||||
      D{"Use the editor"}
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      D -- Mermaid js --> I{"fa:fa-code Text"}
 | 
			
		||||
            D --> I
 | 
			
		||||
            D --> I
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('6088-2: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
      flowchart LR
 | 
			
		||||
      a
 | 
			
		||||
      subgraph s0["APA"]
 | 
			
		||||
      subgraph s8["APA"]
 | 
			
		||||
      subgraph s1["APA"]
 | 
			
		||||
        D{"X"}
 | 
			
		||||
        E[Q]
 | 
			
		||||
      end
 | 
			
		||||
      subgraph s3["BAPA"]
 | 
			
		||||
        F[Q]
 | 
			
		||||
        I
 | 
			
		||||
      end
 | 
			
		||||
            D --> I
 | 
			
		||||
            D --> I
 | 
			
		||||
            D --> I
 | 
			
		||||
 | 
			
		||||
      I{"X"}
 | 
			
		||||
      end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('6088-3: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
      flowchart LR
 | 
			
		||||
      a
 | 
			
		||||
        D{"Use the editor"}
 | 
			
		||||
 | 
			
		||||
      D -- Mermaid js --> I{"fa:fa-code Text"}
 | 
			
		||||
      D-->I
 | 
			
		||||
      D-->I
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('6088-4: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
 subgraph s1["Untitled subgraph"]
 | 
			
		||||
        n1["Evaluate"]
 | 
			
		||||
        n2["Option 1"]
 | 
			
		||||
        n3["Option 2"]
 | 
			
		||||
        n4["fa:fa-car Option 3"]
 | 
			
		||||
  end
 | 
			
		||||
 subgraph s2["Untitled subgraph"]
 | 
			
		||||
        n5["Evaluate"]
 | 
			
		||||
        n6["Option 1"]
 | 
			
		||||
        n7["Option 2"]
 | 
			
		||||
        n8["fa:fa-car Option 3"]
 | 
			
		||||
  end
 | 
			
		||||
    A["Start"] -- Some text --> B("Continue")
 | 
			
		||||
    B --> C{"Evaluate"}
 | 
			
		||||
    C -- One --> D["Option 1"]
 | 
			
		||||
    C -- Two --> E["Option 2"]
 | 
			
		||||
    C -- Three --> F["fa:fa-car Option 3"]
 | 
			
		||||
    n1 -- One --> n2
 | 
			
		||||
    n1 -- Two --> n3
 | 
			
		||||
    n1 -- Three --> n4
 | 
			
		||||
    n5 -- One --> n6
 | 
			
		||||
    n5 -- Two --> n7
 | 
			
		||||
    n5 -- Three --> n8
 | 
			
		||||
    n1@{ shape: diam}
 | 
			
		||||
    n2@{ shape: rect}
 | 
			
		||||
    n3@{ shape: rect}
 | 
			
		||||
    n4@{ shape: rect}
 | 
			
		||||
    n5@{ shape: diam}
 | 
			
		||||
    n6@{ shape: rect}
 | 
			
		||||
    n7@{ shape: rect}
 | 
			
		||||
    n8@{ shape: rect}
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('6088-5: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
    A{A} --> B & C
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
      it('6088-6: should handle diamond shape intersections', () => {
 | 
			
		||||
        imgSnapshotTest(
 | 
			
		||||
          `---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
    A{A} --> B & C
 | 
			
		||||
    subgraph "subbe"
 | 
			
		||||
      A
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
          { flowchart: { titleTopMargin: 0 } }
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -89,70 +89,239 @@
 | 
			
		||||
 | 
			
		||||
  <body>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
      flowchart LR
 | 
			
		||||
      subgraph S2
 | 
			
		||||
      subgraph s1["APA"]
 | 
			
		||||
      D{"Use the editor"}
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      D -- Mermaid js --> I{"fa:fa-code Text"}
 | 
			
		||||
            D --> I
 | 
			
		||||
            D --> I
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
      flowchart LR
 | 
			
		||||
      a
 | 
			
		||||
      subgraph s0["APA"]
 | 
			
		||||
      subgraph s8["APA"]
 | 
			
		||||
      subgraph s1["APA"]
 | 
			
		||||
        D{"X"}
 | 
			
		||||
        E[Q]
 | 
			
		||||
      end
 | 
			
		||||
      subgraph s3["BAPA"]
 | 
			
		||||
        F[Q]
 | 
			
		||||
        I
 | 
			
		||||
      end
 | 
			
		||||
            D --> I
 | 
			
		||||
            D --> I
 | 
			
		||||
            D --> I
 | 
			
		||||
 | 
			
		||||
      I{"X"}
 | 
			
		||||
      end
 | 
			
		||||
      end
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
      flowchart LR
 | 
			
		||||
      a
 | 
			
		||||
        D{"Use the editor"}
 | 
			
		||||
 | 
			
		||||
      D -- Mermaid js --> I{"fa:fa-code Text"}
 | 
			
		||||
      D-->I
 | 
			
		||||
      D-->I
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
 subgraph s1["Untitled subgraph"]
 | 
			
		||||
        n1["Evaluate"]
 | 
			
		||||
        n2["Option 1"]
 | 
			
		||||
        n3["Option 2"]
 | 
			
		||||
        n4["fa:fa-car Option 3"]
 | 
			
		||||
  end
 | 
			
		||||
 subgraph s2["Untitled subgraph"]
 | 
			
		||||
        n5["Evaluate"]
 | 
			
		||||
        n6["Option 1"]
 | 
			
		||||
        n7["Option 2"]
 | 
			
		||||
        n8["fa:fa-car Option 3"]
 | 
			
		||||
  end
 | 
			
		||||
    A["Start"] -- Some text --> B("Continue")
 | 
			
		||||
    B --> C{"Evaluate"}
 | 
			
		||||
    C -- One --> D["Option 1"]
 | 
			
		||||
    C -- Two --> E["Option 2"]
 | 
			
		||||
    C -- Three --> F["fa:fa-car Option 3"]
 | 
			
		||||
    n1 -- One --> n2
 | 
			
		||||
    n1 -- Two --> n3
 | 
			
		||||
    n1 -- Three --> n4
 | 
			
		||||
    n5 -- One --> n6
 | 
			
		||||
    n5 -- Two --> n7
 | 
			
		||||
    n5 -- Three --> n8
 | 
			
		||||
    n1@{ shape: diam}
 | 
			
		||||
    n2@{ shape: rect}
 | 
			
		||||
    n3@{ shape: rect}
 | 
			
		||||
    n4@{ shape: rect}
 | 
			
		||||
    n5@{ shape: diam}
 | 
			
		||||
    n6@{ shape: rect}
 | 
			
		||||
    n7@{ shape: rect}
 | 
			
		||||
    n8@{ shape: rect}
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
 subgraph s1["Untitled subgraph"]
 | 
			
		||||
        n1["Evaluate"]
 | 
			
		||||
        n2["Option 1"]
 | 
			
		||||
  end
 | 
			
		||||
    n1 -- One --> n2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
    A{A} --> B & C
 | 
			
		||||
</pre
 | 
			
		||||
    >
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
    A{A} --> B & C
 | 
			
		||||
    subgraph "subbe"
 | 
			
		||||
      A
 | 
			
		||||
    end
 | 
			
		||||
</pre
 | 
			
		||||
    >
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  layout: elk
 | 
			
		||||
---
 | 
			
		||||
flowchart LR
 | 
			
		||||
    n2@{ shape: rect}
 | 
			
		||||
    n3@{ shape: rect}
 | 
			
		||||
    n4@{ shape: rect}
 | 
			
		||||
    A["Start"] -- Some text --> B("Continue")
 | 
			
		||||
    B --> C{"Evaluate"}
 | 
			
		||||
    C -- One --> D["Option 1"]
 | 
			
		||||
    C -- Two --> E["Option 2"]
 | 
			
		||||
    C -- Three --> F["fa:fa-car Option 3"]
 | 
			
		||||
    %% C@{ shape: hexagon}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
---
 | 
			
		||||
config:
 | 
			
		||||
  kanban:
 | 
			
		||||
    ticketBaseUrl: 'https://github.com/your-repo/issues/#TICKET#'
 | 
			
		||||
---
 | 
			
		||||
kanban
 | 
			
		||||
  Backlog
 | 
			
		||||
    task1[📝 Define project requirements]@{ ticket: a101 }
 | 
			
		||||
  To Do
 | 
			
		||||
    task2[🔍 Research technologies]@{ ticket: a102 }
 | 
			
		||||
  Review
 | 
			
		||||
    task4[🔍 Code review for login feature]@{ ticket: a104 }
 | 
			
		||||
  Done
 | 
			
		||||
    task5[✅ Deploy initial version]@{ ticket: a105 }
 | 
			
		||||
  In Progress
 | 
			
		||||
    task3[💻 Develop login feature]@{ ticket: 103 }
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
 | 
			
		||||
style A fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
 | 
			
		||||
A:::AClass
 | 
			
		||||
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
  nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
 | 
			
		||||
style A fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
 | 
			
		||||
A:::AClass
 | 
			
		||||
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
  nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
 | 
			
		||||
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
 | 
			
		||||
style A fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
 | 
			
		||||
A:::AClass
 | 
			
		||||
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
  nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
 | 
			
		||||
  A:::AClass
 | 
			
		||||
  classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
    </pre>
 | 
			
		||||
    <pre id="diagram4" class="mermaid">
 | 
			
		||||
    <pre id="diagram4" class="mermaid2">
 | 
			
		||||
flowchart LR
 | 
			
		||||
  nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
 | 
			
		||||
  style A fill:#f9f,stroke:#333,stroke-width:4px
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,8 @@ graph TB
 | 
			
		||||
 | 
			
		||||
    <script type="module">
 | 
			
		||||
      import mermaid from '/mermaid.esm.mjs';
 | 
			
		||||
      import flowchartELK from '/mermaid-flowchart-elk.esm.mjs';
 | 
			
		||||
      await mermaid.registerExternalDiagrams([flowchartELK]);
 | 
			
		||||
      import layouts from '/mermaid-layout-elk.esm.mjs';
 | 
			
		||||
      mermaid.registerLayoutLoaders(layouts);
 | 
			
		||||
      async function render(str) {
 | 
			
		||||
        const { svg } = await mermaid.render('dynamic', str);
 | 
			
		||||
        document.getElementById('dynamicDiagram').innerHTML = svg;
 | 
			
		||||
 
 | 
			
		||||
@@ -185,8 +185,6 @@ Communication tools and platforms
 | 
			
		||||
  - [=Diagram block](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
 | 
			
		||||
- [Standard Notes](https://standardnotes.com/)
 | 
			
		||||
  - [Mermaid Extension](https://github.com/nienow/sn-mermaid)
 | 
			
		||||
- [Sublime Text 3](https://sublimetext.com)
 | 
			
		||||
  - [Mermaid Package](https://packagecontrol.io/packages/Mermaid)
 | 
			
		||||
- [VS Code](https://code.visualstudio.com/)
 | 
			
		||||
  - [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
 | 
			
		||||
  - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,18 @@
 | 
			
		||||
 | 
			
		||||
# Blog
 | 
			
		||||
 | 
			
		||||
## [Mermaid 11.4 is out: New Features and Kanban Diagramming](https://www.mermaidchart.com/blog/posts/mermaid-11-4-is-out-new-features-and-kanban-diagramming)
 | 
			
		||||
 | 
			
		||||
Mermaid 11.4 brings enhanced functionality with the introduction of Kanban diagrams, allowing users to create visual workflows with status columns and task details.
 | 
			
		||||
 | 
			
		||||
October 31, 2024 · 2 mins
 | 
			
		||||
 | 
			
		||||
## [How To Build an ER Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-build-an-er-diagram-with-mermaid-chart)
 | 
			
		||||
 | 
			
		||||
An entity relationship (ER) diagram acts like a blueprint for your database. This makes ER diagrams effective tools for anyone dealing with complex databases, data modeling, and AI model training.
 | 
			
		||||
 | 
			
		||||
October 24, 2024 · 4 mins
 | 
			
		||||
 | 
			
		||||
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
 | 
			
		||||
 | 
			
		||||
24 September 2024 · 5 mins
 | 
			
		||||
 
 | 
			
		||||
@@ -500,7 +500,7 @@ mermaid.ganttConfig = {
 | 
			
		||||
  sectionFontSize: 24, // Font size for sections
 | 
			
		||||
  numberSectionStyles: 1, // The number of alternating section styles
 | 
			
		||||
  axisFormat: '%d/%m', // Date/time format of the axis
 | 
			
		||||
  tickInterval: '1 week', // Axis ticks
 | 
			
		||||
  tickInterval: '1week', // Axis ticks
 | 
			
		||||
  topAxis: true, // When this flag is set, date labels will be added to the top of the chart
 | 
			
		||||
  displayMode: 'compact', // Turns compact mode on
 | 
			
		||||
  weekday: 'sunday', // On which day a week-based interval should start
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@ todo[Todo]
 | 
			
		||||
 | 
			
		||||
## Configuration Options
 | 
			
		||||
 | 
			
		||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams tacketBaseUrl. This can be set as in the the following example:
 | 
			
		||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams `ticketBaseUrl`. This can be set as in the the following example:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
---
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,20 @@
 | 
			
		||||
# @mermaid-js/layout-elk
 | 
			
		||||
 | 
			
		||||
## 0.1.7
 | 
			
		||||
 | 
			
		||||
### Patch Changes
 | 
			
		||||
 | 
			
		||||
- [#6090](https://github.com/mermaid-js/mermaid/pull/6090) [`654097c`](https://github.com/mermaid-js/mermaid/commit/654097c43801b2d606bc3d2bef8c6fbc3301e9e4) Thanks [@knsv](https://github.com/knsv)! - fix: Updated offset calculations for diamond shape when handling intersections
 | 
			
		||||
 | 
			
		||||
## 0.1.6
 | 
			
		||||
 | 
			
		||||
### Patch Changes
 | 
			
		||||
 | 
			
		||||
- [#6081](https://github.com/mermaid-js/mermaid/pull/6081) [`68f41f6`](https://github.com/mermaid-js/mermaid/commit/68f41f685d2afe7d12f63aabf3de0c3461898471) Thanks [@knsv](https://github.com/knsv)! - fix: Elk rendering of Diamond shape intersections
 | 
			
		||||
 | 
			
		||||
- Updated dependencies [[`01b5079`](https://github.com/mermaid-js/mermaid/commit/01b5079562ec8d34ce9964910f168873843c68f8), [`1388662`](https://github.com/mermaid-js/mermaid/commit/1388662132cc829f9820c2e9970ae04e2dd90588), [`fe3cffb`](https://github.com/mermaid-js/mermaid/commit/fe3cffbb673a25b81989aacb06e5d0eda35326db)]:
 | 
			
		||||
  - mermaid@11.4.1
 | 
			
		||||
 | 
			
		||||
## 0.1.5
 | 
			
		||||
 | 
			
		||||
### Patch Changes
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@mermaid-js/layout-elk",
 | 
			
		||||
  "version": "0.1.5",
 | 
			
		||||
  "version": "0.1.7",
 | 
			
		||||
  "description": "ELK layout engine for mermaid",
 | 
			
		||||
  "module": "dist/mermaid-layout-elk.core.mjs",
 | 
			
		||||
  "types": "dist/layouts.d.ts",
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ interface LabelData {
 | 
			
		||||
interface NodeWithVertex extends Omit<Node, 'domId'> {
 | 
			
		||||
  children?: unknown[];
 | 
			
		||||
  labelData?: LabelData;
 | 
			
		||||
 | 
			
		||||
  domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -484,6 +485,8 @@ export const render = async (
 | 
			
		||||
    const r3 = a1 * q1.x + b1 * q1.y + c1;
 | 
			
		||||
    const r4 = a1 * q2.x + b1 * q2.y + c1;
 | 
			
		||||
 | 
			
		||||
    const epsilon = 1e-6;
 | 
			
		||||
 | 
			
		||||
    // Check signs of r3 and r4. If both point 3 and point 4 lie on
 | 
			
		||||
    // same side of line 1, the line segments do not intersect.
 | 
			
		||||
    if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
 | 
			
		||||
@@ -502,7 +505,7 @@ export const render = async (
 | 
			
		||||
    // Check signs of r1 and r2. If both point 1 and point 2 lie
 | 
			
		||||
    // on same side of second line segment, the line segments do
 | 
			
		||||
    // not intersect.
 | 
			
		||||
    if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
 | 
			
		||||
    if (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && sameSign(r1, r2)) {
 | 
			
		||||
      return /*DON'T_INTERSECT*/;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -547,11 +550,11 @@ export const render = async (
 | 
			
		||||
      { x: x1 - w / 2, y: y1 },
 | 
			
		||||
    ];
 | 
			
		||||
    log.debug(
 | 
			
		||||
      `UIO diamondIntersection calc abc89:
 | 
			
		||||
      `APA16 diamondIntersection calc abc89:
 | 
			
		||||
  outsidePoint: ${JSON.stringify(outsidePoint)}
 | 
			
		||||
  insidePoint : ${JSON.stringify(insidePoint)}
 | 
			
		||||
  node        : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
 | 
			
		||||
      polyPoints
 | 
			
		||||
  node-bounds       : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
 | 
			
		||||
      JSON.stringify(polyPoints)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const intersections = [];
 | 
			
		||||
@@ -564,8 +567,8 @@ export const render = async (
 | 
			
		||||
      minY = Math.min(minY, entry.y);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // const left = x1 - w / 2;
 | 
			
		||||
    // const top = y1 + h / 2;
 | 
			
		||||
    const left = x1 - w / 2 - minX;
 | 
			
		||||
    const top = y1 - h / 2 - minY;
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < polyPoints.length; i++) {
 | 
			
		||||
      const p1 = polyPoints[i];
 | 
			
		||||
@@ -573,8 +576,8 @@ export const render = async (
 | 
			
		||||
      const intersect = intersectLine(
 | 
			
		||||
        bounds,
 | 
			
		||||
        outsidePoint,
 | 
			
		||||
        { x: p1.x, y: p1.y },
 | 
			
		||||
        { x: p2.x, y: p2.y }
 | 
			
		||||
        { x: left + p1.x, y: top + p1.y },
 | 
			
		||||
        { x: left + p2.x, y: top + p2.y }
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (intersect) {
 | 
			
		||||
@@ -703,14 +706,11 @@ export const render = async (
 | 
			
		||||
    bounds: { x: any; y: any; width: any; height: any; padding: any },
 | 
			
		||||
    isDiamond: boolean
 | 
			
		||||
  ) => {
 | 
			
		||||
    log.debug('UIO cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond);
 | 
			
		||||
    log.debug('APA18 cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond);
 | 
			
		||||
    const points: any[] = [];
 | 
			
		||||
    let lastPointOutside = _points[0];
 | 
			
		||||
    let isInside = false;
 | 
			
		||||
    _points.forEach((point: any) => {
 | 
			
		||||
      // const node = clusterDb[edge.toCluster].node;
 | 
			
		||||
      log.debug(' checking point', point, bounds);
 | 
			
		||||
 | 
			
		||||
      // check if point is inside the boundary rect
 | 
			
		||||
      if (!outsideNode(bounds, point) && !isInside) {
 | 
			
		||||
        // First point inside the rect found
 | 
			
		||||
@@ -753,7 +753,6 @@ export const render = async (
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    log.debug('returning points', points);
 | 
			
		||||
    return points;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -905,7 +904,7 @@ export const render = async (
 | 
			
		||||
 | 
			
		||||
      const offset = calcOffset(sourceId, targetId, parentLookupDb);
 | 
			
		||||
      log.debug(
 | 
			
		||||
        'offset',
 | 
			
		||||
        'APA18 offset',
 | 
			
		||||
        offset,
 | 
			
		||||
        sourceId,
 | 
			
		||||
        ' ==> ',
 | 
			
		||||
@@ -968,48 +967,41 @@ export const render = async (
 | 
			
		||||
            startNode.innerHTML
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        if (startNode.shape === 'diamond') {
 | 
			
		||||
        if (startNode.shape === 'diamond' || startNode.shape === 'diam') {
 | 
			
		||||
          edge.points.unshift({
 | 
			
		||||
            x: startNode.x + startNode.width / 2 + offset.x,
 | 
			
		||||
            y: startNode.y + startNode.height / 2 + offset.y,
 | 
			
		||||
            x: startNode.offset.posX + startNode.width / 2,
 | 
			
		||||
            y: startNode.offset.posY + startNode.height / 2,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        if (endNode.shape === 'diamond') {
 | 
			
		||||
          const x = endNode.x + endNode.width / 2 + offset.x;
 | 
			
		||||
          // Add a point at the center of the diamond
 | 
			
		||||
          if (
 | 
			
		||||
            Math.abs(edge.points[edge.points.length - 1].y - endNode.y - offset.y) > 0.001 ||
 | 
			
		||||
            Math.abs(edge.points[edge.points.length - 1].x - x) > 0.001
 | 
			
		||||
          ) {
 | 
			
		||||
            edge.points.push({
 | 
			
		||||
              x: endNode.x + endNode.width / 2 + offset.x,
 | 
			
		||||
              y: endNode.y + endNode.height / 2 + offset.y,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        if (endNode.shape === 'diamond' || endNode.shape === 'diam') {
 | 
			
		||||
          edge.points.push({
 | 
			
		||||
            x: endNode.offset.posX + endNode.width / 2,
 | 
			
		||||
            y: endNode.offset.posY + endNode.height / 2,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        edge.points = cutPathAtIntersect(
 | 
			
		||||
          edge.points.reverse(),
 | 
			
		||||
          {
 | 
			
		||||
            x: startNode.x + startNode.width / 2 + offset.x,
 | 
			
		||||
            y: startNode.y + startNode.height / 2 + offset.y,
 | 
			
		||||
            x: startNode.offset.posX + startNode.width / 2,
 | 
			
		||||
            y: startNode.offset.posY + startNode.height / 2,
 | 
			
		||||
            width: sw,
 | 
			
		||||
            height: startNode.height,
 | 
			
		||||
            padding: startNode.padding,
 | 
			
		||||
          },
 | 
			
		||||
          startNode.shape === 'diamond'
 | 
			
		||||
          startNode.shape === 'diamond' || startNode.shape === 'diam'
 | 
			
		||||
        ).reverse();
 | 
			
		||||
 | 
			
		||||
        edge.points = cutPathAtIntersect(
 | 
			
		||||
          edge.points,
 | 
			
		||||
          {
 | 
			
		||||
            x: endNode.x + ew / 2 + endNode.offset.x,
 | 
			
		||||
            y: endNode.y + endNode.height / 2 + endNode.offset.y,
 | 
			
		||||
            x: endNode.offset.posX + endNode.width / 2,
 | 
			
		||||
            y: endNode.offset.posY + endNode.height / 2,
 | 
			
		||||
            width: ew,
 | 
			
		||||
            height: endNode.height,
 | 
			
		||||
            padding: endNode.padding,
 | 
			
		||||
          },
 | 
			
		||||
          endNode.shape === 'diamond'
 | 
			
		||||
          endNode.shape === 'diamond' || endNode.shape === 'diam'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const paths = insertEdge(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,15 @@
 | 
			
		||||
# mermaid
 | 
			
		||||
 | 
			
		||||
## 11.4.1
 | 
			
		||||
 | 
			
		||||
### Patch Changes
 | 
			
		||||
 | 
			
		||||
- [#6059](https://github.com/mermaid-js/mermaid/pull/6059) [`01b5079`](https://github.com/mermaid-js/mermaid/commit/01b5079562ec8d34ce9964910f168873843c68f8) Thanks [@knsv](https://github.com/knsv)! - fix: Kanban diagrams will not render when adding a number as ticket id or assigned for a task
 | 
			
		||||
 | 
			
		||||
- [#6038](https://github.com/mermaid-js/mermaid/pull/6038) [`1388662`](https://github.com/mermaid-js/mermaid/commit/1388662132cc829f9820c2e9970ae04e2dd90588) Thanks [@knsv](https://github.com/knsv)! - fix: Intersection calculations for tilted cylinder/DAS when using handdrawn look. Some random seeds could cause the calculations to break.
 | 
			
		||||
 | 
			
		||||
- [#6079](https://github.com/mermaid-js/mermaid/pull/6079) [`fe3cffb`](https://github.com/mermaid-js/mermaid/commit/fe3cffbb673a25b81989aacb06e5d0eda35326db) Thanks [@aloisklink](https://github.com/aloisklink)! - Bump dompurify to `^3.2.1`. This removes the need for `@types/dompurify`.
 | 
			
		||||
 | 
			
		||||
## 11.4.0
 | 
			
		||||
 | 
			
		||||
### Minor Changes
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "mermaid",
 | 
			
		||||
  "version": "11.4.0",
 | 
			
		||||
  "version": "11.4.1",
 | 
			
		||||
  "description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "module": "./dist/mermaid.core.mjs",
 | 
			
		||||
@@ -71,7 +71,6 @@
 | 
			
		||||
    "@iconify/utils": "^2.1.32",
 | 
			
		||||
    "@mermaid-js/parser": "workspace:^",
 | 
			
		||||
    "@types/d3": "^7.4.3",
 | 
			
		||||
    "@types/dompurify": "^3.0.5",
 | 
			
		||||
    "cytoscape": "^3.29.2",
 | 
			
		||||
    "cytoscape-cose-bilkent": "^4.1.0",
 | 
			
		||||
    "cytoscape-fcose": "^2.2.0",
 | 
			
		||||
@@ -79,7 +78,7 @@
 | 
			
		||||
    "d3-sankey": "^0.12.3",
 | 
			
		||||
    "dagre-d3-es": "7.0.11",
 | 
			
		||||
    "dayjs": "^1.11.10",
 | 
			
		||||
    "dompurify": "^3.0.11 <3.1.7",
 | 
			
		||||
    "dompurify": "^3.2.1",
 | 
			
		||||
    "katex": "^0.16.9",
 | 
			
		||||
    "khroma": "^2.1.0",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
 
 | 
			
		||||
@@ -950,8 +950,8 @@ const class_box = (parent, node) => {
 | 
			
		||||
    maxWidth = classTitleBBox.width;
 | 
			
		||||
  }
 | 
			
		||||
  const classAttributes = [];
 | 
			
		||||
  node.classData.members.forEach((member) => {
 | 
			
		||||
    const parsedInfo = member.getDisplayDetails();
 | 
			
		||||
  node.classData.attributes.forEach((attribute) => {
 | 
			
		||||
    const parsedInfo = attribute.getDisplayDetails();
 | 
			
		||||
    let parsedText = parsedInfo.displayText;
 | 
			
		||||
    if (getConfig().flowchart.htmlLabels) {
 | 
			
		||||
      parsedText = parsedText.replace(/</g, '<').replace(/>/g, '>');
 | 
			
		||||
@@ -984,8 +984,8 @@ const class_box = (parent, node) => {
 | 
			
		||||
  maxHeight += lineHeight;
 | 
			
		||||
 | 
			
		||||
  const classMethods = [];
 | 
			
		||||
  node.classData.methods.forEach((member) => {
 | 
			
		||||
    const parsedInfo = member.getDisplayDetails();
 | 
			
		||||
  node.classData.methods.forEach((method) => {
 | 
			
		||||
    const parsedInfo = method.getDisplayDetails();
 | 
			
		||||
    let displayText = parsedInfo.displayText;
 | 
			
		||||
    if (getConfig().flowchart.htmlLabels) {
 | 
			
		||||
      displayText = displayText.replace(/</g, '<').replace(/>/g, '>');
 | 
			
		||||
@@ -1059,9 +1059,9 @@ const class_box = (parent, node) => {
 | 
			
		||||
        ((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) +
 | 
			
		||||
        ')'
 | 
			
		||||
    );
 | 
			
		||||
    //get the height of the bounding box of each member if exists
 | 
			
		||||
    const memberBBox = lbl?.getBBox();
 | 
			
		||||
    verticalPos += (memberBBox?.height ?? 0) + rowPadding;
 | 
			
		||||
    //get the height of the bounding box of each attribute if exists
 | 
			
		||||
    const fieldBBox = lbl?.getBBox();
 | 
			
		||||
    verticalPos += (fieldBBox?.height ?? 0) + rowPadding;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  verticalPos += lineHeight;
 | 
			
		||||
@@ -1079,8 +1079,8 @@ const class_box = (parent, node) => {
 | 
			
		||||
      'transform',
 | 
			
		||||
      'translate( ' + -maxWidth / 2 + ', ' + ((-1 * maxHeight) / 2 + verticalPos) + ')'
 | 
			
		||||
    );
 | 
			
		||||
    const memberBBox = lbl?.getBBox();
 | 
			
		||||
    verticalPos += (memberBBox?.height ?? 0) + rowPadding;
 | 
			
		||||
    const methodBBox = lbl?.getBBox();
 | 
			
		||||
    verticalPos += (methodBBox?.height ?? 0) + rowPadding;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  rect
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import {
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
} from '../common/commonDb.js';
 | 
			
		||||
import type {
 | 
			
		||||
  ArchitectureAlignment,
 | 
			
		||||
  ArchitectureDB,
 | 
			
		||||
  ArchitectureDirectionPair,
 | 
			
		||||
  ArchitectureDirectionPairMap,
 | 
			
		||||
@@ -25,6 +26,7 @@ import type {
 | 
			
		||||
  ArchitectureState,
 | 
			
		||||
} from './architectureTypes.js';
 | 
			
		||||
import {
 | 
			
		||||
  getArchitectureDirectionAlignment,
 | 
			
		||||
  getArchitectureDirectionPair,
 | 
			
		||||
  isArchitectureDirection,
 | 
			
		||||
  isArchitectureJunction,
 | 
			
		||||
@@ -211,12 +213,18 @@ const addEdge = function ({
 | 
			
		||||
const getEdges = (): ArchitectureEdge[] => state.records.edges;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns the current diagram's adjacency list & spatial map.
 | 
			
		||||
 * Returns the current diagram's adjacency list, spatial map, & group alignments.
 | 
			
		||||
 * If they have not been created, run the algorithms to generate them.
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
const getDataStructures = () => {
 | 
			
		||||
  if (state.records.dataStructures === undefined) {
 | 
			
		||||
    // Tracks how groups are aligned with one another. Generated while creating the adj list
 | 
			
		||||
    const groupAlignments: Record<
 | 
			
		||||
      string,
 | 
			
		||||
      Record<string, Exclude<ArchitectureAlignment, 'bend'>>
 | 
			
		||||
    > = {};
 | 
			
		||||
 | 
			
		||||
    // Create an adjacency list of the diagram to perform BFS on
 | 
			
		||||
    // Outer reduce applied on all services
 | 
			
		||||
    // Inner reduce applied on the edges for a service
 | 
			
		||||
@@ -224,6 +232,19 @@ const getDataStructures = () => {
 | 
			
		||||
      Record<string, ArchitectureDirectionPairMap>
 | 
			
		||||
    >((prevOuter, [id, service]) => {
 | 
			
		||||
      prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
 | 
			
		||||
        // track the direction groups connect to one another
 | 
			
		||||
        const lhsGroupId = getNode(edge.lhsId)?.in;
 | 
			
		||||
        const rhsGroupId = getNode(edge.rhsId)?.in;
 | 
			
		||||
        if (lhsGroupId && rhsGroupId && lhsGroupId !== rhsGroupId) {
 | 
			
		||||
          const alignment = getArchitectureDirectionAlignment(edge.lhsDir, edge.rhsDir);
 | 
			
		||||
          if (alignment !== 'bend') {
 | 
			
		||||
            groupAlignments[lhsGroupId] ??= {};
 | 
			
		||||
            groupAlignments[lhsGroupId][rhsGroupId] = alignment;
 | 
			
		||||
            groupAlignments[rhsGroupId] ??= {};
 | 
			
		||||
            groupAlignments[rhsGroupId][lhsGroupId] = alignment;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (edge.lhsId === id) {
 | 
			
		||||
          // source is LHS
 | 
			
		||||
          const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
 | 
			
		||||
@@ -245,6 +266,7 @@ const getDataStructures = () => {
 | 
			
		||||
    // Configuration for the initial pass of BFS
 | 
			
		||||
    const firstId = Object.keys(adjList)[0];
 | 
			
		||||
    const visited = { [firstId]: 1 };
 | 
			
		||||
    // If a key is present in this object, it has not been visited
 | 
			
		||||
    const notVisited = Object.keys(adjList).reduce(
 | 
			
		||||
      (prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
 | 
			
		||||
      {} as Record<string, number>
 | 
			
		||||
@@ -283,6 +305,7 @@ const getDataStructures = () => {
 | 
			
		||||
    state.records.dataStructures = {
 | 
			
		||||
      adjList,
 | 
			
		||||
      spatialMaps,
 | 
			
		||||
      groupAlignments,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  return state.records.dataStructures;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,9 @@ import { setupGraphViewbox } from '../../setupGraphViewbox.js';
 | 
			
		||||
import { getConfigField } from './architectureDb.js';
 | 
			
		||||
import { architectureIcons } from './architectureIcons.js';
 | 
			
		||||
import type {
 | 
			
		||||
  ArchitectureAlignment,
 | 
			
		||||
  ArchitectureDataStructures,
 | 
			
		||||
  ArchitectureGroupAlignments,
 | 
			
		||||
  ArchitectureJunction,
 | 
			
		||||
  ArchitectureSpatialMap,
 | 
			
		||||
  EdgeSingular,
 | 
			
		||||
@@ -149,25 +151,91 @@ function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignmentConstraint {
 | 
			
		||||
function getAlignments(
 | 
			
		||||
  db: ArchitectureDB,
 | 
			
		||||
  spatialMaps: ArchitectureSpatialMap[],
 | 
			
		||||
  groupAlignments: ArchitectureGroupAlignments
 | 
			
		||||
): fcose.FcoseAlignmentConstraint {
 | 
			
		||||
  /**
 | 
			
		||||
   * Flattens the alignment object so nodes in different groups will be in the same alignment array IFF their groups don't connect in a conflicting alignment
 | 
			
		||||
   *
 | 
			
		||||
   * i.e., two groups which connect horizontally should not have nodes with vertical alignments to one another
 | 
			
		||||
   *
 | 
			
		||||
   * See: #5952
 | 
			
		||||
   *
 | 
			
		||||
   * @param alignmentObj - alignment object with the outer key being the row/col # and the inner key being the group name mapped to the nodes on that axis in the group
 | 
			
		||||
   * @param alignmentDir - alignment direction
 | 
			
		||||
   * @returns flattened alignment object with an arbitrary key mapping to nodes in the same row/col
 | 
			
		||||
   */
 | 
			
		||||
  const flattenAlignments = (
 | 
			
		||||
    alignmentObj: Record<number, Record<string, string[]>>,
 | 
			
		||||
    alignmentDir: ArchitectureAlignment
 | 
			
		||||
  ): Record<string, string[]> => {
 | 
			
		||||
    return Object.entries(alignmentObj).reduce(
 | 
			
		||||
      (prev, [dir, alignments]) => {
 | 
			
		||||
        // prev is the mapping of x/y coordinate to an array of the nodes in that row/column
 | 
			
		||||
        let cnt = 0;
 | 
			
		||||
        const arr = Object.entries(alignments); // [group name, array of nodes within the group on axis dir]
 | 
			
		||||
        if (arr.length === 1) {
 | 
			
		||||
          // If only one group exists in the row/column, we don't need to do anything else
 | 
			
		||||
          prev[dir] = arr[0][1];
 | 
			
		||||
          return prev;
 | 
			
		||||
        }
 | 
			
		||||
        for (let i = 0; i < arr.length - 1; i++) {
 | 
			
		||||
          for (let j = i + 1; j < arr.length; j++) {
 | 
			
		||||
            const [aGroupId, aNodeIds] = arr[i];
 | 
			
		||||
            const [bGroupId, bNodeIds] = arr[j];
 | 
			
		||||
            const alignment = groupAlignments[aGroupId]?.[bGroupId]; // Get how the two groups are intended to align (undefined if they aren't)
 | 
			
		||||
 | 
			
		||||
            if (alignment === alignmentDir) {
 | 
			
		||||
              // If the intended alignment between the two groups is the same as the alignment we are parsing
 | 
			
		||||
              prev[dir] ??= [];
 | 
			
		||||
              prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds]; // add the node ids of both groups to the axis array in prev
 | 
			
		||||
            } else if (aGroupId === 'default' || bGroupId === 'default') {
 | 
			
		||||
              // If either of the groups are in the default space (not in a group), use the same behavior as above
 | 
			
		||||
              prev[dir] ??= [];
 | 
			
		||||
              prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds];
 | 
			
		||||
            } else {
 | 
			
		||||
              // Otherwise, the nodes in the two groups are not intended to align
 | 
			
		||||
              const keyA = `${dir}-${cnt++}`;
 | 
			
		||||
              prev[keyA] = aNodeIds;
 | 
			
		||||
              const keyB = `${dir}-${cnt++}`;
 | 
			
		||||
              prev[keyB] = bNodeIds;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return prev;
 | 
			
		||||
      },
 | 
			
		||||
      {} as Record<string, string[]>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const alignments = spatialMaps.map((spatialMap) => {
 | 
			
		||||
    const horizontalAlignments: Record<number, string[]> = {};
 | 
			
		||||
    const verticalAlignments: Record<number, string[]> = {};
 | 
			
		||||
    const horizontalAlignments: Record<number, Record<string, string[]>> = {};
 | 
			
		||||
    const verticalAlignments: Record<number, Record<string, string[]>> = {};
 | 
			
		||||
 | 
			
		||||
    // Group service ids in an object with their x and y coordinate as the key
 | 
			
		||||
    Object.entries(spatialMap).forEach(([id, [x, y]]) => {
 | 
			
		||||
      if (!horizontalAlignments[y]) {
 | 
			
		||||
        horizontalAlignments[y] = [];
 | 
			
		||||
      }
 | 
			
		||||
      if (!verticalAlignments[x]) {
 | 
			
		||||
        verticalAlignments[x] = [];
 | 
			
		||||
      }
 | 
			
		||||
      horizontalAlignments[y].push(id);
 | 
			
		||||
      verticalAlignments[x].push(id);
 | 
			
		||||
      const nodeGroup = db.getNode(id)?.in ?? 'default';
 | 
			
		||||
 | 
			
		||||
      horizontalAlignments[y] ??= {};
 | 
			
		||||
      horizontalAlignments[y][nodeGroup] ??= [];
 | 
			
		||||
      horizontalAlignments[y][nodeGroup].push(id);
 | 
			
		||||
 | 
			
		||||
      verticalAlignments[x] ??= {};
 | 
			
		||||
      verticalAlignments[x][nodeGroup] ??= [];
 | 
			
		||||
      verticalAlignments[x][nodeGroup].push(id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Merge the values of each object into a list if the inner list has at least 2 elements
 | 
			
		||||
    return {
 | 
			
		||||
      horiz: Object.values(horizontalAlignments).filter((arr) => arr.length > 1),
 | 
			
		||||
      vert: Object.values(verticalAlignments).filter((arr) => arr.length > 1),
 | 
			
		||||
      horiz: Object.values(flattenAlignments(horizontalAlignments, 'horizontal')).filter(
 | 
			
		||||
        (arr) => arr.length > 1
 | 
			
		||||
      ),
 | 
			
		||||
      vert: Object.values(flattenAlignments(verticalAlignments, 'vertical')).filter(
 | 
			
		||||
        (arr) => arr.length > 1
 | 
			
		||||
      ),
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@@ -244,7 +312,8 @@ function layoutArchitecture(
 | 
			
		||||
  junctions: ArchitectureJunction[],
 | 
			
		||||
  groups: ArchitectureGroup[],
 | 
			
		||||
  edges: ArchitectureEdge[],
 | 
			
		||||
  { spatialMaps }: ArchitectureDataStructures
 | 
			
		||||
  db: ArchitectureDB,
 | 
			
		||||
  { spatialMaps, groupAlignments }: ArchitectureDataStructures
 | 
			
		||||
): Promise<cytoscape.Core> {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
 | 
			
		||||
@@ -318,9 +387,8 @@ function layoutArchitecture(
 | 
			
		||||
    addServices(services, cy);
 | 
			
		||||
    addJunctions(junctions, cy);
 | 
			
		||||
    addEdges(edges, cy);
 | 
			
		||||
 | 
			
		||||
    // Use the spatial map to create alignment arrays for fcose
 | 
			
		||||
    const alignmentConstraint = getAlignments(spatialMaps);
 | 
			
		||||
    const alignmentConstraint = getAlignments(db, spatialMaps, groupAlignments);
 | 
			
		||||
 | 
			
		||||
    // Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
 | 
			
		||||
    const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
 | 
			
		||||
@@ -454,7 +522,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
 | 
			
		||||
  await drawServices(db, servicesElem, services);
 | 
			
		||||
  drawJunctions(db, servicesElem, junctions);
 | 
			
		||||
 | 
			
		||||
  const cy = await layoutArchitecture(services, junctions, groups, edges, ds);
 | 
			
		||||
  const cy = await layoutArchitecture(services, junctions, groups, edges, db, ds);
 | 
			
		||||
 | 
			
		||||
  await drawEdges(edgesElem, cy);
 | 
			
		||||
  await drawGroups(groupElem, cy);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ import type cytoscape from 'cytoscape';
 | 
			
		||||
|       Architecture Diagram Types        |
 | 
			
		||||
\*=======================================*/
 | 
			
		||||
 | 
			
		||||
export type ArchitectureAlignment = 'vertical' | 'horizontal' | 'bend';
 | 
			
		||||
 | 
			
		||||
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
 | 
			
		||||
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
 | 
			
		||||
export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>;
 | 
			
		||||
@@ -170,6 +172,18 @@ export const getArchitectureDirectionXYFactors = function (
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getArchitectureDirectionAlignment = function (
 | 
			
		||||
  a: ArchitectureDirection,
 | 
			
		||||
  b: ArchitectureDirection
 | 
			
		||||
): ArchitectureAlignment {
 | 
			
		||||
  if (isArchitectureDirectionXY(a, b)) {
 | 
			
		||||
    return 'bend';
 | 
			
		||||
  } else if (isArchitectureDirectionX(a)) {
 | 
			
		||||
    return 'horizontal';
 | 
			
		||||
  }
 | 
			
		||||
  return 'vertical';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface ArchitectureStyleOptions {
 | 
			
		||||
  archEdgeColor: string;
 | 
			
		||||
  archEdgeArrowColor: string;
 | 
			
		||||
@@ -249,9 +263,27 @@ export interface ArchitectureDB extends DiagramDB {
 | 
			
		||||
 | 
			
		||||
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
 | 
			
		||||
export type ArchitectureSpatialMap = Record<string, number[]>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Maps the direction that groups connect from.
 | 
			
		||||
 *
 | 
			
		||||
 * **Outer key**: ID of group A
 | 
			
		||||
 *
 | 
			
		||||
 * **Inner key**: ID of group B
 | 
			
		||||
 *
 | 
			
		||||
 * **Value**: 'vertical' or 'horizontal'
 | 
			
		||||
 *
 | 
			
		||||
 * Note: tmp[groupA][groupB] == tmp[groupB][groupA]
 | 
			
		||||
 */
 | 
			
		||||
export type ArchitectureGroupAlignments = Record<
 | 
			
		||||
  string,
 | 
			
		||||
  Record<string, Exclude<ArchitectureAlignment, 'bend'>>
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export interface ArchitectureDataStructures {
 | 
			
		||||
  adjList: ArchitectureAdjacencyList;
 | 
			
		||||
  spatialMaps: ArchitectureSpatialMap[];
 | 
			
		||||
  groupAlignments: ArchitectureGroupAlignments;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ArchitectureState extends Record<string, unknown> {
 | 
			
		||||
 
 | 
			
		||||
@@ -40,30 +40,18 @@ let functions: any[] = [];
 | 
			
		||||
 | 
			
		||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
 | 
			
		||||
 | 
			
		||||
const splitClassNameAndType = function (_id: string) {
 | 
			
		||||
  const id = common.sanitizeText(_id, getConfig());
 | 
			
		||||
const splitClassIdAndType = function (_id: string) {
 | 
			
		||||
  const id = sanitizeText(_id);
 | 
			
		||||
  let genericType = '';
 | 
			
		||||
  let className = id;
 | 
			
		||||
  let classId = id;
 | 
			
		||||
 | 
			
		||||
  if (id.indexOf('~') > 0) {
 | 
			
		||||
    const split = id.split('~');
 | 
			
		||||
    className = sanitizeText(split[0]);
 | 
			
		||||
    classId = sanitizeText(split[0]);
 | 
			
		||||
    genericType = sanitizeText(split[1]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { className: className, type: genericType };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const setClassLabel = function (_id: string, label: string) {
 | 
			
		||||
  const id = common.sanitizeText(_id, getConfig());
 | 
			
		||||
  if (label) {
 | 
			
		||||
    label = sanitizeText(label);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { className } = splitClassNameAndType(id);
 | 
			
		||||
  classes.get(className)!.label = label;
 | 
			
		||||
  classes.get(className)!.text =
 | 
			
		||||
    `${label}${classes.get(className)!.type ? `<${classes.get(className)!.type}>` : ''}`;
 | 
			
		||||
  return { classId, type: genericType };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -72,28 +60,33 @@ export const setClassLabel = function (_id: string, label: string) {
 | 
			
		||||
 * @param id - Id of the class to add
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const addClass = function (_id: string) {
 | 
			
		||||
  const id = common.sanitizeText(_id, getConfig());
 | 
			
		||||
  const { className, type } = splitClassNameAndType(id);
 | 
			
		||||
  // Only add class if not exists
 | 
			
		||||
  if (classes.has(className)) {
 | 
			
		||||
export const addClass = function (_id: string, label?: string) {
 | 
			
		||||
  const id = sanitizeText(_id);
 | 
			
		||||
  const { classId, type } = splitClassIdAndType(id);
 | 
			
		||||
  let newLabel = classId;
 | 
			
		||||
 | 
			
		||||
  if (classes.has(classId)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // alert('Adding class: ' + className);
 | 
			
		||||
  const name = common.sanitizeText(className, getConfig());
 | 
			
		||||
  // alert('Adding class after: ' + name);
 | 
			
		||||
  classes.set(name, {
 | 
			
		||||
    id: name,
 | 
			
		||||
 | 
			
		||||
  if (label) {
 | 
			
		||||
    newLabel = sanitizeText(label);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const text = `${newLabel}${type ? `<${type}>` : ''}`;
 | 
			
		||||
 | 
			
		||||
  classes.set(classId, {
 | 
			
		||||
    id: classId,
 | 
			
		||||
    type: type,
 | 
			
		||||
    label: name,
 | 
			
		||||
    text: `${name}${type ? `<${type}>` : ''}`,
 | 
			
		||||
    label: newLabel,
 | 
			
		||||
    text: text,
 | 
			
		||||
    shape: 'classBox',
 | 
			
		||||
    cssClasses: 'default',
 | 
			
		||||
    methods: [],
 | 
			
		||||
    members: [],
 | 
			
		||||
    attributes: [],
 | 
			
		||||
    annotations: [],
 | 
			
		||||
    styles: [],
 | 
			
		||||
    domId: MERMAID_DOM_ID_PREFIX + name + '-' + classCounter,
 | 
			
		||||
    domId: `${MERMAID_DOM_ID_PREFIX}${classId}-${classCounter}`,
 | 
			
		||||
  } as ClassNode);
 | 
			
		||||
 | 
			
		||||
  classCounter++;
 | 
			
		||||
@@ -116,7 +109,7 @@ const addInterface = function (label: string, classId: string) {
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const lookUpDomId = function (_id: string): string {
 | 
			
		||||
  const id = common.sanitizeText(_id, getConfig());
 | 
			
		||||
  const id = sanitizeText(_id);
 | 
			
		||||
  if (classes.has(id)) {
 | 
			
		||||
    return classes.get(id)!.domId;
 | 
			
		||||
  }
 | 
			
		||||
@@ -182,18 +175,12 @@ export const addRelation = function (classRelation: ClassRelation) {
 | 
			
		||||
    addClass(classRelation.id2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  classRelation.id1 = splitClassNameAndType(classRelation.id1).className;
 | 
			
		||||
  classRelation.id2 = splitClassNameAndType(classRelation.id2).className;
 | 
			
		||||
  classRelation.id1 = splitClassIdAndType(classRelation.id1).classId;
 | 
			
		||||
  classRelation.id2 = splitClassIdAndType(classRelation.id2).classId;
 | 
			
		||||
 | 
			
		||||
  classRelation.relationTitle1 = common.sanitizeText(
 | 
			
		||||
    classRelation.relationTitle1.trim(),
 | 
			
		||||
    getConfig()
 | 
			
		||||
  );
 | 
			
		||||
  classRelation.relationTitle1 = sanitizeText(classRelation.relationTitle1.trim());
 | 
			
		||||
 | 
			
		||||
  classRelation.relationTitle2 = common.sanitizeText(
 | 
			
		||||
    classRelation.relationTitle2.trim(),
 | 
			
		||||
    getConfig()
 | 
			
		||||
  );
 | 
			
		||||
  classRelation.relationTitle2 = sanitizeText(classRelation.relationTitle2.trim());
 | 
			
		||||
 | 
			
		||||
  relations.push(classRelation);
 | 
			
		||||
};
 | 
			
		||||
@@ -202,29 +189,29 @@ export const addRelation = function (classRelation: ClassRelation) {
 | 
			
		||||
 * Adds an annotation to the specified class Annotations mark special properties of the given type
 | 
			
		||||
 * (like 'interface' or 'service')
 | 
			
		||||
 *
 | 
			
		||||
 * @param className - The class name
 | 
			
		||||
 * @param classId - The class name
 | 
			
		||||
 * @param annotation - The name of the annotation without any brackets
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const addAnnotation = function (className: string, annotation: string) {
 | 
			
		||||
  const validatedClassName = splitClassNameAndType(className).className;
 | 
			
		||||
  const validatedClassName = splitClassIdAndType(className).classId;
 | 
			
		||||
  classes.get(validatedClassName)!.annotations.push(annotation);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds a member to the specified class
 | 
			
		||||
 *
 | 
			
		||||
 * @param className - The class name
 | 
			
		||||
 * @param classId - The class name
 | 
			
		||||
 * @param member - The full name of the member. If the member is enclosed in `<<brackets>>` it is
 | 
			
		||||
 *   treated as an annotation If the member is ending with a closing bracket ) it is treated as a
 | 
			
		||||
 *   method Otherwise the member will be treated as a normal property
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const addMember = function (className: string, member: string) {
 | 
			
		||||
  addClass(className);
 | 
			
		||||
export const addMember = function (classId: string, member: string) {
 | 
			
		||||
  addClass(classId);
 | 
			
		||||
 | 
			
		||||
  const validatedClassName = splitClassNameAndType(className).className;
 | 
			
		||||
  const theClass = classes.get(validatedClassName)!;
 | 
			
		||||
  const validatedClassId = splitClassIdAndType(classId).classId;
 | 
			
		||||
  const theClass = classes.get(validatedClassId)!;
 | 
			
		||||
 | 
			
		||||
  if (typeof member === 'string') {
 | 
			
		||||
    // Member can contain white spaces, we trim them out
 | 
			
		||||
@@ -237,7 +224,7 @@ export const addMember = function (className: string, member: string) {
 | 
			
		||||
      //its a method
 | 
			
		||||
      theClass.methods.push(new ClassMember(memberString, 'method'));
 | 
			
		||||
    } else if (memberString) {
 | 
			
		||||
      theClass.members.push(new ClassMember(memberString, 'attribute'));
 | 
			
		||||
      theClass.attributes.push(new ClassMember(memberString, 'attribute'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -377,7 +364,7 @@ export const setClickEvent = function (ids: string, functionName: string, functi
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) {
 | 
			
		||||
  const domId = common.sanitizeText(_domId, getConfig());
 | 
			
		||||
  const domId = sanitizeText(_domId);
 | 
			
		||||
  const config = getConfig();
 | 
			
		||||
  if (config.securityLevel !== 'loose') {
 | 
			
		||||
    return;
 | 
			
		||||
@@ -520,17 +507,15 @@ const getNamespaces = function (): NamespaceMap {
 | 
			
		||||
 * Function called by parser when a namespace definition has been found.
 | 
			
		||||
 *
 | 
			
		||||
 * @param id - Id of the namespace to add
 | 
			
		||||
 * @param classNames - Ids of the class to add
 | 
			
		||||
 * @param classIds - Ids of the class to add
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
 | 
			
		||||
  if (!namespaces.has(id)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  for (const name of classNames) {
 | 
			
		||||
    const { className } = splitClassNameAndType(name);
 | 
			
		||||
    classes.get(className)!.parent = id;
 | 
			
		||||
    namespaces.get(id)!.classes.set(className, classes.get(className)!);
 | 
			
		||||
export const addClassesToNamespace = function (_id: string, classIds: string[]) {
 | 
			
		||||
  addNamespace(_id);
 | 
			
		||||
  for (const id of classIds) {
 | 
			
		||||
    const { classId } = splitClassIdAndType(id);
 | 
			
		||||
    classes.get(classId)!.parent = _id;
 | 
			
		||||
    namespaces.get(_id)!.classes.set(classId, classes.get(classId)!);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -726,7 +711,6 @@ export default {
 | 
			
		||||
  lookUpDomId,
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
  getDiagramTitle,
 | 
			
		||||
  setClassLabel,
 | 
			
		||||
  addNamespace,
 | 
			
		||||
  addClassesToNamespace,
 | 
			
		||||
  getNamespace,
 | 
			
		||||
 
 | 
			
		||||
@@ -212,29 +212,29 @@ describe('given a basic class diagram, ', function () {
 | 
			
		||||
      expect(c2.label).toBe('Class 2 with chars @?');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with a text label and member', () => {
 | 
			
		||||
      const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: member1';
 | 
			
		||||
    it('should parse a class with a text label and attribute', () => {
 | 
			
		||||
      const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: attribute1';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members.length).toBe(1);
 | 
			
		||||
      expect(c1.members[0].getDisplayDetails().displayText).toBe('member1');
 | 
			
		||||
      expect(c1.attributes.length).toBe(1);
 | 
			
		||||
      expect(c1.attributes[0].getDisplayDetails().displayText).toBe('attribute1');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with a text label, member and annotation', () => {
 | 
			
		||||
    it('should parse a class with a text label, attribute and annotation', () => {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class C1["Class 1 with text label"]\n' +
 | 
			
		||||
        '<<interface>> C1\n' +
 | 
			
		||||
        'C1 : int member1';
 | 
			
		||||
        'C1 : int attribute1';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members.length).toBe(1);
 | 
			
		||||
      expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
 | 
			
		||||
      expect(c1.attributes.length).toBe(1);
 | 
			
		||||
      expect(c1.attributes[0].getDisplayDetails().displayText).toBe('int attribute1');
 | 
			
		||||
      expect(c1.annotations.length).toBe(1);
 | 
			
		||||
      expect(c1.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
@@ -253,14 +253,14 @@ describe('given a basic class diagram, ', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class C1["Class 1 with text label"]\n' +
 | 
			
		||||
        'C1 : int member1\n' +
 | 
			
		||||
        'C1 : int attribute1\n' +
 | 
			
		||||
        'cssClass "C1" styleClass';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
 | 
			
		||||
      expect(c1.attributes[0].getDisplayDetails().displayText).toBe('int attribute1');
 | 
			
		||||
      expect(c1.cssClasses).toBe('default styleClass');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -268,7 +268,7 @@ describe('given a basic class diagram, ', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class C1["Class 1 with text label"]\n' +
 | 
			
		||||
        'C1 : int member1\n' +
 | 
			
		||||
        'C1 : int attribute1\n' +
 | 
			
		||||
        'class C2["Long long long long long long long long long long label"]\n' +
 | 
			
		||||
        'cssClass "C1,C2" styleClass';
 | 
			
		||||
 | 
			
		||||
@@ -486,7 +486,7 @@ class C13["With Città foreign language"]
 | 
			
		||||
      expect(studentClass).toMatchObject({
 | 
			
		||||
        id: 'Student',
 | 
			
		||||
        label: 'Student',
 | 
			
		||||
        members: [
 | 
			
		||||
        attributes: [
 | 
			
		||||
          expect.objectContaining({
 | 
			
		||||
            id: 'idCard : IdCard',
 | 
			
		||||
            visibility: '-',
 | 
			
		||||
@@ -500,11 +500,7 @@ class C13["With Città foreign language"]
 | 
			
		||||
      expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
 | 
			
		||||
        {
 | 
			
		||||
          "annotations": [],
 | 
			
		||||
          "cssClasses": "default",
 | 
			
		||||
          "domId": "classId-Student-141",
 | 
			
		||||
          "id": "Student",
 | 
			
		||||
          "label": "Student",
 | 
			
		||||
          "members": [
 | 
			
		||||
          "attributes": [
 | 
			
		||||
            ClassMember {
 | 
			
		||||
              "classifier": "",
 | 
			
		||||
              "id": "idCard : IdCard",
 | 
			
		||||
@@ -513,6 +509,10 @@ class C13["With Città foreign language"]
 | 
			
		||||
              "visibility": "-",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          "cssClasses": "default",
 | 
			
		||||
          "domId": "classId-Student-141",
 | 
			
		||||
          "id": "Student",
 | 
			
		||||
          "label": "Student",
 | 
			
		||||
          "methods": [],
 | 
			
		||||
          "shape": "classBox",
 | 
			
		||||
          "styles": [],
 | 
			
		||||
@@ -569,7 +569,7 @@ class C13["With Città foreign language"]
 | 
			
		||||
      parser.yy = classDb;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle member definitions', function () {
 | 
			
		||||
    it('should handle attribute definitions', function () {
 | 
			
		||||
      const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
@@ -588,7 +588,7 @@ class C13["With Città foreign language"]
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle member and method definitions', () => {
 | 
			
		||||
    it('should handle attribute and method definitions', () => {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' + 'class Dummy_Class {\n' + 'String data\n' + 'void methods()\n' + '}';
 | 
			
		||||
 | 
			
		||||
@@ -611,45 +611,46 @@ class C13["With Città foreign language"]
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class Class1 {\n' +
 | 
			
		||||
        'int testMember\n' +
 | 
			
		||||
        'int testAttribute\n' +
 | 
			
		||||
        'test()\n' +
 | 
			
		||||
        'string fooMember\n' +
 | 
			
		||||
        'string fooAttribute\n' +
 | 
			
		||||
        'foo()\n' +
 | 
			
		||||
        '}';
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.members.length).toBe(2);
 | 
			
		||||
      expect(actual.attributes.length).toBe(2);
 | 
			
		||||
      expect(actual.methods.length).toBe(2);
 | 
			
		||||
      expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember');
 | 
			
		||||
      expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember');
 | 
			
		||||
      expect(actual.attributes[0].getDisplayDetails().displayText).toBe('int testAttribute');
 | 
			
		||||
      expect(actual.attributes[1].getDisplayDetails().displayText).toBe('string fooAttribute');
 | 
			
		||||
      expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()');
 | 
			
		||||
      expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with a text label and members', () => {
 | 
			
		||||
      const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+member1\n' + '}';
 | 
			
		||||
    it('should parse a class with a text label and attribute', () => {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+attributes1\n' + '}';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members.length).toBe(1);
 | 
			
		||||
      expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
 | 
			
		||||
      expect(c1.attributes.length).toBe(1);
 | 
			
		||||
      expect(c1.attributes[0].getDisplayDetails().displayText).toBe('+attributes1');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with a text label, members and annotation', () => {
 | 
			
		||||
    it('should parse a class with a text label, attribute and annotation', () => {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class C1["Class 1 with text label"] {\n' +
 | 
			
		||||
        '<<interface>>\n' +
 | 
			
		||||
        '+member1\n' +
 | 
			
		||||
        '+attribute1\n' +
 | 
			
		||||
        '}';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members.length).toBe(1);
 | 
			
		||||
      expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
 | 
			
		||||
      expect(c1.attributes.length).toBe(1);
 | 
			
		||||
      expect(c1.attributes[0].getDisplayDetails().displayText).toBe('+attribute1');
 | 
			
		||||
      expect(c1.annotations.length).toBe(1);
 | 
			
		||||
      expect(c1.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
@@ -868,12 +869,12 @@ foo()
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.annotations.length).toBe(1);
 | 
			
		||||
      expect(actual.members.length).toBe(0);
 | 
			
		||||
      expect(actual.methods.length).toBe(0);
 | 
			
		||||
      expect(actual.attributes.length).toBe(0);
 | 
			
		||||
      expect(actual.attributes.length).toBe(0);
 | 
			
		||||
      expect(actual.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle class annotations with members and methods', function () {
 | 
			
		||||
    it('should handle class annotations with attributes and methods', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class Class1\n' +
 | 
			
		||||
@@ -884,7 +885,7 @@ foo()
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.annotations.length).toBe(1);
 | 
			
		||||
      expect(actual.members.length).toBe(1);
 | 
			
		||||
      expect(actual.attributes.length).toBe(1);
 | 
			
		||||
      expect(actual.methods.length).toBe(1);
 | 
			
		||||
      expect(actual.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
@@ -895,12 +896,12 @@ foo()
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.annotations.length).toBe(1);
 | 
			
		||||
      expect(actual.members.length).toBe(0);
 | 
			
		||||
      expect(actual.attributes.length).toBe(0);
 | 
			
		||||
      expect(actual.methods.length).toBe(0);
 | 
			
		||||
      expect(actual.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle class annotations in brackets with members and methods', function () {
 | 
			
		||||
    it('should handle class annotations in brackets with attributes and methods', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class Class1 {\n' +
 | 
			
		||||
@@ -912,41 +913,41 @@ foo()
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.annotations.length).toBe(1);
 | 
			
		||||
      expect(actual.members.length).toBe(1);
 | 
			
		||||
      expect(actual.attributes.length).toBe(1);
 | 
			
		||||
      expect(actual.methods.length).toBe(1);
 | 
			
		||||
      expect(actual.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('given a class diagram with members and methods ', function () {
 | 
			
		||||
  describe('when parsing members', function () {
 | 
			
		||||
describe('given a class diagram with attributes and methods ', function () {
 | 
			
		||||
  describe('when parsing attributes', function () {
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
      classDb.clear();
 | 
			
		||||
      parser.yy = classDb;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle simple member declaration', function () {
 | 
			
		||||
    it('should handle simple attribute declaration', function () {
 | 
			
		||||
      const str = 'classDiagram\n' + 'class Car\n' + 'Car : wheels';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle direct member declaration', function () {
 | 
			
		||||
    it('should handle direct attribute declaration', function () {
 | 
			
		||||
      parser.parse('classDiagram\n' + 'Car : wheels');
 | 
			
		||||
      const car = classDb.getClass('Car');
 | 
			
		||||
      expect(car.members.length).toBe(1);
 | 
			
		||||
      expect(car.members[0].id).toBe('wheels');
 | 
			
		||||
      expect(car.attributes.length).toBe(1);
 | 
			
		||||
      expect(car.attributes[0].id).toBe('wheels');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle direct member declaration with type', function () {
 | 
			
		||||
    it('should handle direct attribute declaration with type', function () {
 | 
			
		||||
      parser.parse('classDiagram\n' + 'Car : int wheels');
 | 
			
		||||
      const car = classDb.getClass('Car');
 | 
			
		||||
      expect(car.members.length).toBe(1);
 | 
			
		||||
      expect(car.members[0].id).toBe('int wheels');
 | 
			
		||||
      expect(car.attributes.length).toBe(1);
 | 
			
		||||
      expect(car.attributes[0].id).toBe('int wheels');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle simple member declaration with type', function () {
 | 
			
		||||
    it('should handle simple attribute declaration with type', function () {
 | 
			
		||||
      const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
@@ -956,20 +957,20 @@ describe('given a class diagram with members and methods ', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class actual\n' +
 | 
			
		||||
        'actual : -int privateMember\n' +
 | 
			
		||||
        'actual : +int publicMember\n' +
 | 
			
		||||
        'actual : #int protectedMember\n' +
 | 
			
		||||
        'actual : -int privateAttribute\n' +
 | 
			
		||||
        'actual : +int publicAttribute\n' +
 | 
			
		||||
        'actual : #int protectedAttribute\n' +
 | 
			
		||||
        'actual : ~int privatePackage';
 | 
			
		||||
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('actual');
 | 
			
		||||
      expect(actual.members.length).toBe(4);
 | 
			
		||||
      expect(actual.attributes.length).toBe(4);
 | 
			
		||||
      expect(actual.methods.length).toBe(0);
 | 
			
		||||
      expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember');
 | 
			
		||||
      expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember');
 | 
			
		||||
      expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember');
 | 
			
		||||
      expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage');
 | 
			
		||||
      expect(actual.attributes[0].getDisplayDetails().displayText).toBe('-int privateAttribute');
 | 
			
		||||
      expect(actual.attributes[1].getDisplayDetails().displayText).toBe('+int publicAttribute');
 | 
			
		||||
      expect(actual.attributes[2].getDisplayDetails().displayText).toBe('#int protectedAttribute');
 | 
			
		||||
      expect(actual.attributes[3].getDisplayDetails().displayText).toBe('~int privatePackage');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle generic types', function () {
 | 
			
		||||
@@ -1020,7 +1021,7 @@ describe('given a class diagram with members and methods ', function () {
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.annotations.length).toBe(0);
 | 
			
		||||
      expect(actual.members.length).toBe(0);
 | 
			
		||||
      expect(actual.attributes.length).toBe(0);
 | 
			
		||||
      expect(actual.methods.length).toBe(1);
 | 
			
		||||
      const method = actual.methods[0];
 | 
			
		||||
      expect(method.getDisplayDetails().displayText).toBe('someMethod()');
 | 
			
		||||
@@ -1033,7 +1034,7 @@ describe('given a class diagram with members and methods ', function () {
 | 
			
		||||
 | 
			
		||||
      const actual = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(actual.annotations.length).toBe(0);
 | 
			
		||||
      expect(actual.members.length).toBe(0);
 | 
			
		||||
      expect(actual.attributes.length).toBe(0);
 | 
			
		||||
      expect(actual.methods.length).toBe(1);
 | 
			
		||||
      const method = actual.methods[0];
 | 
			
		||||
      expect(method.getDisplayDetails().displayText).toBe('someMethod()');
 | 
			
		||||
@@ -1051,7 +1052,7 @@ describe('given a class diagram with members and methods ', function () {
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle generic types in members in class with brackets', function () {
 | 
			
		||||
    it('should handle generic types in attributes in class with brackets', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class Car {\n' +
 | 
			
		||||
@@ -1385,12 +1386,12 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.annotations.length).toBe(1);
 | 
			
		||||
      expect(testClass.members.length).toBe(0);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(0);
 | 
			
		||||
      expect(testClass.methods.length).toBe(0);
 | 
			
		||||
      expect(testClass.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle class annotations with members and methods', function () {
 | 
			
		||||
    it('should handle class annotations with attributes and methods', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class Class1\n' +
 | 
			
		||||
@@ -1401,7 +1402,7 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.annotations.length).toBe(1);
 | 
			
		||||
      expect(testClass.members.length).toBe(1);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(1);
 | 
			
		||||
      expect(testClass.methods.length).toBe(1);
 | 
			
		||||
      expect(testClass.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
@@ -1412,12 +1413,12 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.annotations.length).toBe(1);
 | 
			
		||||
      expect(testClass.members.length).toBe(0);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(0);
 | 
			
		||||
      expect(testClass.methods.length).toBe(0);
 | 
			
		||||
      expect(testClass.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle class annotations in brackets with members and methods', function () {
 | 
			
		||||
    it('should handle class annotations in brackets with attributes and methods', function () {
 | 
			
		||||
      const str =
 | 
			
		||||
        'classDiagram\n' +
 | 
			
		||||
        'class Class1 {\n' +
 | 
			
		||||
@@ -1429,7 +1430,7 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.annotations.length).toBe(1);
 | 
			
		||||
      expect(testClass.members.length).toBe(1);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(1);
 | 
			
		||||
      expect(testClass.methods.length).toBe(1);
 | 
			
		||||
      expect(testClass.annotations[0]).toBe('interface');
 | 
			
		||||
    });
 | 
			
		||||
@@ -1446,10 +1447,10 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
      parser.parse(str);
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.members.length).toBe(2);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(2);
 | 
			
		||||
      expect(testClass.methods.length).toBe(2);
 | 
			
		||||
      expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test');
 | 
			
		||||
      expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo');
 | 
			
		||||
      expect(testClass.attributes[0].getDisplayDetails().displayText).toBe('int : test');
 | 
			
		||||
      expect(testClass.attributes[1].getDisplayDetails().displayText).toBe('string : foo');
 | 
			
		||||
      expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()');
 | 
			
		||||
      expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()');
 | 
			
		||||
    });
 | 
			
		||||
@@ -1460,7 +1461,7 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.annotations.length).toBe(0);
 | 
			
		||||
      expect(testClass.members.length).toBe(0);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(0);
 | 
			
		||||
      expect(testClass.methods.length).toBe(1);
 | 
			
		||||
      const method = testClass.methods[0];
 | 
			
		||||
      expect(method.getDisplayDetails().displayText).toBe('someMethod()');
 | 
			
		||||
@@ -1473,7 +1474,7 @@ describe('given a class diagram with relationships, ', function () {
 | 
			
		||||
 | 
			
		||||
      const testClass = parser.yy.getClass('Class1');
 | 
			
		||||
      expect(testClass.annotations.length).toBe(0);
 | 
			
		||||
      expect(testClass.members.length).toBe(0);
 | 
			
		||||
      expect(testClass.attributes.length).toBe(0);
 | 
			
		||||
      expect(testClass.methods.length).toBe(1);
 | 
			
		||||
      const method = testClass.methods[0];
 | 
			
		||||
      expect(method.getDisplayDetails().displayText).toBe('someMethod()');
 | 
			
		||||
@@ -1688,17 +1689,17 @@ class Class2
 | 
			
		||||
      const testClasses = parser.yy.getClasses();
 | 
			
		||||
      const testRelations = parser.yy.getRelations();
 | 
			
		||||
      expect(testNamespaceA.classes.size).toBe(2);
 | 
			
		||||
      expect(testNamespaceA.classes.get('A1').members[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
      expect(testNamespaceA.classes.get('A1').attributes[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
        '+foo : string'
 | 
			
		||||
      );
 | 
			
		||||
      expect(testNamespaceA.classes.get('A2').members[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
      expect(testNamespaceA.classes.get('A2').attributes[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
        '+bar : int'
 | 
			
		||||
      );
 | 
			
		||||
      expect(testNamespaceB.classes.size).toBe(2);
 | 
			
		||||
      expect(testNamespaceB.classes.get('B1').members[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
      expect(testNamespaceB.classes.get('B1').attributes[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
        '+foo : bool'
 | 
			
		||||
      );
 | 
			
		||||
      expect(testNamespaceB.classes.get('B2').members[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
      expect(testNamespaceB.classes.get('B2').attributes[0].getDisplayDetails().displayText).toBe(
 | 
			
		||||
        '+bar : float'
 | 
			
		||||
      );
 | 
			
		||||
      expect(testClasses.size).toBe(4);
 | 
			
		||||
@@ -1742,37 +1743,37 @@ class Class2
 | 
			
		||||
      expect(c2.label).toBe('Class 2 with chars @?');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with a text label and members', () => {
 | 
			
		||||
    it('should parse a class with a text label and attributes', () => {
 | 
			
		||||
      parser.parse(`classDiagram
 | 
			
		||||
  class C1["Class 1 with text label"] {
 | 
			
		||||
    +member1
 | 
			
		||||
    +attribute1
 | 
			
		||||
  }
 | 
			
		||||
  C1 -->  C2
 | 
			
		||||
      `);
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members.length).toBe(1);
 | 
			
		||||
      const member = c1.members[0];
 | 
			
		||||
      expect(member.getDisplayDetails().displayText).toBe('+member1');
 | 
			
		||||
      expect(c1.attributes.length).toBe(1);
 | 
			
		||||
      const attribute = c1.attributes[0];
 | 
			
		||||
      expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
 | 
			
		||||
      const c2 = classDb.getClass('C2');
 | 
			
		||||
      expect(c2.label).toBe('C2');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with a text label, members and annotation', () => {
 | 
			
		||||
    it('should parse a class with a text label, attributes and annotation', () => {
 | 
			
		||||
      parser.parse(`classDiagram
 | 
			
		||||
  class C1["Class 1 with text label"] {
 | 
			
		||||
    <<interface>>
 | 
			
		||||
    +member1
 | 
			
		||||
    +attribute1
 | 
			
		||||
  }
 | 
			
		||||
  C1 -->  C2
 | 
			
		||||
      `);
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.members.length).toBe(1);
 | 
			
		||||
      expect(c1.attributes.length).toBe(1);
 | 
			
		||||
      expect(c1.annotations.length).toBe(1);
 | 
			
		||||
      expect(c1.annotations[0]).toBe('interface');
 | 
			
		||||
      const member = c1.members[0];
 | 
			
		||||
      expect(member.getDisplayDetails().displayText).toBe('+member1');
 | 
			
		||||
      const attribute = c1.attributes[0];
 | 
			
		||||
      expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
 | 
			
		||||
 | 
			
		||||
      const c2 = classDb.getClass('C2');
 | 
			
		||||
      expect(c2.label).toBe('C2');
 | 
			
		||||
@@ -1781,7 +1782,7 @@ class Class2
 | 
			
		||||
    it('should parse a class with text label and css class shorthand', () => {
 | 
			
		||||
      parser.parse(`classDiagram
 | 
			
		||||
class C1["Class 1 with text label"]:::styleClass {
 | 
			
		||||
  +member1
 | 
			
		||||
  +attribute1
 | 
			
		||||
}
 | 
			
		||||
C1 -->  C2
 | 
			
		||||
  `);
 | 
			
		||||
@@ -1789,14 +1790,14 @@ C1 -->  C2
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.cssClasses).toBe('default styleClass');
 | 
			
		||||
      const member = c1.members[0];
 | 
			
		||||
      expect(member.getDisplayDetails().displayText).toBe('+member1');
 | 
			
		||||
      const attribute = c1.attributes[0];
 | 
			
		||||
      expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse a class with text label and css class', () => {
 | 
			
		||||
      parser.parse(`classDiagram
 | 
			
		||||
class C1["Class 1 with text label"] {
 | 
			
		||||
  +member1
 | 
			
		||||
  +attribute1
 | 
			
		||||
}
 | 
			
		||||
C1 --> C2
 | 
			
		||||
cssClass "C1" styleClass
 | 
			
		||||
@@ -1805,14 +1806,14 @@ cssClass "C1" styleClass
 | 
			
		||||
      const c1 = classDb.getClass('C1');
 | 
			
		||||
      expect(c1.label).toBe('Class 1 with text label');
 | 
			
		||||
      expect(c1.cssClasses).toBe('default styleClass');
 | 
			
		||||
      const member = c1.members[0];
 | 
			
		||||
      expect(member.getDisplayDetails().displayText).toBe('+member1');
 | 
			
		||||
      const attribute = c1.attributes[0];
 | 
			
		||||
      expect(attribute.getDisplayDetails().displayText).toBe('+attribute1');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should parse two classes with text labels and css classes', () => {
 | 
			
		||||
      parser.parse(`classDiagram
 | 
			
		||||
class C1["Class 1 with text label"] {
 | 
			
		||||
  +member1
 | 
			
		||||
  +attribute1
 | 
			
		||||
}
 | 
			
		||||
class C2["Long long long long long long long long long long label"]
 | 
			
		||||
C1 --> C2
 | 
			
		||||
@@ -1831,7 +1832,7 @@ cssClass "C1,C2" styleClass
 | 
			
		||||
    it('should parse two classes with text labels and css class shorthands', () => {
 | 
			
		||||
      parser.parse(`classDiagram
 | 
			
		||||
class C1["Class 1 with text label"]:::styleClass1 {
 | 
			
		||||
  +member1
 | 
			
		||||
  +attribute1
 | 
			
		||||
}
 | 
			
		||||
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
 | 
			
		||||
C1 --> C2
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@ import { parseGenericTypes, sanitizeText } from '../common/common.js';
 | 
			
		||||
 | 
			
		||||
export interface ClassNode {
 | 
			
		||||
  id: string;
 | 
			
		||||
  type: string;
 | 
			
		||||
  label: string;
 | 
			
		||||
  type?: string;
 | 
			
		||||
  label?: string;
 | 
			
		||||
  shape: string;
 | 
			
		||||
  text: string;
 | 
			
		||||
  text?: string;
 | 
			
		||||
  cssClasses: string;
 | 
			
		||||
  methods: ClassMember[];
 | 
			
		||||
  members: ClassMember[];
 | 
			
		||||
  attributes: ClassMember[];
 | 
			
		||||
  annotations: string[];
 | 
			
		||||
  domId: string;
 | 
			
		||||
  styles: string[];
 | 
			
		||||
 
 | 
			
		||||
@@ -297,7 +297,7 @@ classStatement
 | 
			
		||||
 | 
			
		||||
classIdentifier
 | 
			
		||||
    : CLASS className                                    {$$=$2; yy.addClass($2);}
 | 
			
		||||
    | CLASS className classLabel                         {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
 | 
			
		||||
    | CLASS className classLabel                         {$$=$2; yy.addClass($2, $3);}
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
annotationStatement
 | 
			
		||||
 
 | 
			
		||||
@@ -27,12 +27,12 @@ export async function textHelper<T extends SVGGraphicsElement>(
 | 
			
		||||
 | 
			
		||||
  let annotationGroup = null;
 | 
			
		||||
  let labelGroup = null;
 | 
			
		||||
  let membersGroup = null;
 | 
			
		||||
  let attributeGroup = null;
 | 
			
		||||
  let methodsGroup = null;
 | 
			
		||||
 | 
			
		||||
  let annotationGroupHeight = 0;
 | 
			
		||||
  let labelGroupHeight = 0;
 | 
			
		||||
  let membersGroupHeight = 0;
 | 
			
		||||
  let attributeGroupHeight = 0;
 | 
			
		||||
 | 
			
		||||
  annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
 | 
			
		||||
  if (node.annotations.length > 0) {
 | 
			
		||||
@@ -48,15 +48,15 @@ export async function textHelper<T extends SVGGraphicsElement>(
 | 
			
		||||
  const labelGroupBBox = labelGroup.node()!.getBBox();
 | 
			
		||||
  labelGroupHeight = labelGroupBBox.height;
 | 
			
		||||
 | 
			
		||||
  membersGroup = shapeSvg.insert('g').attr('class', 'members-group text');
 | 
			
		||||
  attributeGroup = shapeSvg.insert('g').attr('class', 'attribute-group text');
 | 
			
		||||
  let yOffset = 0;
 | 
			
		||||
  for (const member of node.members) {
 | 
			
		||||
    const height = await addText(membersGroup, member, yOffset, [member.parseClassifier()]);
 | 
			
		||||
  for (const attribute of node.attributes) {
 | 
			
		||||
    const height = await addText(attributeGroup, attribute, yOffset, [attribute.parseClassifier()]);
 | 
			
		||||
    yOffset += height + TEXT_PADDING;
 | 
			
		||||
  }
 | 
			
		||||
  membersGroupHeight = membersGroup.node()!.getBBox().height;
 | 
			
		||||
  if (membersGroupHeight <= 0) {
 | 
			
		||||
    membersGroupHeight = GAP / 2;
 | 
			
		||||
  attributeGroupHeight = attributeGroup.node()!.getBBox().height;
 | 
			
		||||
  if (attributeGroupHeight <= 0) {
 | 
			
		||||
    attributeGroupHeight = GAP / 2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  methodsGroup = shapeSvg.insert('g').attr('class', 'methods-group text');
 | 
			
		||||
@@ -79,14 +79,14 @@ export async function textHelper<T extends SVGGraphicsElement>(
 | 
			
		||||
 | 
			
		||||
  bbox = shapeSvg.node()!.getBBox();
 | 
			
		||||
 | 
			
		||||
  membersGroup.attr(
 | 
			
		||||
  attributeGroup.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)})`
 | 
			
		||||
    `translate(${0}, ${annotationGroupHeight + labelGroupHeight + (attributeGroupHeight ? attributeGroupHeight + GAP * 4 : GAP * 2)})`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  bbox = shapeSvg.node()!.getBBox();
 | 
			
		||||
@@ -107,9 +107,10 @@ async function addText<T extends SVGGraphicsElement>(
 | 
			
		||||
    '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;
 | 
			
		||||
    textContent = node.text ?? '';
 | 
			
		||||
  } else {
 | 
			
		||||
    textContent = node.label!;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -190,8 +190,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
 | 
			
		||||
  // add annotations
 | 
			
		||||
  let isFirst = true;
 | 
			
		||||
  classDef.annotations.forEach(function (member) {
 | 
			
		||||
    const titleText2 = title.append('tspan').text('«' + member + '»');
 | 
			
		||||
  classDef.annotations.forEach(function (annotation) {
 | 
			
		||||
    const titleText2 = title.append('tspan').text('«' + annotation + '»');
 | 
			
		||||
    if (!isFirst) {
 | 
			
		||||
      titleText2.attr('dy', conf.textHeight);
 | 
			
		||||
    }
 | 
			
		||||
@@ -208,19 +208,19 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const titleHeight = title.node().getBBox().height;
 | 
			
		||||
  let membersLine;
 | 
			
		||||
  let membersBox;
 | 
			
		||||
  let attributesLine;
 | 
			
		||||
  let attributesBox;
 | 
			
		||||
  let methodsLine;
 | 
			
		||||
 | 
			
		||||
  // don't draw box if no members
 | 
			
		||||
  if (classDef.members.length > 0) {
 | 
			
		||||
    membersLine = g
 | 
			
		||||
  // don't draw box if no attributes
 | 
			
		||||
  if (classDef.attributes.length > 0) {
 | 
			
		||||
    attributesLine = g
 | 
			
		||||
      .append('line') // text label for the x axis
 | 
			
		||||
      .attr('x1', 0)
 | 
			
		||||
      .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
 | 
			
		||||
      .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
 | 
			
		||||
 | 
			
		||||
    const members = g
 | 
			
		||||
    const attributes = g
 | 
			
		||||
      .append('text') // text label for the x axis
 | 
			
		||||
      .attr('x', conf.padding)
 | 
			
		||||
      .attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
 | 
			
		||||
@@ -228,12 +228,12 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
      .attr('class', 'classText');
 | 
			
		||||
 | 
			
		||||
    isFirst = true;
 | 
			
		||||
    classDef.members.forEach(function (member) {
 | 
			
		||||
      addTspan(members, member, isFirst, conf);
 | 
			
		||||
    classDef.attributes.forEach(function (attribute) {
 | 
			
		||||
      addTspan(attributes, attribute, isFirst, conf);
 | 
			
		||||
      isFirst = false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    membersBox = members.node().getBBox();
 | 
			
		||||
    attributesBox = attributes.node().getBBox();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // don't draw box if no methods
 | 
			
		||||
@@ -241,13 +241,13 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
    methodsLine = g
 | 
			
		||||
      .append('line') // text label for the x axis
 | 
			
		||||
      .attr('x1', 0)
 | 
			
		||||
      .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
 | 
			
		||||
      .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
 | 
			
		||||
      .attr('y1', conf.padding + titleHeight + conf.dividerMargin + attributesBox.height)
 | 
			
		||||
      .attr('y2', conf.padding + titleHeight + conf.dividerMargin + attributesBox.height);
 | 
			
		||||
 | 
			
		||||
    const methods = g
 | 
			
		||||
      .append('text') // text label for the x axis
 | 
			
		||||
      .attr('x', conf.padding)
 | 
			
		||||
      .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
 | 
			
		||||
      .attr('y', titleHeight + 2 * conf.dividerMargin + attributesBox.height + conf.textHeight)
 | 
			
		||||
      .attr('fill', 'white')
 | 
			
		||||
      .attr('class', 'classText');
 | 
			
		||||
 | 
			
		||||
@@ -286,8 +286,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
    title.insert('title').text(classDef.tooltip);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (membersLine) {
 | 
			
		||||
    membersLine.attr('x2', rectWidth);
 | 
			
		||||
  if (attributesLine) {
 | 
			
		||||
    attributesLine.attr('x2', rectWidth);
 | 
			
		||||
  }
 | 
			
		||||
  if (methodsLine) {
 | 
			
		||||
    methodsLine.attr('x2', rectWidth);
 | 
			
		||||
 
 | 
			
		||||
@@ -32,14 +32,14 @@ const setupDompurifyHooksIfNotSetup = (() => {
 | 
			
		||||
function setupDompurifyHooks() {
 | 
			
		||||
  const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
 | 
			
		||||
 | 
			
		||||
  DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => {
 | 
			
		||||
    if (node.tagName === 'A' && node.hasAttribute('target')) {
 | 
			
		||||
  DOMPurify.addHook('beforeSanitizeAttributes', (node) => {
 | 
			
		||||
    if (node instanceof Element && node.tagName === 'A' && node.hasAttribute('target')) {
 | 
			
		||||
      node.setAttribute(TEMPORARY_ATTRIBUTE, node.getAttribute('target') ?? '');
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {
 | 
			
		||||
    if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
 | 
			
		||||
  DOMPurify.addHook('afterSanitizeAttributes', (node) => {
 | 
			
		||||
    if (node instanceof Element && node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
 | 
			
		||||
      node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) ?? '');
 | 
			
		||||
      node.removeAttribute(TEMPORARY_ATTRIBUTE);
 | 
			
		||||
      if (node.getAttribute('target') === '_blank') {
 | 
			
		||||
@@ -83,7 +83,6 @@ export const sanitizeText = (text: string, config: MermaidConfig): string => {
 | 
			
		||||
    return text;
 | 
			
		||||
  }
 | 
			
		||||
  if (config.dompurifyConfig) {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-base-to-string
 | 
			
		||||
    text = DOMPurify.sanitize(sanitizeMore(text, config), config.dompurifyConfig).toString();
 | 
			
		||||
  } else {
 | 
			
		||||
    text = DOMPurify.sanitize(sanitizeMore(text, config), {
 | 
			
		||||
 
 | 
			
		||||
@@ -138,13 +138,13 @@ const addNode = (level: number, id: string, descr: string, type: number, shapeDa
 | 
			
		||||
      node.label = doc?.label;
 | 
			
		||||
    }
 | 
			
		||||
    if (doc?.icon) {
 | 
			
		||||
      node.icon = doc?.icon;
 | 
			
		||||
      node.icon = doc?.icon.toString();
 | 
			
		||||
    }
 | 
			
		||||
    if (doc?.assigned) {
 | 
			
		||||
      node.assigned = doc?.assigned;
 | 
			
		||||
      node.assigned = doc?.assigned.toString();
 | 
			
		||||
    }
 | 
			
		||||
    if (doc?.ticket) {
 | 
			
		||||
      node.ticket = doc?.ticket;
 | 
			
		||||
      node.ticket = doc?.ticket.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (doc?.priority) {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,19 +9,19 @@ interface Taglines {
 | 
			
		||||
const taglines: Taglines[] = [
 | 
			
		||||
  {
 | 
			
		||||
    label: 'Explore the Mermaid Whiteboard from the creators of Mermaid',
 | 
			
		||||
    url: 'https://docs.mermaidchart.com/guides/whiteboard?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=whiteboard',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/whiteboard?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=whiteboard',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: 'Use the Visual Editor in Mermaid Chart to design and build diagrams',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=visual_editor',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=visual_editor',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: 'Diagram live with teammates in Mermaid Chart',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=teams',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=teams',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: 'Replace ChatGPT Pro, Mermaid.live, and LucidChart with Mermaid Pro',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=AIbundle',
 | 
			
		||||
    url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=AIbundle',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -180,8 +180,6 @@ Communication tools and platforms
 | 
			
		||||
  - [=Diagram block](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
 | 
			
		||||
- [Standard Notes](https://standardnotes.com/)
 | 
			
		||||
  - [Mermaid Extension](https://github.com/nienow/sn-mermaid)
 | 
			
		||||
- [Sublime Text 3](https://sublimetext.com)
 | 
			
		||||
  - [Mermaid Package](https://packagecontrol.io/packages/Mermaid)
 | 
			
		||||
- [VS Code](https://code.visualstudio.com/)
 | 
			
		||||
  - [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
 | 
			
		||||
  - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,17 @@
 | 
			
		||||
# Blog
 | 
			
		||||
 | 
			
		||||
## [Mermaid 11.4 is out: New Features and Kanban Diagramming](https://www.mermaidchart.com/blog/posts/mermaid-11-4-is-out-new-features-and-kanban-diagramming)
 | 
			
		||||
 | 
			
		||||
Mermaid 11.4 brings enhanced functionality with the introduction of Kanban diagrams, allowing users to create visual workflows with status columns and task details.
 | 
			
		||||
 | 
			
		||||
October 31, 2024 · 2 mins
 | 
			
		||||
 | 
			
		||||
## [How To Build an ER Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-build-an-er-diagram-with-mermaid-chart)
 | 
			
		||||
 | 
			
		||||
An entity relationship (ER) diagram acts like a blueprint for your database. This makes ER diagrams effective tools for anyone dealing with complex databases, data modeling, and AI model training.
 | 
			
		||||
 | 
			
		||||
October 24, 2024 · 4 mins
 | 
			
		||||
 | 
			
		||||
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
 | 
			
		||||
 | 
			
		||||
24 September 2024 · 5 mins
 | 
			
		||||
 
 | 
			
		||||
@@ -390,7 +390,7 @@ mermaid.ganttConfig = {
 | 
			
		||||
  sectionFontSize: 24, // Font size for sections
 | 
			
		||||
  numberSectionStyles: 1, // The number of alternating section styles
 | 
			
		||||
  axisFormat: '%d/%m', // Date/time format of the axis
 | 
			
		||||
  tickInterval: '1 week', // Axis ticks
 | 
			
		||||
  tickInterval: '1week', // Axis ticks
 | 
			
		||||
  topAxis: true, // When this flag is set, date labels will be added to the top of the chart
 | 
			
		||||
  displayMode: 'compact', // Turns compact mode on
 | 
			
		||||
  weekday: 'sunday', // On which day a week-based interval should start
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ todo[Todo]
 | 
			
		||||
 | 
			
		||||
## Configuration Options
 | 
			
		||||
 | 
			
		||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams tacketBaseUrl. This can be set as in the the following example:
 | 
			
		||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams `ticketBaseUrl`. This can be set as in the the following example:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
---
 | 
			
		||||
 
 | 
			
		||||
@@ -455,6 +455,7 @@ const render = async function (
 | 
			
		||||
    svgCode = DOMPurify.sanitize(svgCode, {
 | 
			
		||||
      ADD_TAGS: DOMPURIFY_TAGS,
 | 
			
		||||
      ADD_ATTR: DOMPURIFY_ATTR,
 | 
			
		||||
      HTML_INTEGRATION_POINTS: { foreignobject: true },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
  // Treat node as classNode
 | 
			
		||||
  const classNode = node as unknown as ClassNode;
 | 
			
		||||
  classNode.annotations = classNode.annotations ?? [];
 | 
			
		||||
  classNode.members = classNode.members ?? [];
 | 
			
		||||
  classNode.attributes = classNode.attributes ?? [];
 | 
			
		||||
  classNode.methods = classNode.methods ?? [];
 | 
			
		||||
 | 
			
		||||
  const { shapeSvg, bbox } = await textHelper(parent, node, config, useHtmlLabels, GAP);
 | 
			
		||||
@@ -35,7 +35,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderExtraBox =
 | 
			
		||||
    classNode.members.length === 0 &&
 | 
			
		||||
    classNode.attributes.length === 0 &&
 | 
			
		||||
    classNode.methods.length === 0 &&
 | 
			
		||||
    !config.class?.hideEmptyMembersBox;
 | 
			
		||||
 | 
			
		||||
@@ -51,9 +51,9 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
 | 
			
		||||
  const w = bbox.width;
 | 
			
		||||
  let h = bbox.height;
 | 
			
		||||
  if (classNode.members.length === 0 && classNode.methods.length === 0) {
 | 
			
		||||
  if (classNode.attributes.length === 0 && classNode.methods.length === 0) {
 | 
			
		||||
    h += GAP;
 | 
			
		||||
  } else if (classNode.members.length > 0 && classNode.methods.length === 0) {
 | 
			
		||||
  } else if (classNode.attributes.length > 0 && classNode.methods.length === 0) {
 | 
			
		||||
    h += GAP * 2;
 | 
			
		||||
  }
 | 
			
		||||
  const x = -w / 2;
 | 
			
		||||
@@ -66,7 +66,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
      PADDING -
 | 
			
		||||
      (renderExtraBox
 | 
			
		||||
        ? PADDING
 | 
			
		||||
        : classNode.members.length === 0 && classNode.methods.length === 0
 | 
			
		||||
        : classNode.attributes.length === 0 && classNode.methods.length === 0
 | 
			
		||||
          ? -PADDING / 2
 | 
			
		||||
          : 0),
 | 
			
		||||
    w + 2 * PADDING,
 | 
			
		||||
@@ -74,7 +74,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
      2 * PADDING +
 | 
			
		||||
      (renderExtraBox
 | 
			
		||||
        ? PADDING * 2
 | 
			
		||||
        : classNode.members.length === 0 && classNode.methods.length === 0
 | 
			
		||||
        : classNode.attributes.length === 0 && classNode.methods.length === 0
 | 
			
		||||
          ? -PADDING
 | 
			
		||||
          : 0),
 | 
			
		||||
    options
 | 
			
		||||
@@ -107,7 +107,7 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
      PADDING -
 | 
			
		||||
      (renderExtraBox
 | 
			
		||||
        ? PADDING
 | 
			
		||||
        : classNode.members.length === 0 && classNode.methods.length === 0
 | 
			
		||||
        : classNode.attributes.length === 0 && classNode.methods.length === 0
 | 
			
		||||
          ? -PADDING / 2
 | 
			
		||||
          : 0);
 | 
			
		||||
    if (!useHtmlLabels) {
 | 
			
		||||
@@ -138,11 +138,11 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
  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 -
 | 
			
		||||
  const attributeGroupHeight =
 | 
			
		||||
    (shapeSvg.select('.attribute-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) {
 | 
			
		||||
  if (classNode.attributes.length > 0 || classNode.methods.length > 0 || renderExtraBox) {
 | 
			
		||||
    const roughLine = rc.line(
 | 
			
		||||
      rectBBox.x,
 | 
			
		||||
      annotationGroupHeight + labelGroupHeight + y + PADDING,
 | 
			
		||||
@@ -154,13 +154,13 @@ export async function classBox<T extends SVGGraphicsElement>(parent: D3Selection
 | 
			
		||||
    line.attr('class', 'divider').attr('style', styles);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Second line (under members)
 | 
			
		||||
  if (renderExtraBox || classNode.members.length > 0 || classNode.methods.length > 0) {
 | 
			
		||||
  // Second line (under attributes)
 | 
			
		||||
  if (renderExtraBox || classNode.attributes.length > 0 || classNode.methods.length > 0) {
 | 
			
		||||
    const roughLine = rc.line(
 | 
			
		||||
      rectBBox.x,
 | 
			
		||||
      annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + GAP * 2 + PADDING,
 | 
			
		||||
      annotationGroupHeight + labelGroupHeight + attributeGroupHeight + y + GAP * 2 + PADDING,
 | 
			
		||||
      rectBBox.x + rectBBox.width,
 | 
			
		||||
      annotationGroupHeight + labelGroupHeight + membersGroupHeight + y + PADDING + GAP * 2,
 | 
			
		||||
      annotationGroupHeight + labelGroupHeight + attributeGroupHeight + y + PADDING + GAP * 2,
 | 
			
		||||
      options
 | 
			
		||||
    );
 | 
			
		||||
    const line = shapeSvg.insert(() => roughLine);
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@ export async function tiltedCylinder<T extends SVGGraphicsElement>(
 | 
			
		||||
    ) {
 | 
			
		||||
      let x = rx * rx * (1 - (y * y) / (ry * ry));
 | 
			
		||||
      if (x != 0) {
 | 
			
		||||
        x = Math.sqrt(x);
 | 
			
		||||
        x = Math.sqrt(Math.abs(x));
 | 
			
		||||
      }
 | 
			
		||||
      x = rx - x;
 | 
			
		||||
      if (point.x - (node.x ?? 0) > 0) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										24
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -226,9 +226,6 @@ importers:
 | 
			
		||||
      '@types/d3':
 | 
			
		||||
        specifier: ^7.4.3
 | 
			
		||||
        version: 7.4.3
 | 
			
		||||
      '@types/dompurify':
 | 
			
		||||
        specifier: ^3.0.5
 | 
			
		||||
        version: 3.0.5
 | 
			
		||||
      cytoscape:
 | 
			
		||||
        specifier: ^3.29.2
 | 
			
		||||
        version: 3.30.2
 | 
			
		||||
@@ -251,8 +248,8 @@ importers:
 | 
			
		||||
        specifier: ^1.11.10
 | 
			
		||||
        version: 1.11.13
 | 
			
		||||
      dompurify:
 | 
			
		||||
        specifier: ^3.0.11 <3.1.7
 | 
			
		||||
        version: 3.1.6
 | 
			
		||||
        specifier: ^3.2.1
 | 
			
		||||
        version: 3.2.1
 | 
			
		||||
      katex:
 | 
			
		||||
        specifier: ^0.16.9
 | 
			
		||||
        version: 0.16.11
 | 
			
		||||
@@ -2768,9 +2765,6 @@ packages:
 | 
			
		||||
  '@types/debug@4.1.12':
 | 
			
		||||
    resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
 | 
			
		||||
 | 
			
		||||
  '@types/dompurify@3.0.5':
 | 
			
		||||
    resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
 | 
			
		||||
 | 
			
		||||
  '@types/estree@0.0.39':
 | 
			
		||||
    resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
 | 
			
		||||
 | 
			
		||||
@@ -4720,8 +4714,8 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
 | 
			
		||||
    engines: {node: '>= 4'}
 | 
			
		||||
 | 
			
		||||
  dompurify@3.1.6:
 | 
			
		||||
    resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==}
 | 
			
		||||
  dompurify@3.2.1:
 | 
			
		||||
    resolution: {integrity: sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==}
 | 
			
		||||
 | 
			
		||||
  domutils@3.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
 | 
			
		||||
@@ -12125,10 +12119,6 @@ snapshots:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@types/ms': 0.7.34
 | 
			
		||||
 | 
			
		||||
  '@types/dompurify@3.0.5':
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@types/trusted-types': 2.0.7
 | 
			
		||||
 | 
			
		||||
  '@types/estree@0.0.39': {}
 | 
			
		||||
 | 
			
		||||
  '@types/estree@1.0.6': {}
 | 
			
		||||
@@ -12970,7 +12960,7 @@ snapshots:
 | 
			
		||||
      antlr4: 4.11.0
 | 
			
		||||
      color-string: 1.9.1
 | 
			
		||||
      dom-to-image-more: 2.16.0
 | 
			
		||||
      dompurify: 3.1.6
 | 
			
		||||
      dompurify: 3.2.1
 | 
			
		||||
      file-saver: 2.0.5
 | 
			
		||||
      highlight.js: 10.7.3
 | 
			
		||||
      html-to-image: 1.11.11
 | 
			
		||||
@@ -14509,7 +14499,9 @@ snapshots:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      domelementtype: 2.3.0
 | 
			
		||||
 | 
			
		||||
  dompurify@3.1.6: {}
 | 
			
		||||
  dompurify@3.2.1:
 | 
			
		||||
    optionalDependencies:
 | 
			
		||||
      '@types/trusted-types': 2.0.7
 | 
			
		||||
 | 
			
		||||
  domutils@3.1.0:
 | 
			
		||||
    dependencies:
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,6 @@ const SRC = {
 | 
			
		||||
          // to match the real `package.json` values
 | 
			
		||||
          'type-fest': '*',
 | 
			
		||||
          '@types/d3': '^7.4.3',
 | 
			
		||||
          '@types/dompurify': '^3.0.5',
 | 
			
		||||
          typescript: '*',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user