mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-31 02:44:17 +01:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			@mermaid-j
			...
			4915545429
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 4915545429 | ||
|   | 334fe87bc6 | ||
|   | 283e7810d2 | ||
|   | 237d01d510 | ||
|   | afeb761296 | ||
|   | 3abcfbb8d2 | ||
|   | ee82694645 | ||
|   | 012530e98e | ||
|   | a4a27611dd | ||
|   | 5055ade44e | ||
|   | b61bec8faf | ||
|   | 76d073b027 | ||
|   | cc476d59d1 | 
							
								
								
									
										5
									
								
								.changeset/hungry-baths-glow.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/hungry-baths-glow.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| --- | ||||
| 'mermaid': minor | ||||
| --- | ||||
|  | ||||
| feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`. | ||||
							
								
								
									
										644
									
								
								cypress/integration/rendering/sequencediagram-v2.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										644
									
								
								cypress/integration/rendering/sequencediagram-v2.spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,644 @@ | ||||
| import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; | ||||
|  | ||||
| const looks = ['classic']; | ||||
| const participantTypes = [ | ||||
|   'participant', | ||||
|   'actor', | ||||
|   'boundary', | ||||
|   'control', | ||||
|   'entity', | ||||
|   'database', | ||||
|   'collections', | ||||
|   'queue', | ||||
| ]; | ||||
|  | ||||
| const interactionTypes = [ | ||||
|   '->>', // Solid arrow with arrowhead | ||||
|   '-->>', // Dotted arrow with arrowhead | ||||
|   '->', // Solid arrow without arrowhead | ||||
|   '-->', // Dotted arrow without arrowhead | ||||
|   '-x', // Solid arrow with cross | ||||
|   '--x', // Dotted arrow with cross | ||||
|   '->>+', // Solid arrow with arrowhead (activate) | ||||
|   '-->>+', // Dotted arrow with arrowhead (activate) | ||||
| ]; | ||||
|  | ||||
| const notePositions = ['left of', 'right of', 'over']; | ||||
|  | ||||
| looks.forEach((look) => { | ||||
|   describe(`Sequence Diagram Tests - ${look} look`, () => { | ||||
|     it('should render all participant types', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       participantTypes.forEach((type, index) => { | ||||
|         diagramCode += `  ${type} ${type}${index} as ${type} ${index}\n`; | ||||
|       }); | ||||
|       // Add some basic interactions | ||||
|       for (let i = 0; i < participantTypes.length - 1; i++) { | ||||
|         diagramCode += `  ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; | ||||
|       } | ||||
|       imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } }); | ||||
|     }); | ||||
|  | ||||
|     it('should render all interaction types', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       // Create two participants | ||||
|       // Add all interaction types | ||||
|       diagramCode += `  participant A\n`; | ||||
|       diagramCode += `  participant B\n`; | ||||
|       interactionTypes.forEach((interaction, index) => { | ||||
|         diagramCode += `  A ${interaction} B: ${interaction} message ${index}\n`; | ||||
|       }); | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render participant creation and destruction', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       participantTypes.forEach((type, index) => { | ||||
|         diagramCode += `  ${type} A\n`; | ||||
|         diagramCode += `  ${type} B\n`; | ||||
|         diagramCode += `  create ${type} ${type}${index}\n`; | ||||
|         diagramCode += `  A ->> ${type}${index}: Hello ${type}\n`; | ||||
|         if (index % 2 === 0) { | ||||
|           diagramCode += `  destroy ${type}${index}\n`; | ||||
|         } | ||||
|       }); | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render notes in all positions', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       diagramCode += `  participant A\n`; | ||||
|       diagramCode += `  participant B\n`; | ||||
|       notePositions.forEach((position, index) => { | ||||
|         diagramCode += `  Note ${position} A: Note ${position} ${index}\n`; | ||||
|       }); | ||||
|       diagramCode += `  A ->> B: Message with notes\n`; | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render parallel interactions', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       participantTypes.slice(0, 4).forEach((type, index) => { | ||||
|         diagramCode += `  ${type} ${type}${index}\n`; | ||||
|       }); | ||||
|       diagramCode += `  par Parallel actions\n`; | ||||
|       for (let i = 0; i < participantTypes.length - 1; i += 2) { | ||||
|         diagramCode += `    ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`; | ||||
|         if (i < participantTypes.length - 2) { | ||||
|           diagramCode += `    and\n`; | ||||
|         } | ||||
|       } | ||||
|       diagramCode += `  end\n`; | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render alternative flows', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       diagramCode += `  participant A\n`; | ||||
|       diagramCode += `  participant B\n`; | ||||
|       diagramCode += `  alt Successful case\n`; | ||||
|       diagramCode += `    A ->> B: Request\n`; | ||||
|       diagramCode += `    B -->> A: Success\n`; | ||||
|       diagramCode += `  else Failure case\n`; | ||||
|       diagramCode += `    A ->> B: Request\n`; | ||||
|       diagramCode += `    B --x A: Failure\n`; | ||||
|       diagramCode += `  end\n`; | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render loops', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       participantTypes.slice(0, 3).forEach((type, index) => { | ||||
|         diagramCode += `  ${type} ${type}${index}\n`; | ||||
|       }); | ||||
|       diagramCode += `  loop For each participant\n`; | ||||
|       for (let i = 0; i < 3; i++) { | ||||
|         diagramCode += `    ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Message ${i}\n`; | ||||
|       } | ||||
|       diagramCode += `  end\n`; | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render boxes around groups', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       diagramCode += `  box Group 1\n`; | ||||
|       participantTypes.slice(0, 3).forEach((type, index) => { | ||||
|         diagramCode += `    ${type} ${type}${index}\n`; | ||||
|       }); | ||||
|       diagramCode += `  end\n`; | ||||
|       diagramCode += `  box rgb(200,220,255) Group 2\n`; | ||||
|       participantTypes.slice(3, 6).forEach((type, index) => { | ||||
|         diagramCode += `    ${type} ${type}${index}\n`; | ||||
|       }); | ||||
|       diagramCode += `  end\n`; | ||||
|       // Add some interactions | ||||
|       diagramCode += `  ${participantTypes[0]}0 ->> ${participantTypes[3]}0: Cross-group message\n`; | ||||
|       imgSnapshotTest(diagramCode, { look }); | ||||
|     }); | ||||
|  | ||||
|     it('should render with different font settings', () => { | ||||
|       let diagramCode = `sequenceDiagram\n`; | ||||
|       participantTypes.slice(0, 3).forEach((type, index) => { | ||||
|         diagramCode += `  ${type} ${type}${index}\n`; | ||||
|       }); | ||||
|       diagramCode += `  ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Regular message\n`; | ||||
|       diagramCode += `  Note right of ${participantTypes[1]}1: Regular note\n`; | ||||
|       imgSnapshotTest(diagramCode, { | ||||
|         look, | ||||
|         sequence: { | ||||
|           actorFontFamily: 'courier', | ||||
|           actorFontSize: 14, | ||||
|           messageFontFamily: 'Arial', | ||||
|           messageFontSize: 12, | ||||
|           noteFontFamily: 'times', | ||||
|           noteFontSize: 16, | ||||
|           noteAlign: 'left', | ||||
|         }, | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| // Additional tests for specific combinations | ||||
| describe('Sequence Diagram Special Cases', () => { | ||||
|   it('should render complex sequence with all features', () => { | ||||
|     const diagramCode = ` | ||||
|       sequenceDiagram | ||||
|         box rgb(200,220,255) Authentication | ||||
|           actor User | ||||
|           boundary LoginUI | ||||
|           control AuthService | ||||
|           database UserDB | ||||
|         end | ||||
|          | ||||
|         box rgb(200,255,220) Order Processing | ||||
|           entity Order | ||||
|           queue OrderQueue | ||||
|           collections AuditLogs | ||||
|         end | ||||
|          | ||||
|         User ->> LoginUI: Enter credentials | ||||
|         LoginUI ->> AuthService: Validate | ||||
|         AuthService ->> UserDB: Query user | ||||
|         UserDB -->> AuthService: User data | ||||
|         alt Valid credentials | ||||
|           AuthService -->> LoginUI: Success | ||||
|           LoginUI -->> User: Welcome | ||||
|            | ||||
|           par Place order | ||||
|             User ->> Order: New order | ||||
|             Order ->> OrderQueue: Process | ||||
|             and | ||||
|             Order ->> AuditLogs: Record | ||||
|           end | ||||
|            | ||||
|           loop Until confirmed | ||||
|             OrderQueue ->> Order: Update status | ||||
|             Order -->> User: Notification | ||||
|           end | ||||
|         else Invalid credentials | ||||
|           AuthService --x LoginUI: Failure | ||||
|           LoginUI --x User: Retry | ||||
|         end | ||||
|     `; | ||||
|     imgSnapshotTest(diagramCode, {}); | ||||
|   }); | ||||
|  | ||||
|   it('should render with wrapped messages and notes', () => { | ||||
|     const diagramCode = ` | ||||
|       sequenceDiagram | ||||
|         participant A | ||||
|         participant B | ||||
|          | ||||
|         A ->> B: This is a very long message that should wrap properly in the diagram rendering | ||||
|         Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram | ||||
|          | ||||
|         par Wrapped parallel | ||||
|           A ->> B: Parallel message 1<br>with explicit line break | ||||
|           and | ||||
|           B ->> A: Parallel message 2<br>with explicit line break | ||||
|         end | ||||
|          | ||||
|         loop Wrapped loop | ||||
|           Note right of B: This is a long note<br>in a loop | ||||
|           A ->> B: Message in loop | ||||
|         end | ||||
|     `; | ||||
|     imgSnapshotTest(diagramCode, { sequence: { wrap: true } }); | ||||
|   }); | ||||
|   describe('Sequence Diagram Rendering with Different Participant Types', () => { | ||||
|     it('should render a sequence diagram with various participant types', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|         actor User | ||||
|         participant AuthService as Authentication Service | ||||
|         boundary UI | ||||
|         control OrderController | ||||
|         entity Product | ||||
|         database MongoDB | ||||
|         collections Products | ||||
|         queue OrderQueue | ||||
|         User ->> UI: Login request | ||||
|         UI ->> AuthService: Validate credentials | ||||
|         AuthService -->> UI: Authentication token | ||||
|         UI ->> OrderController: Place order | ||||
|         OrderController ->> Product: Check availability | ||||
|         Product -->> OrderController: Available | ||||
|         OrderController ->> MongoDB: Save order | ||||
|         MongoDB -->> OrderController: Order saved | ||||
|         OrderController ->> OrderQueue: Process payment | ||||
|         OrderQueue -->> User: Order confirmation | ||||
|       ` | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     it('should render participant creation and destruction with different types', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|     sequenceDiagram | ||||
|       actor Customer | ||||
|       participant Frontend | ||||
|       boundary PaymentGateway | ||||
|       Customer ->> Frontend: Place order | ||||
|       Frontend ->> OrderProcessor: Process order | ||||
|       create database OrderDB | ||||
|       OrderProcessor ->> OrderDB: Save order | ||||
|       ` | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     it('should handle complex interactions between different participant types', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|     box rgba(200,220,255,0.5) System Components | ||||
|     actor User | ||||
|     boundary WebUI | ||||
|     control API | ||||
|     entity BusinessLogic | ||||
|     database MainDB | ||||
|     end | ||||
|     box rgba(200,255,220,0.5) External Services | ||||
|     queue MessageQueue | ||||
|     database AuditLogs | ||||
|     end | ||||
|  | ||||
|     User ->> WebUI: Submit request | ||||
|     WebUI ->> API: Process request | ||||
|     API ->> BusinessLogic: Execute business rules | ||||
|     BusinessLogic ->> MainDB: Query data | ||||
|     MainDB -->> BusinessLogic: Return results | ||||
|     BusinessLogic ->> MessageQueue: Publish event | ||||
|     MessageQueue -->> AuditLogs: Store audit trail | ||||
|     AuditLogs -->> API: Audit complete | ||||
|     API -->> WebUI: Return response | ||||
|     WebUI -->> User: Show results | ||||
|       `, | ||||
|         { sequence: { useMaxWidth: false } } | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     it('should render parallel processes with different participant types', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|         actor Customer | ||||
|         participant Frontend | ||||
|         boundary PaymentService | ||||
|         control InventoryManager | ||||
|         entity Order | ||||
|         database OrdersDB | ||||
|         queue NotificationQueue | ||||
|          | ||||
|         Customer ->> Frontend: Place order | ||||
|         Frontend ->> Order: Create order | ||||
|         par Parallel Processing | ||||
|           Order ->> PaymentService: Process payment | ||||
|           and | ||||
|           Order ->> InventoryManager: Reserve items | ||||
|         end | ||||
|         PaymentService -->> Order: Payment confirmed | ||||
|         InventoryManager -->> Order: Items reserved | ||||
|         Order ->> OrdersDB: Save finalized order | ||||
|         OrdersDB -->> Order: Order saved | ||||
|         Order ->> NotificationQueue: Send confirmation | ||||
|         NotificationQueue -->> Customer: Order confirmation | ||||
|       ` | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     it('should render different participant types with notes and loops', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|     sequenceDiagram | ||||
|     actor Admin | ||||
|     participant Dashboard | ||||
|     boundary AuthService | ||||
|     control UserManager | ||||
|     entity UserProfile | ||||
|     database UserDB | ||||
|     database Logs | ||||
|      | ||||
|     Admin ->> Dashboard: Open user management | ||||
|     loop Authentication check | ||||
|       Dashboard ->> AuthService: Verify admin rights | ||||
|       AuthService ->> Dashboard: Access granted | ||||
|     end | ||||
|     Dashboard ->> UserManager: List users | ||||
|     UserManager ->> UserDB: Query users | ||||
|     UserDB ->> UserManager: Return user data | ||||
|     Note right of UserDB: Encrypted data<br/>requires decryption | ||||
|     UserManager ->> UserProfile: Format profiles | ||||
|     UserProfile ->> UserManager: Formatted data | ||||
|     UserManager ->> Dashboard: Display users | ||||
|     Dashboard ->> Logs: Record access | ||||
|     Logs ->> Admin: Audit trail | ||||
|       ` | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     it('should render different participant types with alternative flows', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|         actor Client | ||||
|         participant MobileApp | ||||
|         boundary CloudService | ||||
|         control DataProcessor | ||||
|         entity Transaction | ||||
|         database TransactionsDB | ||||
|         queue EventBus | ||||
|          | ||||
|         Client ->> MobileApp: Initiate transaction | ||||
|         MobileApp ->> CloudService: Authenticate | ||||
|         alt Authentication successful | ||||
|           CloudService -->> MobileApp: Auth token | ||||
|           MobileApp ->> DataProcessor: Process data | ||||
|           DataProcessor ->> Transaction: Create transaction | ||||
|           Transaction ->> TransactionsDB: Save record | ||||
|           TransactionsDB -->> Transaction: Confirmation | ||||
|           Transaction ->> EventBus: Publish event | ||||
|           EventBus -->> Client: Notification | ||||
|         else Authentication failed | ||||
|           CloudService -->> MobileApp: Error | ||||
|           MobileApp -->> Client: Show error | ||||
|         end | ||||
|       ` | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     it('should render different participant types with wrapping text', () => { | ||||
|       imgSnapshotTest( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|         actor LongNameUser as User With A Very<br/>Long Name | ||||
|         participant FE as Frontend Service<br/>With Long Name | ||||
|         boundary B as Boundary With<br/>Multiline Name | ||||
|         control C as Control With<br/>Multiline Name | ||||
|         entity E as Entity With<br/>Multiline Name | ||||
|         database DB as Database With<br/>Multiline Name | ||||
|         collections COL as Collections With<br/>Multiline Name | ||||
|         queue Q as Queue With<br/>Multiline Name | ||||
|          | ||||
|         LongNameUser ->> FE: This is a very long message that should wrap properly in the diagram | ||||
|         FE ->> B: Another long message<br/>with explicit<br/>line breaks | ||||
|         B -->> FE: Response message that is also quite long and needs to wrap | ||||
|         FE ->> C: Process data | ||||
|         C ->> E: Validate | ||||
|         E -->> C: Validation result | ||||
|         C ->> DB: Save | ||||
|         DB -->> C: Save result | ||||
|         C ->> COL: Log | ||||
|         COL -->> Q: Forward | ||||
|         Q -->> LongNameUser: Final response with confirmation of all actions taken | ||||
|       `, | ||||
|         { sequence: { wrap: true } } | ||||
|       ); | ||||
|     }); | ||||
|     describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => { | ||||
|       it('should render long notes left of boundary', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       boundary Alice | ||||
|       actor Bob | ||||
|       Alice->>Bob: Hola | ||||
|       Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render wrapped long notes left of control', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       control Alice | ||||
|       actor Bob | ||||
|       Alice->>Bob: Hola | ||||
|       Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render long notes right of entity', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       entity Alice | ||||
|       actor Bob | ||||
|       Alice->>Bob: Hola | ||||
|       Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render wrapped long notes right of database', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       database Alice | ||||
|       actor Bob | ||||
|       Alice->>Bob: Hola | ||||
|       Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render long notes over collections', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       collections Alice | ||||
|       actor Bob | ||||
|       Alice->>Bob: Hola | ||||
|       Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render wrapped long notes over queue', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       queue Alice | ||||
|       actor Bob | ||||
|       Alice->>Bob: Hola | ||||
|       Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render notes over actor and boundary', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       actor Alice | ||||
|       boundary Charlie | ||||
|       note over Alice: Some note | ||||
|       note over Charlie: Other note | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render long messages from database to collections', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       database Alice | ||||
|       collections Bob | ||||
|       Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render wrapped long messages from control to entity', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       control Alice | ||||
|       entity Bob | ||||
|       Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|       Bob->>Alice: I'm short though | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render long messages from queue to boundary', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       queue Alice | ||||
|       boundary Bob | ||||
|       Alice->>Bob: I'm short | ||||
|       Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       it('should render wrapped long messages from actor to database', () => { | ||||
|         imgSnapshotTest( | ||||
|           ` | ||||
|       sequenceDiagram | ||||
|       actor Alice | ||||
|       database Bob | ||||
|       Alice->>Bob: I'm short | ||||
|       Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be | ||||
|     `, | ||||
|           {} | ||||
|         ); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('svg size', () => { | ||||
|     it('should render a sequence diagram when useMaxWidth is true (default)', () => { | ||||
|       renderGraph( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|         actor Alice | ||||
|         boundary Bob | ||||
|         control John as John<br/>Second Line | ||||
|         Alice ->> Bob: Hello Bob, how are you? | ||||
|         Bob-->>John: How about you John? | ||||
|         Bob--x Alice: I am good thanks! | ||||
|         Bob-x John: I am good thanks! | ||||
|         Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row. | ||||
|         Bob-->Alice: Checking with John... | ||||
|         alt either this | ||||
|           Alice->>John: Yes | ||||
|           else or this | ||||
|           Alice->>John: No | ||||
|           else or this will happen | ||||
|           Alice->John: Maybe | ||||
|         end | ||||
|         par this happens in parallel | ||||
|           Alice -->> Bob: Parallel message 1 | ||||
|         and | ||||
|           Alice -->> John: Parallel message 2 | ||||
|         end | ||||
|       `, | ||||
|         { sequence: { useMaxWidth: true } } | ||||
|       ); | ||||
|       cy.get('svg').should((svg) => { | ||||
|         expect(svg).to.have.attr('width', '100%'); | ||||
|         const style = svg.attr('style'); | ||||
|         expect(style).to.match(/^max-width: [\d.]+px;$/); | ||||
|         const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); | ||||
|         expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     it('should render a sequence diagram when useMaxWidth is false', () => { | ||||
|       renderGraph( | ||||
|         ` | ||||
|       sequenceDiagram | ||||
|         actor Alice | ||||
|         boundary Bob | ||||
|         control John as John<br/>Second Line | ||||
|         Alice ->> Bob: Hello Bob, how are you? | ||||
|         Bob-->>John: How about you John? | ||||
|         Bob--x Alice: I am good thanks! | ||||
|         Bob-x John: I am good thanks! | ||||
|         Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row. | ||||
|         Bob-->Alice: Checking with John... | ||||
|         alt either this | ||||
|           Alice->>John: Yes | ||||
|           else or this | ||||
|           Alice->>John: No | ||||
|           else or this will happen | ||||
|           Alice->John: Maybe | ||||
|         end | ||||
|         par this happens in parallel | ||||
|           Alice -->> Bob: Parallel message 1 | ||||
|         and | ||||
|           Alice -->> John: Parallel message 2 | ||||
|         end | ||||
|       `, | ||||
|         { sequence: { useMaxWidth: false } } | ||||
|       ); | ||||
|       cy.get('svg').should((svg) => { | ||||
|         const width = parseFloat(svg.attr('width')); | ||||
|         expect(width).to.be.within(820 * 0.95, 820 * 1.05); | ||||
|         expect(svg).to.not.have.attr('style'); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @@ -12,6 +12,7 @@ | ||||
|  | ||||
| %options case-insensitive | ||||
|  | ||||
|  | ||||
| // Special states for recognizing aliases | ||||
| // A special state for grabbing text up to the first comment/newline | ||||
| %x ID ALIAS LINE | ||||
| @@ -31,6 +32,12 @@ | ||||
| "box"															{ this.begin('LINE'); return 'box'; } | ||||
| "participant"                                                   { this.begin('ID'); return 'participant'; } | ||||
| "actor"                                                   		{ this.begin('ID'); return 'participant_actor'; } | ||||
| "boundary"                                                      { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); } | ||||
| "control"                                                       { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); } | ||||
| "entity"                                                        { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); } | ||||
| "database"                                                      { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); } | ||||
| "collections"                                                   { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); } | ||||
| "queue"                                                         { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); } | ||||
| "create"                                                        return 'create'; | ||||
| "destroy"                                                       { this.begin('ID'); return 'destroy'; } | ||||
| <ID>[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$)     { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } | ||||
| @@ -231,6 +238,25 @@ participant_statement | ||||
| 	| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;} | ||||
| 	| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} | ||||
|  | ||||
| 	| 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;} | ||||
|  | ||||
| 	| 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_control' actor 'NEWLINE' {$2.draw='control'; $2.type='addParticipant'; $$=$2;} | ||||
|  | ||||
| 	| 'participant_entity' actor 'AS' restOfLine 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_entity' actor 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant'; $$=$2;} | ||||
|  | ||||
| 	| 'participant_database' actor 'AS' restOfLine 'NEWLINE' {$2.draw='database'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_database' actor 'NEWLINE' {$2.draw='database'; $2.type='addParticipant'; $$=$2;} | ||||
|  | ||||
| 	| 'participant_collections' actor 'AS' restOfLine 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_collections' actor 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant'; $$=$2;} | ||||
|  | ||||
| 	| 'participant_queue' actor 'AS' restOfLine 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} | ||||
| 	| 'participant_queue' actor 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant'; $$=$2;} | ||||
|  | ||||
| 	; | ||||
|  | ||||
| note_statement | ||||
|   | ||||
| @@ -75,6 +75,17 @@ const PLACEMENT = { | ||||
|   OVER: 2, | ||||
| } as const; | ||||
|  | ||||
| export const PARTICIPANT_TYPE = { | ||||
|   ACTOR: 'actor', | ||||
|   BOUNDARY: 'boundary', | ||||
|   COLLECTIONS: 'collections', | ||||
|   CONTROL: 'control', | ||||
|   DATABASE: 'database', | ||||
|   ENTITY: 'entity', | ||||
|   PARTICIPANT: 'participant', | ||||
|   QUEUE: 'queue', | ||||
| } as const; | ||||
|  | ||||
| export class SequenceDB implements DiagramDB { | ||||
|   private readonly state = new ImperativeState<SequenceState>(() => ({ | ||||
|     prevActor: undefined, | ||||
| @@ -96,6 +107,7 @@ export class SequenceDB implements DiagramDB { | ||||
|     this.apply = this.apply.bind(this); | ||||
|     this.parseBoxData = this.parseBoxData.bind(this); | ||||
|     this.parseMessage = this.parseMessage.bind(this); | ||||
|     this.matchAsActorOrParticipant = this.matchAsActorOrParticipant.bind(this); | ||||
|  | ||||
|     this.clear(); | ||||
|  | ||||
| @@ -330,6 +342,23 @@ export class SequenceDB implements DiagramDB { | ||||
|     return message; | ||||
|   } | ||||
|  | ||||
|   public matchAsActorOrParticipant( | ||||
|     tokenName: string, | ||||
|     tokenType: string, | ||||
|     inputRemainder: string, | ||||
|     lexer: any | ||||
|   ): string { | ||||
|     log.info({ tokenName }); | ||||
|     const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/; | ||||
|     const colonLike = /^\s*:/; | ||||
|  | ||||
|     if (arrowLike.test(inputRemainder) || colonLike.test(inputRemainder)) { | ||||
|       return 'ACTOR'; | ||||
|     } | ||||
|     lexer.begin('ID'); // used  the passed lexer | ||||
|     return tokenType; | ||||
|   } | ||||
|  | ||||
|   // We expect the box statement to be color first then description | ||||
|   // The color can be rgb,rgba,hsl,hsla, or css code names  #hex codes are not supported for now because of the way the char # is handled | ||||
|   // We extract first segment as color, the rest of the line is considered as text | ||||
|   | ||||
| @@ -2038,4 +2038,189 @@ Bob->>Alice:Got it! | ||||
|     expect(messages[0].from).toBe('Alice'); | ||||
|     expect(messages[0].to).toBe('Bob'); | ||||
|   }); | ||||
|   describe('when newly parsing messages ', () => { | ||||
|     it('should parse a message', async () => { | ||||
|       const actor1 = 'database'; | ||||
|       const diagram = await Diagram.fromText(` | ||||
|     sequenceDiagram | ||||
|         database Alice | ||||
|         database Bob | ||||
|         Bob->>+Alice: Hi Alice | ||||
|         Alice->>+Bob: Hi Bob | ||||
|       `); | ||||
|  | ||||
|       const messages = diagram.db.getMessages(); | ||||
|     }); | ||||
|  | ||||
|     it('should parse a message', async () => { | ||||
|       const actor1 = 'database'; | ||||
|       const diagram = await Diagram.fromText(` | ||||
|     sequenceDiagram | ||||
|         participant lead | ||||
|         queue dsa | ||||
|         API->>+Database: getUserb | ||||
|         Database-->>-API: userb | ||||
|         queue --> Database: hello | ||||
|     `); | ||||
|  | ||||
|       const messages = diagram.db.getMessages(); | ||||
|     }); | ||||
|   }); | ||||
|   describe('participant type parsing', () => { | ||||
|     it('should parse boundary participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|           sequenceDiagram | ||||
|           boundary B as Boundary Box | ||||
|           B->B: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('B').type).toBe('boundary'); | ||||
|       expect(actors.get('B').description).toBe('Boundary Box'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse control participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|         sequenceDiagram | ||||
|         control C as Controller | ||||
|         C->C: test | ||||
|         `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('C').type).toBe('control'); | ||||
|       expect(actors.get('C').description).toBe('Controller'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse entity participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       entity E as Entity | ||||
|       E->E: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('E').type).toBe('entity'); | ||||
|       expect(actors.get('E').description).toBe('Entity'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse database participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       database D as Database | ||||
|       D->D: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('D').type).toBe('database'); | ||||
|       expect(actors.get('D').description).toBe('Database'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse collections participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       collections L as List | ||||
|       L->L: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('L').type).toBe('collections'); | ||||
|       expect(actors.get('L').description).toBe('List'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse queue participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       queue Q as Jobs | ||||
|       Q->Q: test  | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('Q').type).toBe('queue'); | ||||
|       expect(actors.get('Q').description).toBe('Jobs'); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('participant type parsing', () => { | ||||
|     it('should parse actor participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       queue A as ActorName | ||||
|       A->A: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('A').type).toBe('queue'); | ||||
|       expect(actors.get('A').description).toBe('ActorName'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse participant participant', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       database P as PartName | ||||
|       P->P: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('P').type).toBe('database'); | ||||
|       expect(actors.get('P').description).toBe('PartName'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse boundary using actor keyword', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       collections B as Boundary | ||||
|       B->B: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('B').type).toBe('collections'); | ||||
|       expect(actors.get('B').description).toBe('Boundary'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse control using participant keyword', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       control C as Controller | ||||
|       C->C: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('C').type).toBe('control'); | ||||
|       expect(actors.get('C').description).toBe('Controller'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse entity using actor keyword', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       entity E as Entity | ||||
|       E->E: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('E').type).toBe('entity'); | ||||
|       expect(actors.get('E').description).toBe('Entity'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse database using participant keyword', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       participant D as Database | ||||
|       D->D: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('D').type).toBe('participant'); | ||||
|       expect(actors.get('D').description).toBe('Database'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse collections using actor keyword', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       actor L as List | ||||
|       L->L: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('L').type).toBe('actor'); | ||||
|       expect(actors.get('L').description).toBe('List'); | ||||
|     }); | ||||
|  | ||||
|     it('should parse queue using participant keyword', async () => { | ||||
|       const diagram = await Diagram.fromText(` | ||||
|       sequenceDiagram | ||||
|       participant Q as Jobs | ||||
|       Q->Q: test | ||||
|       `); | ||||
|       const actors = diagram.db.getActors(); | ||||
|       expect(actors.get('Q').type).toBe('participant'); | ||||
|       expect(actors.get('Q').description).toBe('Jobs'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js'; | ||||
| import utils from '../../utils.js'; | ||||
| import { configureSvgSize } from '../../setupGraphViewbox.js'; | ||||
| import type { Diagram } from '../../Diagram.js'; | ||||
| import { PARTICIPANT_TYPE } from './sequenceDb.js'; | ||||
|  | ||||
| let conf = {}; | ||||
|  | ||||
| @@ -724,11 +725,19 @@ function adjustCreatedDestroyedData( | ||||
|       msgModel.startx = msgModel.startx - adjustment; | ||||
|     } | ||||
|   } | ||||
|   const actorArray = [ | ||||
|     PARTICIPANT_TYPE.ACTOR, | ||||
|     PARTICIPANT_TYPE.CONTROL, | ||||
|     PARTICIPANT_TYPE.ENTITY, | ||||
|     PARTICIPANT_TYPE.DATABASE, | ||||
|   ]; | ||||
|  | ||||
|   // if it is a create message | ||||
|   if (createdActors.get(msg.to) == index) { | ||||
|     const actor = actors.get(msg.to); | ||||
|     const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; | ||||
|     const adjustment = actorArray.includes(actor.type) | ||||
|       ? ACTOR_TYPE_WIDTH / 2 + 3 | ||||
|       : actor.width / 2 + 3; | ||||
|     receiverAdjustment(actor, adjustment); | ||||
|     actor.starty = lineStartY - actor.height / 2; | ||||
|     bounds.bumpVerticalPos(actor.height / 2); | ||||
| @@ -737,7 +746,7 @@ function adjustCreatedDestroyedData( | ||||
|   else if (destroyedActors.get(msg.from) == index) { | ||||
|     const actor = actors.get(msg.from); | ||||
|     if (conf.mirrorActors) { | ||||
|       const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; | ||||
|       const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; | ||||
|       senderAdjustment(actor, adjustment); | ||||
|     } | ||||
|     actor.stopy = lineStartY - actor.height / 2; | ||||
| @@ -747,7 +756,9 @@ function adjustCreatedDestroyedData( | ||||
|   else if (destroyedActors.get(msg.to) == index) { | ||||
|     const actor = actors.get(msg.to); | ||||
|     if (conf.mirrorActors) { | ||||
|       const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; | ||||
|       const adjustment = actorArray.includes(actor.type) | ||||
|         ? ACTOR_TYPE_WIDTH / 2 + 3 | ||||
|         : actor.width / 2 + 3; | ||||
|       receiverAdjustment(actor, adjustment); | ||||
|     } | ||||
|     actor.stopy = lineStartY - actor.height / 2; | ||||
| @@ -1065,10 +1076,11 @@ export const draw = async function (_text: string, id: string, _version: string, | ||||
|   for (const box of bounds.models.boxes) { | ||||
|     box.height = bounds.getVerticalPos() - box.y; | ||||
|     bounds.insert(box.x, box.y, box.x + box.width, box.height); | ||||
|     box.startx = box.x; | ||||
|     box.starty = box.y; | ||||
|     box.stopx = box.startx + box.width; | ||||
|     box.stopy = box.starty + box.height; | ||||
|     const boxPadding = conf.boxMargin * 2; | ||||
|     box.startx = box.x - boxPadding; | ||||
|     box.starty = box.y - boxPadding * 0.25; | ||||
|     box.stopx = box.startx + box.width + 2 * boxPadding; | ||||
|     box.stopy = box.starty + box.height + boxPadding * 0.75; | ||||
|     box.stroke = 'rgb(0,0,0, 0.5)'; | ||||
|     svgDraw.drawBox(diagram, box, conf); | ||||
|   } | ||||
| @@ -1333,6 +1345,9 @@ async function calculateActorMargins( | ||||
|       return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0)); | ||||
|     }, 0); | ||||
|  | ||||
|     const standardBoxPadding = conf.boxMargin * 8; | ||||
|     totalWidth += standardBoxPadding; | ||||
|  | ||||
|     totalWidth -= 2 * conf.boxTextMargin; | ||||
|     if (box.wrap) { | ||||
|       box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); | ||||
|   | ||||
| @@ -13,6 +13,11 @@ const getStyles = (options) => | ||||
|     stroke: ${options.actorLineColor}; | ||||
|   } | ||||
|    | ||||
|   .innerArc { | ||||
|     stroke-width: 1.5; | ||||
|     stroke-dasharray: none; | ||||
|   } | ||||
|  | ||||
|   .messageLine0 { | ||||
|     stroke-width: 1.5; | ||||
|     stroke-dasharray: none; | ||||
| @@ -115,6 +120,7 @@ const getStyles = (options) => | ||||
|     fill: ${options.actorBkg}; | ||||
|     stroke-width: 2px; | ||||
|   } | ||||
|  | ||||
| `; | ||||
|  | ||||
| export default getStyles; | ||||
|   | ||||
| @@ -411,6 +411,608 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { | ||||
|   return height; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Draws an actor in the diagram with the attached line | ||||
|  * | ||||
|  * @param {any} elem - The diagram we'll draw to. | ||||
|  * @param {any} actor - The actor to draw. | ||||
|  * @param {any} conf - DrawText implementation discriminator object | ||||
|  * @param {boolean} isFooter - If the actor is the footer one | ||||
|  */ | ||||
| const drawActorTypeCollections = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
|   const centerY = actorY + actor.height; | ||||
|  | ||||
|   const boxplusLineGroup = elem.append('g').lower(); | ||||
|   var g = boxplusLineGroup; | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actorCnt++; | ||||
|     if (Object.keys(actor.links || {}).length && !conf.forceMenus) { | ||||
|       g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); | ||||
|     } | ||||
|     g.append('line') | ||||
|       .attr('id', 'actor' + actorCnt) | ||||
|       .attr('x1', center) | ||||
|       .attr('y1', centerY) | ||||
|       .attr('x2', center) | ||||
|       .attr('y2', 2000) | ||||
|       .attr('class', 'actor-line 200') | ||||
|       .attr('stroke-width', '0.5px') | ||||
|       .attr('stroke', '#999') | ||||
|       .attr('name', actor.name); | ||||
|  | ||||
|     g = boxplusLineGroup.append('g'); | ||||
|     actor.actorCnt = actorCnt; | ||||
|  | ||||
|     if (actor.links != null) { | ||||
|       g.attr('id', 'root-' + actorCnt); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const rect = svgDrawCommon.getNoteRect(); | ||||
|   var cssclass = 'actor'; | ||||
|   if (actor.properties?.class) { | ||||
|     cssclass = actor.properties.class; | ||||
|   } else { | ||||
|     rect.fill = '#eaeaea'; | ||||
|   } | ||||
|   if (isFooter) { | ||||
|     cssclass += ` ${BOTTOM_ACTOR_CLASS}`; | ||||
|   } else { | ||||
|     cssclass += ` ${TOP_ACTOR_CLASS}`; | ||||
|   } | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   rect.width = actor.width; | ||||
|   rect.height = actor.height; | ||||
|   rect.class = cssclass; | ||||
|   // rect.rx = 3; | ||||
|   // rect.ry = 3; | ||||
|   rect.name = actor.name; | ||||
|  | ||||
|   // 🔹 DRAW STACKED RECTANGLES | ||||
|   const offset = 6; | ||||
|   const shadowRect = { | ||||
|     ...rect, | ||||
|     x: rect.x + offset, | ||||
|     y: rect.y + (isFooter ? +offset : -offset), | ||||
|     class: 'actor', | ||||
|   }; | ||||
|   drawRect(g, shadowRect); | ||||
|   const rectElem = drawRect(g, rect); // draw main rectangle on top | ||||
|   actor.rectData = rect; | ||||
|  | ||||
|   if (actor.properties?.icon) { | ||||
|     const iconSrc = actor.properties.icon.trim(); | ||||
|     if (iconSrc.charAt(0) === '@') { | ||||
|       svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); | ||||
|     } else { | ||||
|       svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _drawTextCandidateFunc(conf, hasKatex(actor.description))( | ||||
|     actor.description, | ||||
|     g, | ||||
|     rect.x, | ||||
|     rect.y, | ||||
|     rect.width, | ||||
|     rect.height, | ||||
|     { class: `actor ${ACTOR_BOX_CLASS}` }, | ||||
|     conf | ||||
|   ); | ||||
|  | ||||
|   let height = actor.height; | ||||
|   if (rectElem.node) { | ||||
|     const bounds = rectElem.node().getBBox(); | ||||
|     actor.height = bounds.height; | ||||
|     height = bounds.height; | ||||
|   } | ||||
|  | ||||
|   return height; | ||||
| }; | ||||
|  | ||||
| const drawActorTypeQueue = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
|   const centerY = actorY + actor.height; | ||||
|  | ||||
|   const boxplusLineGroup = elem.append('g').lower(); | ||||
|   let g = boxplusLineGroup; | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actorCnt++; | ||||
|     if (Object.keys(actor.links || {}).length && !conf.forceMenus) { | ||||
|       g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); | ||||
|     } | ||||
|     g.append('line') | ||||
|       .attr('id', 'actor' + actorCnt) | ||||
|       .attr('x1', center) | ||||
|       .attr('y1', centerY) | ||||
|       .attr('x2', center) | ||||
|       .attr('y2', 2000) | ||||
|       .attr('class', 'actor-line 200') | ||||
|       .attr('stroke-width', '0.5px') | ||||
|       .attr('stroke', '#999') | ||||
|       .attr('name', actor.name); | ||||
|  | ||||
|     g = boxplusLineGroup.append('g'); | ||||
|     actor.actorCnt = actorCnt; | ||||
|  | ||||
|     if (actor.links != null) { | ||||
|       g.attr('id', 'root-' + actorCnt); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const rect = svgDrawCommon.getNoteRect(); | ||||
|   let cssclass = 'actor'; | ||||
|   if (actor.properties?.class) { | ||||
|     cssclass = actor.properties.class; | ||||
|   } else { | ||||
|     rect.fill = '#eaeaea'; | ||||
|   } | ||||
|  | ||||
|   if (isFooter) { | ||||
|     cssclass += ` ${BOTTOM_ACTOR_CLASS}`; | ||||
|   } else { | ||||
|     cssclass += ` ${TOP_ACTOR_CLASS}`; | ||||
|   } | ||||
|  | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   rect.width = actor.width; | ||||
|   rect.height = actor.height; | ||||
|   rect.class = cssclass; | ||||
|   rect.name = actor.name; | ||||
|  | ||||
|   // Cylinder dimensions | ||||
|   const ry = rect.height / 2; | ||||
|   const rx = ry / (2.5 + rect.height / 50); | ||||
|  | ||||
|   // Cylinder base group | ||||
|   const cylinderGroup = g.append('g'); | ||||
|   const cylinderArc = g.append('g'); | ||||
|  | ||||
|   // Main cylinder body | ||||
|   cylinderGroup | ||||
|     .append('path') | ||||
|     .attr( | ||||
|       'd', | ||||
|       `M ${rect.x},${rect.y + ry} | ||||
|     a ${rx},${ry} 0 0 0 0,${rect.height} | ||||
|     h ${rect.width - 2 * rx} | ||||
|     a ${rx},${ry} 0 0 0 0,-${rect.height} | ||||
|     Z | ||||
|   ` | ||||
|     ) | ||||
|     .attr('class', cssclass); | ||||
|   cylinderArc | ||||
|     .append('path') | ||||
|     .attr( | ||||
|       'd', | ||||
|       `M ${rect.x},${rect.y + ry} | ||||
|       a ${rx},${ry} 0 0 0 0,${rect.height}` | ||||
|     ) | ||||
|     .attr('stroke', '#666') | ||||
|     .attr('stroke-width', '1px') | ||||
|     .attr('class', cssclass); | ||||
|  | ||||
|   cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`); | ||||
|   cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`); | ||||
|  | ||||
|   actor.rectData = rect; | ||||
|  | ||||
|   if (actor.properties?.icon) { | ||||
|     const iconSrc = actor.properties.icon.trim(); | ||||
|     const iconX = rect.x + rect.width - 20; | ||||
|     const iconY = rect.y + 10; | ||||
|     if (iconSrc.charAt(0) === '@') { | ||||
|       svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1)); | ||||
|     } else { | ||||
|       svgDrawCommon.drawImage(g, iconX, iconY, iconSrc); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _drawTextCandidateFunc(conf, hasKatex(actor.description))( | ||||
|     actor.description, | ||||
|     g, | ||||
|     rect.x, | ||||
|     rect.y, | ||||
|     rect.width, | ||||
|     rect.height, | ||||
|     { class: `actor ${ACTOR_BOX_CLASS}` }, | ||||
|     conf | ||||
|   ); | ||||
|  | ||||
|   let height = actor.height; | ||||
|   const lastPath = cylinderGroup.select('path:last-child'); | ||||
|   if (lastPath.node()) { | ||||
|     const bounds = lastPath.node().getBBox(); | ||||
|     actor.height = bounds.height; | ||||
|     height = bounds.height; | ||||
|   } | ||||
|  | ||||
|   return height; | ||||
| }; | ||||
|  | ||||
| const drawActorTypeControl = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
|   const centerY = actorY + 75; | ||||
|  | ||||
|   const line = elem.append('g').lower(); | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actorCnt++; | ||||
|     line | ||||
|       .append('line') | ||||
|       .attr('id', 'actor' + actorCnt) | ||||
|       .attr('x1', center) | ||||
|       .attr('y1', centerY) | ||||
|       .attr('x2', center) | ||||
|       .attr('y2', 2000) | ||||
|       .attr('class', 'actor-line 200') | ||||
|       .attr('stroke-width', '0.5px') | ||||
|       .attr('stroke', '#999') | ||||
|       .attr('name', actor.name); | ||||
|  | ||||
|     actor.actorCnt = actorCnt; | ||||
|   } | ||||
|   const actElem = elem.append('g'); | ||||
|   let cssClass = ACTOR_MAN_FIGURE_CLASS; | ||||
|   if (isFooter) { | ||||
|     cssClass += ` ${BOTTOM_ACTOR_CLASS}`; | ||||
|   } else { | ||||
|     cssClass += ` ${TOP_ACTOR_CLASS}`; | ||||
|   } | ||||
|   actElem.attr('class', cssClass); | ||||
|   actElem.attr('name', actor.name); | ||||
|  | ||||
|   const rect = svgDrawCommon.getNoteRect(); | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   rect.fill = '#eaeaea'; | ||||
|   rect.width = actor.width; | ||||
|   rect.height = actor.height; | ||||
|   rect.class = 'actor'; | ||||
|  | ||||
|   const cx = actor.x + actor.width / 2; | ||||
|   const cy = actorY + 30; | ||||
|   const r = 18; | ||||
|  | ||||
|   actElem | ||||
|     .append('defs') | ||||
|     .append('marker') | ||||
|     .attr('id', 'filled-head-control') | ||||
|     .attr('refX', 15.5) | ||||
|     .attr('refY', 7) | ||||
|     .attr('markerWidth', 20) | ||||
|     .attr('markerHeight', 28) | ||||
|     .attr('orient', '180') | ||||
|     .append('path') | ||||
|     .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); | ||||
|  | ||||
|   // Draw the base circle | ||||
|   actElem | ||||
|     .append('circle') | ||||
|     .attr('cx', cx) | ||||
|     .attr('cy', cy) | ||||
|     .attr('r', r) | ||||
|     .attr('fill', '#eaeaf7') | ||||
|     .attr('stroke', '#666') | ||||
|     .attr('stroke-width', 1.2); | ||||
|  | ||||
|   // Draw looping arrow as arc path | ||||
|   actElem | ||||
|     .append('path') | ||||
|     .attr('d', `M ${cx},${cy - r}`) | ||||
|     .attr('stroke-width', 1.5) | ||||
|     .attr('marker-end', 'url(#filled-head-control)'); | ||||
|  | ||||
|   const bounds = actElem.node().getBBox(); | ||||
|   actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0); | ||||
|  | ||||
|   _drawTextCandidateFunc(conf, hasKatex(actor.description))( | ||||
|     actor.description, | ||||
|     actElem, | ||||
|     rect.x, | ||||
|     rect.y + 30, | ||||
|     rect.width, | ||||
|     rect.height, | ||||
|     { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, | ||||
|     conf | ||||
|   ); | ||||
|  | ||||
|   return actor.height; | ||||
| }; | ||||
|  | ||||
| const drawActorTypeEntity = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
|   const centerY = actorY + 75; | ||||
|  | ||||
|   const line = elem.append('g').lower(); | ||||
|  | ||||
|   const actElem = elem.append('g'); | ||||
|   let cssClass = ACTOR_MAN_FIGURE_CLASS; | ||||
|   if (isFooter) { | ||||
|     cssClass += ` ${BOTTOM_ACTOR_CLASS}`; | ||||
|   } else { | ||||
|     cssClass += ` ${TOP_ACTOR_CLASS}`; | ||||
|   } | ||||
|   actElem.attr('class', cssClass); | ||||
|   actElem.attr('name', actor.name); | ||||
|  | ||||
|   const rect = svgDrawCommon.getNoteRect(); | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   rect.fill = '#eaeaea'; | ||||
|   rect.width = actor.width; | ||||
|   rect.height = actor.height; | ||||
|   rect.class = 'actor'; | ||||
|  | ||||
|   const cx = actor.x + actor.width / 2; | ||||
|   const cy = actorY + 25; | ||||
|   const r = 18; | ||||
|  | ||||
|   actElem | ||||
|     .append('circle') | ||||
|     .attr('cx', cx) | ||||
|     .attr('cy', cy) | ||||
|     .attr('r', r) | ||||
|     .attr('width', actor.width) | ||||
|     .attr('height', actor.height); | ||||
|  | ||||
|   actElem | ||||
|     .append('line') | ||||
|     .attr('x1', cx - r) | ||||
|     .attr('x2', cx + r) | ||||
|     .attr('y1', cy + r) | ||||
|     .attr('y2', cy + r) | ||||
|     .attr('stroke', '#333') | ||||
|     .attr('stroke-width', 2); | ||||
|  | ||||
|   const bounds = actElem.node().getBBox(); | ||||
|   actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actorCnt++; | ||||
|     line | ||||
|       .append('line') | ||||
|       .attr('id', 'actor' + actorCnt) | ||||
|       .attr('x1', center) | ||||
|       .attr('y1', centerY) | ||||
|       .attr('x2', center) | ||||
|       .attr('y2', 2000) | ||||
|       .attr('class', 'actor-line 200') | ||||
|       .attr('stroke-width', '0.5px') | ||||
|       .attr('stroke', '#999') | ||||
|       .attr('name', actor.name); | ||||
|  | ||||
|     actor.actorCnt = actorCnt; | ||||
|   } | ||||
|  | ||||
|   _drawTextCandidateFunc(conf, hasKatex(actor.description))( | ||||
|     actor.description, | ||||
|     actElem, | ||||
|     rect.x, | ||||
|     rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY) / 2 + r + 2), | ||||
|     rect.width, | ||||
|     rect.height, | ||||
|     { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, | ||||
|     conf | ||||
|   ); | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actElem.attr('transform', `translate(${0}, ${r / 2})`); | ||||
|   } else { | ||||
|     actElem.attr('transform', `translate(${0}, ${r / 2})`); | ||||
|   } | ||||
|  | ||||
|   return actor.height; | ||||
| }; | ||||
|  | ||||
| const drawActorTypeDatabase = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
|   const centerY = actorY + actor.height + 2 * conf.boxTextMargin; | ||||
|  | ||||
|   const boxplusLineGroup = elem.append('g').lower(); | ||||
|   let g = boxplusLineGroup; | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actorCnt++; | ||||
|     if (Object.keys(actor.links || {}).length && !conf.forceMenus) { | ||||
|       g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); | ||||
|     } | ||||
|     g.append('line') | ||||
|       .attr('id', 'actor' + actorCnt) | ||||
|       .attr('x1', center) | ||||
|       .attr('y1', centerY) | ||||
|       .attr('x2', center) | ||||
|       .attr('y2', 2000) | ||||
|       .attr('class', 'actor-line 200') | ||||
|       .attr('stroke-width', '0.5px') | ||||
|       .attr('stroke', '#999') | ||||
|       .attr('name', actor.name); | ||||
|  | ||||
|     g = boxplusLineGroup.append('g'); | ||||
|     actor.actorCnt = actorCnt; | ||||
|  | ||||
|     if (actor.links != null) { | ||||
|       g.attr('id', 'root-' + actorCnt); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const rect = svgDrawCommon.getNoteRect(); | ||||
|  | ||||
|   let cssclass = 'actor'; | ||||
|   if (actor.properties?.class) { | ||||
|     cssclass = actor.properties.class; | ||||
|   } else { | ||||
|     rect.fill = '#eaeaea'; | ||||
|   } | ||||
|  | ||||
|   if (isFooter) { | ||||
|     cssclass += ` ${BOTTOM_ACTOR_CLASS}`; | ||||
|   } else { | ||||
|     cssclass += ` ${TOP_ACTOR_CLASS}`; | ||||
|   } | ||||
|  | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   rect.width = actor.width; | ||||
|   rect.height = actor.height; | ||||
|   rect.class = cssclass; | ||||
|   rect.name = actor.name; | ||||
|  | ||||
|   // Cylinder dimensions | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   const w = rect.width / 4; | ||||
|   const h = rect.height * 0.8; | ||||
|   const rx = w / 2; | ||||
|   const ry = rx / (2.5 + w / 50); | ||||
|  | ||||
|   // Cylinder base group | ||||
|   const cylinderGroup = g.append('g'); | ||||
|  | ||||
|   const d = ` | ||||
|   M ${rect.x},${rect.y + ry} | ||||
|   a ${rx},${ry} 0 0 0 ${w},0 | ||||
|   a ${rx},${ry} 0 0 0 -${w},0 | ||||
|   l 0,${h - 2 * ry} | ||||
|   a ${rx},${ry} 0 0 0 ${w},0 | ||||
|   l 0,-${h - 2 * ry} | ||||
| `; | ||||
|   // Draw the main cylinder body | ||||
|   cylinderGroup | ||||
|     .append('path') | ||||
|     .attr('d', d) | ||||
|     .attr('fill', '#eaeaea') | ||||
|     .attr('stroke', '#000') | ||||
|     .attr('stroke-width', 1) | ||||
|     .attr('class', cssclass); | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); | ||||
|   } else { | ||||
|     cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`); | ||||
|   } | ||||
|   actor.rectData = rect; | ||||
|   _drawTextCandidateFunc(conf, hasKatex(actor.description))( | ||||
|     actor.description, | ||||
|     g, | ||||
|     rect.x, | ||||
|     rect.y + (!isFooter ? rect.height / 2 : h / 2 + ry), | ||||
|     rect.width, | ||||
|     rect.height, | ||||
|     { class: `actor ${ACTOR_BOX_CLASS}` }, | ||||
|     conf | ||||
|   ); | ||||
|  | ||||
|   const lastPath = cylinderGroup.select('path:last-child'); | ||||
|   if (lastPath.node()) { | ||||
|     const bounds = lastPath.node().getBBox(); | ||||
|     actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); | ||||
|   } | ||||
|  | ||||
|   return actor.height; | ||||
| }; | ||||
|  | ||||
| const drawActorTypeBoundary = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
|   const centerY = actorY + 80; | ||||
|   const radius = 30; | ||||
|   const line = elem.append('g').lower(); | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actorCnt++; | ||||
|     line | ||||
|       .append('line') | ||||
|       .attr('id', 'actor' + actorCnt) | ||||
|       .attr('x1', center) | ||||
|       .attr('y1', centerY) | ||||
|       .attr('x2', center) | ||||
|       .attr('y2', 2000) | ||||
|       .attr('class', 'actor-line 200') | ||||
|       .attr('stroke-width', '0.5px') | ||||
|       .attr('stroke', '#999') | ||||
|       .attr('name', actor.name); | ||||
|  | ||||
|     actor.actorCnt = actorCnt; | ||||
|   } | ||||
|   const actElem = elem.append('g'); | ||||
|   let cssClass = ACTOR_MAN_FIGURE_CLASS; | ||||
|   if (isFooter) { | ||||
|     cssClass += ` ${BOTTOM_ACTOR_CLASS}`; | ||||
|   } else { | ||||
|     cssClass += ` ${TOP_ACTOR_CLASS}`; | ||||
|   } | ||||
|   actElem.attr('class', cssClass); | ||||
|   actElem.attr('name', actor.name); | ||||
|  | ||||
|   const rect = svgDrawCommon.getNoteRect(); | ||||
|   rect.x = actor.x; | ||||
|   rect.y = actorY; | ||||
|   rect.fill = '#eaeaea'; | ||||
|   rect.width = actor.width; | ||||
|   rect.height = actor.height; | ||||
|   rect.class = 'actor'; | ||||
|   rect.rx = 3; | ||||
|   rect.ry = 3; | ||||
|  | ||||
|   actElem | ||||
|     .append('line') | ||||
|     .attr('id', 'actor-man-torso' + actorCnt) | ||||
|     .attr('x1', actor.x + actor.width / 2 - radius * 2.5) | ||||
|     .attr('y1', actorY + 10) | ||||
|     .attr('x2', actor.x + actor.width / 2 - 15) | ||||
|     .attr('y2', actorY + 10); | ||||
|  | ||||
|   actElem | ||||
|     .append('line') | ||||
|     .attr('id', 'actor-man-arms' + actorCnt) | ||||
|     .attr('x1', actor.x + actor.width / 2 - radius * 2.5) | ||||
|     .attr('y1', actorY + 0) // starting Y | ||||
|     .attr('x2', actor.x + actor.width / 2 - radius * 2.5) | ||||
|     .attr('y2', actorY + 20); // ending Y (26px long, adjust as needed) | ||||
|  | ||||
|   actElem | ||||
|     .append('circle') | ||||
|     .attr('cx', actor.x + actor.width / 2) | ||||
|     .attr('cy', actorY + 10) | ||||
|     .attr('r', radius) | ||||
|     .attr('width', actor.width); | ||||
|  | ||||
|   const bounds = actElem.node().getBBox(); | ||||
|   actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0); | ||||
|  | ||||
|   _drawTextCandidateFunc(conf, hasKatex(actor.description))( | ||||
|     actor.description, | ||||
|     actElem, | ||||
|     rect.x, | ||||
|     rect.y + (!isFooter ? radius / 2 : radius / 2), | ||||
|     rect.width, | ||||
|     rect.height, | ||||
|     { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` }, | ||||
|     conf | ||||
|   ); | ||||
|  | ||||
|   if (!isFooter) { | ||||
|     actElem.attr('transform', `translate(0,${radius / 2 + 7})`); | ||||
|   } else { | ||||
|     actElem.attr('transform', `translate(0,${radius / 2 + 7})`); | ||||
|   } | ||||
|  | ||||
|   // actElem.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`); | ||||
|  | ||||
|   return actor.height; | ||||
| }; | ||||
|  | ||||
| const drawActorTypeActor = function (elem, actor, conf, isFooter) { | ||||
|   const actorY = isFooter ? actor.stopy : actor.starty; | ||||
|   const center = actor.x + actor.width / 2; | ||||
| @@ -512,6 +1114,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) { | ||||
|       return await drawActorTypeActor(elem, actor, conf, isFooter); | ||||
|     case 'participant': | ||||
|       return await drawActorTypeParticipant(elem, actor, conf, isFooter); | ||||
|     case 'boundary': | ||||
|       return await drawActorTypeBoundary(elem, actor, conf, isFooter); | ||||
|     case 'control': | ||||
|       return await drawActorTypeControl(elem, actor, conf, isFooter); | ||||
|     case 'entity': | ||||
|       return await drawActorTypeEntity(elem, actor, conf, isFooter); | ||||
|     case 'database': | ||||
|       return await drawActorTypeDatabase(elem, actor, conf, isFooter); | ||||
|     case 'collections': | ||||
|       return await drawActorTypeCollections(elem, actor, conf, isFooter); | ||||
|     case 'queue': | ||||
|       return await drawActorTypeQueue(elem, actor, conf, isFooter); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user