From d752240efce248e36c79441448619ad4defbf97e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 28 Oct 2024 14:49:34 +0100 Subject: [PATCH 01/11] Fix a configuration example in gantt.md According to the [config schema docs](https://mermaid.js.org/config/schema-docs/config-defs-gantt-diagram-config.html#tickinterval-constraints), Gantt's `tickInterval` configuration must match the following regular expression, which does **not** allow any space: ```regexp /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/ ``` --- packages/mermaid/src/docs/syntax/gantt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md index 01a9f041d..eab35d09f 100644 --- a/packages/mermaid/src/docs/syntax/gantt.md +++ b/packages/mermaid/src/docs/syntax/gantt.md @@ -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 From 8cb1c681661edcd242ac7d61c900603ea32236a7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:59:06 +0000 Subject: [PATCH 02/11] [autofix.ci] apply automated fixes --- docs/syntax/gantt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md index cdaf0c2ac..ff6be97aa 100644 --- a/docs/syntax/gantt.md +++ b/docs/syntax/gantt.md @@ -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 From cb0a4703bdf01d47508bde1c08aa9a980d70bc20 Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Sun, 3 Nov 2024 15:53:09 -0600 Subject: [PATCH 03/11] fix(#5952): initial fix for architecture diagrams with extreme heights --- .../diagrams/architecture/architectureDb.ts | 23 ++++- .../architecture/architectureRenderer.ts | 90 +++++++++++++++---- .../architecture/architectureTypes.ts | 32 +++++++ 3 files changed, 128 insertions(+), 17 deletions(-) diff --git a/packages/mermaid/src/diagrams/architecture/architectureDb.ts b/packages/mermaid/src/diagrams/architecture/architectureDb.ts index 93fa71ca3..33d4ba309 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureDb.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureDb.ts @@ -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,7 +213,7 @@ 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 */ @@ -220,10 +222,27 @@ const getDataStructures = () => { // 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 + const groupAlignments: Record< + string, + Record> + > = {}; const adjList = Object.entries(state.records.nodes).reduce< Record >((prevOuter, [id, service]) => { prevOuter[id] = service.edges.reduce((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 +264,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 @@ -283,6 +303,7 @@ const getDataStructures = () => { state.records.dataStructures = { adjList, spatialMaps, + groupAlignments, }; } return state.records.dataStructures; diff --git a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts index af9429539..ad5fccb83 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts @@ -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,80 @@ 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 vertical alignments with one another + * + * See: #5952 + * + * @param alignmentObj - alignment object with the outer key being the row/col and the inner key being the group name + * @param alignmentDir - alignment direction + * @returns flattened alignment object with an arbitrary key mapping to nodes in the same row/col + */ + const flattenAlignments = ( + alignmentObj: Record>, + alignmentDir: ArchitectureAlignment + ): Record => { + return Object.entries(alignmentObj).reduce( + (prev, [dir, alignments]) => { + let cnt = 0; + const arr = Object.entries(alignments); + if (arr.length === 1) { + prev[dir] = arr[0][1]; + return prev; + } + for (let i = 0; i < arr.length - 1; i += 1) { + const [aGroupId, aNodeIds] = arr[i]; + const [bGroupId, bNodeIds] = arr[i + 1]; + const alignment = groupAlignments[aGroupId][bGroupId]; + if (alignment === alignmentDir) { + prev[dir] ??= []; + prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds]; + } else { + const keyA = `${dir}-${cnt++}`; + prev[keyA] = aNodeIds; + const keyB = `${dir}-${cnt++}`; + prev[keyB] = bNodeIds; + } + } + + return prev; + }, + {} as Record + ); + }; + const alignments = spatialMaps.map((spatialMap) => { - const horizontalAlignments: Record = {}; - const verticalAlignments: Record = {}; + const horizontalAlignments: Record> = {}; + const verticalAlignments: Record> = {}; + // 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 +301,8 @@ function layoutArchitecture( junctions: ArchitectureJunction[], groups: ArchitectureGroup[], edges: ArchitectureEdge[], - { spatialMaps }: ArchitectureDataStructures + db: ArchitectureDB, + { spatialMaps, groupAlignments }: ArchitectureDataStructures ): Promise { return new Promise((resolve) => { const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none'); @@ -320,7 +378,7 @@ function layoutArchitecture( 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 +512,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); diff --git a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts index b3ef55ec6..cad2c5c36 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts @@ -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; export type ArchitectureDirectionY = Extract; @@ -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; export type ArchitectureSpatialMap = Record; + +/** + * 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> +>; + export interface ArchitectureDataStructures { adjList: ArchitectureAdjacencyList; spatialMaps: ArchitectureSpatialMap[]; + groupAlignments: ArchitectureGroupAlignments; } export interface ArchitectureState extends Record { From 885ac6f947e1ef5354cf47938ab8fc4027e3cc3f Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Fri, 15 Nov 2024 10:27:20 -0600 Subject: [PATCH 04/11] fix(#5952): handled additional edge cases --- .../diagrams/architecture/architectureDb.ts | 8 ++-- .../architecture/architectureRenderer.ts | 43 ++++++++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/mermaid/src/diagrams/architecture/architectureDb.ts b/packages/mermaid/src/diagrams/architecture/architectureDb.ts index 33d4ba309..2174ebe19 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureDb.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureDb.ts @@ -219,13 +219,15 @@ const getEdges = (): ArchitectureEdge[] => state.records.edges; */ const getDataStructures = () => { if (state.records.dataStructures === undefined) { - // 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 + // Tracks how groups are aligned with one another. Generated while creating the adj list const groupAlignments: Record< string, Record> > = {}; + + // 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 const adjList = Object.entries(state.records.nodes).reduce< Record >((prevOuter, [id, service]) => { diff --git a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts index ad5fccb83..5813b6ea5 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts @@ -159,11 +159,11 @@ function getAlignments( /** * 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 vertical alignments with one another + * 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 + * @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 */ @@ -173,24 +173,36 @@ function getAlignments( ): Record => { 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); + 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 += 1) { - const [aGroupId, aNodeIds] = arr[i]; - const [bGroupId, bNodeIds] = arr[i + 1]; - const alignment = groupAlignments[aGroupId][bGroupId]; - if (alignment === alignmentDir) { - prev[dir] ??= []; - prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds]; - } else { - const keyA = `${dir}-${cnt++}`; - prev[keyA] = aNodeIds; - const keyB = `${dir}-${cnt++}`; - prev[keyB] = bNodeIds; + for (let i = 0; i < arr.length - 1; i++) { + for (let j = i + 1; j < arr.length; j++) { + // Not optimal but arr will not grow large enough to be a concern + 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; + } } } @@ -376,7 +388,6 @@ function layoutArchitecture( addServices(services, cy); addJunctions(junctions, cy); addEdges(edges, cy); - // Use the spatial map to create alignment arrays for fcose const alignmentConstraint = getAlignments(db, spatialMaps, groupAlignments); From 570ae78b156c18ec102786533da66b6a3990fbfa Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Fri, 15 Nov 2024 10:28:50 -0600 Subject: [PATCH 05/11] cicd(#5952): added test case for diagram that instigated the issue --- .../rendering/architecture.spec.ts | 52 +++++++++++++++++++ .../architecture/architectureRenderer.ts | 1 - 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/cypress/integration/rendering/architecture.spec.ts b/cypress/integration/rendering/architecture.spec.ts index bc2276352..25326ff80 100644 --- a/cypress/integration/rendering/architecture.spec.ts +++ b/cypress/integration/rendering/architecture.spec.ts @@ -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. diff --git a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts index 5813b6ea5..768c174b0 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureRenderer.ts @@ -183,7 +183,6 @@ function getAlignments( } for (let i = 0; i < arr.length - 1; i++) { for (let j = i + 1; j < arr.length; j++) { - // Not optimal but arr will not grow large enough to be a concern 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) From 2a91849a38641e97ed6b20cb60aa4506d1b63177 Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Sat, 16 Nov 2024 10:03:13 -0600 Subject: [PATCH 06/11] chore(#5952): changeset --- .changeset/angry-bags-brake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/angry-bags-brake.md diff --git a/.changeset/angry-bags-brake.md b/.changeset/angry-bags-brake.md new file mode 100644 index 000000000..472e486ec --- /dev/null +++ b/.changeset/angry-bags-brake.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: architecture diagrams no longer grow to extreme heights due to conflicting alignments From 9b00f1f2fb6843a5ff94b13974bcb646640a3b4d Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Tue, 17 Dec 2024 14:56:18 +0100 Subject: [PATCH 07/11] #5574 Adding support for edge ids and animations --- cypress/platform/knsv2.html | 81 +++++++++++----- .../mermaid/src/diagrams/flowchart/flowDb.ts | 96 ++++++++++++++----- .../flowchart/parser/flow-edges.spec.js | 89 +++++++++++++++++ .../src/diagrams/flowchart/parser/flow.jison | 11 ++- .../mermaid/src/diagrams/flowchart/types.ts | 4 + .../rendering-elements/edges.js | 28 +++++- .../shapes/handDrawnShapeStyles.ts | 44 +++++---- packages/mermaid/src/rendering-util/types.ts | 1 + packages/mermaid/src/styles.ts | 23 ++++- packages/mermaid/src/types.ts | 5 + packages/mermaid/src/utils.ts | 6 +- 11 files changed, 314 insertions(+), 74 deletions(-) diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 1c7bda8e7..dfcd8aa4f 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -84,29 +84,66 @@ /* tspan { font-size: 6px !important; } */ + /* .flowchart-link { + stroke-dasharray: 4, 4 !important; + animation: flow 1s linear infinite; + animation: dashdraw 4.93282s linear infinite; + stroke-width: 2px !important; + } */ + + @keyframes dashdraw { + from { + stroke-dashoffset: 0; + } + } + + /*stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: 4.932820s linear infinite;*/ + /* stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: dashdraw 4.932820s linear infinite;*/ -
----
-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
+        A --> B
     
+      flowchart LR
+        A e1@==> B
+        e1@{ animate: true}
+    
+
+flowchart LR
+  A e1@--> B
+  classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
+  class e1 animate
+    
+

infinite

+
+flowchart LR
+  A e1@--> B
+  classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+

Mermaid - edge-animation-slow

+
+flowchart LR
+  A e1@--> B
+e1@{ animation: fast}
+    
+

Mermaid - edge-animation-fast

+
+flowchart LR
+  A e1@--> B
+  classDef animate stroke-dasharray: 1000,stroke-dashoffset: 1000,animation: dash 10s linear;
+  class e1 edge-animation-fast
+    
+ +
+
+info    
+
 ---
 config:
   layout: elk
@@ -131,7 +168,7 @@ config:
       end
       end
     
-
+    
 ---
 config:
   layout: elk
@@ -144,7 +181,7 @@ config:
       D-->I
       D-->I
     
-
+    
 ---
 config:
   layout: elk
@@ -183,7 +220,7 @@ flowchart LR
     n8@{ shape: rect}
 
     
-
+    
 ---
 config:
   layout: elk
@@ -199,7 +236,7 @@ flowchart LR
 
 
     
-
+    
 ---
 config:
   layout: elk
@@ -208,7 +245,7 @@ flowchart LR
     A{A} --> B & C
 
-
+    
 ---
 config:
   layout: elk
@@ -220,7 +257,7 @@ flowchart LR
     end
 
-
+    
 ---
 config:
   layout: elk
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index 1dbc789c9..ccb8a8e94 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -24,7 +24,7 @@ import type {
   FlowLink,
   FlowVertexTypeParam,
 } from './types.js';
-import type { NodeMetaData } from '../../types.js';
+import type { NodeMetaData, EdgeMetaData } from '../../types.js';
 
 const MERMAID_DOM_ID_PREFIX = 'flowchart-';
 let vertexCounter = 0;
@@ -71,12 +71,38 @@ export const addVertex = function (
   classes: string[],
   dir: string,
   props = {},
-  shapeData: any
+  metadata: any
 ) {
-  // console.log('addVertex', id, shapeData);
   if (!id || id.trim().length === 0) {
     return;
   }
+  // Extract the metadata from the shapeData, the syntax for adding metadata for nodes and edges is the same
+  // so at this point we don't know if it's a node or an edge, but we can still extract the metadata
+  let doc;
+  if (metadata !== undefined) {
+    let yamlData;
+    // detect if shapeData contains a newline character
+    if (!metadata.includes('\n')) {
+      yamlData = '{\n' + metadata + '\n}';
+    } else {
+      yamlData = metadata + '\n';
+    }
+    doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
+  }
+
+  // Check if this is an edge
+  const edge = edges.find((e) => e.id === id);
+  if (edge) {
+    const edgeDoc = doc as EdgeMetaData;
+    if (edgeDoc?.animate) {
+      edge.animate = edgeDoc.animate;
+    }
+    if (edgeDoc?.animation) {
+      edge.animation = edgeDoc.animation;
+    }
+    return;
+  }
+
   let txt;
 
   let vertex = vertices.get(id);
@@ -128,19 +154,7 @@ export const addVertex = function (
     Object.assign(vertex.props, props);
   }
 
-  if (shapeData !== undefined) {
-    let yamlData;
-    // detect if shapeData contains a newline character
-    // console.log('shapeData', shapeData);
-    if (!shapeData.includes('\n')) {
-      // console.log('yamlData shapeData has no new lines', shapeData);
-      yamlData = '{\n' + shapeData + '\n}';
-    } else {
-      // console.log('yamlData shapeData has new lines', shapeData);
-      yamlData = shapeData + '\n';
-    }
-    // console.log('yamlData', yamlData);
-    const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
+  if (doc !== undefined) {
     if (doc.shape) {
       if (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_')) {
         throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
@@ -187,11 +201,18 @@ export const addVertex = function (
  * Function called by parser when a link/edge definition has been found
  *
  */
-export const addSingleLink = function (_start: string, _end: string, type: any) {
+export const addSingleLink = function (_start: string, _end: string, type: any, id?: string) {
   const start = _start;
   const end = _end;
 
-  const edge: FlowEdge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
+  const edge: FlowEdge = {
+    start: start,
+    end: end,
+    type: undefined,
+    text: '',
+    labelType: 'text',
+    classes: [],
+  };
   log.info('abc78 Got edge...', edge);
   const linkTextObj = type.text;
 
@@ -210,6 +231,9 @@ export const addSingleLink = function (_start: string, _end: string, type: any)
     edge.stroke = type.stroke;
     edge.length = type.length > 10 ? 10 : type.length;
   }
+  if (id) {
+    edge.id = id;
+  }
 
   if (edges.length < (config.maxEdges ?? 500)) {
     log.info('Pushing edge...');
@@ -225,11 +249,17 @@ You have to call mermaid.initialize.`
   }
 };
 
-export const addLink = function (_start: string[], _end: string[], type: unknown) {
-  log.info('addLink', _start, _end, type);
+export const addLink = function (_start: string[], _end: string[], linkData: unknown) {
+  const id =
+    linkData && typeof linkData === 'object' && 'id' in linkData
+      ? linkData.id?.replace('@', '')
+      : undefined;
+
+  log.info('addLink', _start, _end, id);
+
   for (const start of _start) {
     for (const end of _end) {
-      addSingleLink(start, end, type);
+      addSingleLink(start, end, linkData, id);
     }
   }
 };
@@ -282,7 +312,13 @@ export const updateLink = function (positions: ('default' | number)[], style: st
   });
 };
 
-export const addClass = function (ids: string, style: string[]) {
+export const addClass = function (ids: string, _style: string[]) {
+  const style = _style
+    .join()
+    .replace(/\\,/g, '§§§')
+    .replace(/,/g, ';')
+    .replace(/§§§/g, ',')
+    .split(';');
   ids.split(',').forEach(function (id) {
     let classNode = classes.get(id);
     if (classNode === undefined) {
@@ -337,6 +373,10 @@ export const setClass = function (ids: string, className: string) {
     if (vertex) {
       vertex.classes.push(className);
     }
+    const edge = edges.find((e) => e.id === id);
+    if (edge) {
+      edge.classes.push(className);
+    }
     const subGraph = subGraphLookup.get(id);
     if (subGraph) {
       subGraph.classes.push(className);
@@ -997,7 +1037,7 @@ export const getData = () => {
       styles.push(...rawEdge.style);
     }
     const edge: Edge = {
-      id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }),
+      id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }, rawEdge.id),
       start: rawEdge.start,
       end: rawEdge.end,
       type: rawEdge.type ?? 'normal',
@@ -1009,14 +1049,20 @@ export const getData = () => {
         rawEdge?.stroke === 'invisible'
           ? ''
           : 'edge-thickness-normal edge-pattern-solid flowchart-link',
-      arrowTypeStart: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeStart,
-      arrowTypeEnd: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeEnd,
+      arrowTypeStart:
+        rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeStart,
+      arrowTypeEnd:
+        rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeEnd,
       arrowheadStyle: 'fill: #333',
+      cssCompiledStyles: getCompiledStyles(rawEdge.classes),
       labelStyle: styles,
       style: styles,
       pattern: rawEdge.stroke,
       look: config.look,
+      animate: rawEdge.animate,
+      animation: rawEdge.animation,
     };
+
     edges.push(edge);
   });
 
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
index 4ae289bad..5682c9bed 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
@@ -39,6 +39,27 @@ const doubleEndedEdges = [
   { edgeStart: '<==', edgeEnd: '==>', stroke: 'thick', type: 'double_arrow_point' },
   { edgeStart: '<-.', edgeEnd: '.->', stroke: 'dotted', type: 'double_arrow_point' },
 ];
+const regularEdges = [
+  { edgeStart: '--', edgeEnd: '--x', stroke: 'normal', type: 'arrow_cross' },
+  { edgeStart: '==', edgeEnd: '==x', stroke: 'thick', type: 'arrow_cross' },
+  { edgeStart: '-.', edgeEnd: '.-x', stroke: 'dotted', type: 'arrow_cross' },
+  { edgeStart: '--', edgeEnd: '--o', stroke: 'normal', type: 'arrow_circle' },
+  { edgeStart: '==', edgeEnd: '==o', stroke: 'thick', type: 'arrow_circle' },
+  { edgeStart: '-.', edgeEnd: '.-o', stroke: 'dotted', type: 'arrow_circle' },
+  { edgeStart: '--', edgeEnd: '-->', stroke: 'normal', type: 'arrow_point' },
+  { edgeStart: '==', edgeEnd: '==>', stroke: 'thick', type: 'arrow_point' },
+  { edgeStart: '-.', edgeEnd: '.->', stroke: 'dotted', type: 'arrow_point' },
+
+  { edgeStart: '--', edgeEnd: '----x', stroke: 'normal', type: 'arrow_cross' },
+  { edgeStart: '==', edgeEnd: '====x', stroke: 'thick', type: 'arrow_cross' },
+  { edgeStart: '-.', edgeEnd: '...-x', stroke: 'dotted', type: 'arrow_cross' },
+  { edgeStart: '--', edgeEnd: '----o', stroke: 'normal', type: 'arrow_circle' },
+  { edgeStart: '==', edgeEnd: '====o', stroke: 'thick', type: 'arrow_circle' },
+  { edgeStart: '-.', edgeEnd: '...-o', stroke: 'dotted', type: 'arrow_circle' },
+  { edgeStart: '--', edgeEnd: '---->', stroke: 'normal', type: 'arrow_point' },
+  { edgeStart: '==', edgeEnd: '====>', stroke: 'thick', type: 'arrow_point' },
+  { edgeStart: '-.', edgeEnd: '...->', stroke: 'dotted', type: 'arrow_point' },
+];
 
 describe('[Edges] when parsing', () => {
   beforeEach(function () {
@@ -67,6 +88,74 @@ describe('[Edges] when parsing', () => {
     expect(edges[0].type).toBe('arrow_circle');
   });
 
+  describe('edges with ids', function () {
+    describe('open ended edges with ids and labels', function () {
+      regularEdges.forEach((edgeType) => {
+        it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
+          const res = flow.parser.parse(
+            `flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
+          );
+          const vert = flow.parser.yy.getVertices();
+          const edges = flow.parser.yy.getEdges();
+          expect(vert.get('A').id).toBe('A');
+          expect(vert.get('B').id).toBe('B');
+          expect(edges.length).toBe(1);
+          expect(edges[0].id).toBe('e1');
+          expect(edges[0].start).toBe('A');
+          expect(edges[0].end).toBe('B');
+          expect(edges[0].type).toBe(`${edgeType.type}`);
+          expect(edges[0].text).toBe('');
+          expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
+        });
+        it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
+          const res = flow.parser.parse(
+            `flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
+          );
+          const vert = flow.parser.yy.getVertices();
+          const edges = flow.parser.yy.getEdges();
+          expect(vert.get('A').id).toBe('A');
+          expect(vert.get('B').id).toBe('B');
+          expect(edges.length).toBe(1);
+          expect(edges[0].id).toBe('e1');
+          expect(edges[0].start).toBe('A');
+          expect(edges[0].end).toBe('B');
+          expect(edges[0].type).toBe(`${edgeType.type}`);
+          expect(edges[0].text).toBe('');
+          expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
+        });
+      });
+      it('should handle normal edges where you also have a node with metadata', function () {
+        const res = flow.parser.parse(`flowchart LR
+A id1@-->B
+A@{ shape: 'rect' }
+`);
+        const edges = flow.parser.yy.getEdges();
+
+        expect(edges[0].id).toBe('id1');
+      });
+    });
+    describe('double ended edges with ids and labels', function () {
+      doubleEndedEdges.forEach((edgeType) => {
+        it(`should handle ${edgeType.stroke} ${edgeType.type} with  text`, function () {
+          const res = flow.parser.parse(
+            `flowchart TD;\nA e1@${edgeType.edgeStart} label ${edgeType.edgeEnd} B;`
+          );
+          const vert = flow.parser.yy.getVertices();
+          const edges = flow.parser.yy.getEdges();
+          expect(vert.get('A').id).toBe('A');
+          expect(vert.get('B').id).toBe('B');
+          expect(edges.length).toBe(1);
+          expect(edges[0].id).toBe('e1');
+          expect(edges[0].start).toBe('A');
+          expect(edges[0].end).toBe('B');
+          expect(edges[0].type).toBe(`${edgeType.type}`);
+          expect(edges[0].text).toBe('label');
+          expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
+        });
+      });
+    });
+  });
+
   describe('edges', function () {
     doubleEndedEdges.forEach((edgeType) => {
       it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index b3df82fa5..fbd30fa9e 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -141,6 +141,7 @@ that id.
 .*direction\s+RL[^\n]*       return 'direction_rl';
 .*direction\s+LR[^\n]*       return 'direction_lr';
 
+[^\s]+\@(?=[^\{])               { return 'LINK_ID'; }
 [0-9]+                       return 'NUM';
 \#                           return 'BRKT';
 ":::"                        return 'STYLE_SEPARATOR';
@@ -201,7 +202,9 @@ that id.
 "*"                   return 'MULT';
 "#"                   return 'BRKT';
 "&"                   return 'AMP';
-([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+  return 'NODE_STRING';
+([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+  {
+    return 'NODE_STRING';
+}
 "-"                   return 'MINUS'
 [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
 [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
@@ -361,7 +364,7 @@ spaceList
 
 statement
     : vertexStatement separator
-    { /* console.warn('finat vs', $vertexStatement.nodes); */ $$=$vertexStatement.nodes}
+    { $$=$vertexStatement.nodes}
     | styleStatement separator
     {$$=[];}
     | linkStyleStatement separator
@@ -472,6 +475,8 @@ link: linkStatement arrowText
     {$$ = $linkStatement;}
     | START_LINK edgeText LINK
         {var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText};}
+    | LINK_ID START_LINK edgeText LINK
+        {var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText, "id": $LINK_ID};}
     ;
 
 edgeText: edgeTextToken
@@ -487,6 +492,8 @@ edgeText: edgeTextToken
 
 linkStatement: LINK
         {var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length};}
+    | LINK_ID LINK
+        {var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length, "id": $LINK_ID};}
         ;
 
 arrowText:
diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts
index b2c5cf620..00acb6751 100644
--- a/packages/mermaid/src/diagrams/flowchart/types.ts
+++ b/packages/mermaid/src/diagrams/flowchart/types.ts
@@ -62,6 +62,10 @@ export interface FlowEdge {
   length?: number;
   text: string;
   labelType: 'text';
+  classes: string[];
+  id?: string;
+  animation?: 'fast' | 'slow';
+  animate?: boolean;
 }
 
 export interface FlowClass {
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
index a6a7a55f7..2581d342f 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
@@ -9,6 +9,7 @@ import { curveBasis, line, select } from 'd3';
 import rough from 'roughjs';
 import createLabel from './createLabel.js';
 import { addEdgeMarkers } from './edgeMarker.ts';
+import { isLabelStyle } from './shapes/handDrawnShapeStyles.js';
 
 const edgeLabels = new Map();
 const terminalLabels = new Map();
@@ -429,6 +430,14 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
   const tail = startNode;
   var head = endNode;
 
+  const edgeClassStyles = [];
+  for (const key in edge.cssCompiledStyles) {
+    if (isLabelStyle(key)) {
+      continue;
+    }
+    edgeClassStyles.push(edge.cssCompiledStyles[key]);
+  }
+
   if (head.intersect && tail.intersect) {
     points = points.slice(1, edge.points.length - 1);
     points.unshift(tail.intersect(points[0]));
@@ -521,12 +530,27 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
     svgPath.attr('d', d);
     elem.node().appendChild(svgPath.node());
   } else {
+    const stylesFromClasses = edgeClassStyles.join(';');
+    const styles = edge.edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '';
+    let animationClass = '';
+    if (edge.animate) {
+      animationClass = ' edge-animation-fast';
+    }
+    if (edge.animation) {
+      animationClass = ' edge-animation-' + edge.animation;
+    }
     svgPath = elem
       .append('path')
       .attr('d', linePath)
       .attr('id', edge.id)
-      .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
-      .attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
+      .attr(
+        'class',
+        ' ' +
+          strokeClasses +
+          (edge.classes ? ' ' + edge.classes : '') +
+          (animationClass ? animationClass : '')
+      )
+      .attr('style', stylesFromClasses + ';' + styles);
   }
 
   // DEBUG code, DO NOT REMOVE
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts
index 80e2a4423..4ac6b2ddd 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.ts
@@ -32,7 +32,28 @@ export const styles2Map = (styles: string[]) => {
   });
   return styleMap;
 };
-
+export const isLabelStyle = (key: string) => {
+  return (
+    key === 'color' ||
+    key === 'font-size' ||
+    key === 'font-family' ||
+    key === 'font-weight' ||
+    key === 'font-style' ||
+    key === 'text-decoration' ||
+    key === 'text-align' ||
+    key === 'text-transform' ||
+    key === 'line-height' ||
+    key === 'letter-spacing' ||
+    key === 'word-spacing' ||
+    key === 'text-shadow' ||
+    key === 'text-overflow' ||
+    key === 'white-space' ||
+    key === 'word-wrap' ||
+    key === 'word-break' ||
+    key === 'overflow-wrap' ||
+    key === 'hyphens'
+  );
+};
 export const styles2String = (node: Node) => {
   const { stylesArray } = compileStyles(node);
   const labelStyles: string[] = [];
@@ -42,26 +63,7 @@ export const styles2String = (node: Node) => {
 
   stylesArray.forEach((style) => {
     const key = style[0];
-    if (
-      key === 'color' ||
-      key === 'font-size' ||
-      key === 'font-family' ||
-      key === 'font-weight' ||
-      key === 'font-style' ||
-      key === 'text-decoration' ||
-      key === 'text-align' ||
-      key === 'text-transform' ||
-      key === 'line-height' ||
-      key === 'letter-spacing' ||
-      key === 'word-spacing' ||
-      key === 'text-shadow' ||
-      key === 'text-overflow' ||
-      key === 'white-space' ||
-      key === 'word-wrap' ||
-      key === 'word-break' ||
-      key === 'overflow-wrap' ||
-      key === 'hyphens'
-    ) {
+    if (isLabelStyle(key)) {
       labelStyles.push(style.join(':') + ' !important');
     } else {
       nodeStyles.push(style.join(':') + ' !important');
diff --git a/packages/mermaid/src/rendering-util/types.ts b/packages/mermaid/src/rendering-util/types.ts
index 86cfd50b3..d64594218 100644
--- a/packages/mermaid/src/rendering-util/types.ts
+++ b/packages/mermaid/src/rendering-util/types.ts
@@ -101,6 +101,7 @@ export interface Edge {
   arrowheadStyle?: string;
   arrowTypeEnd?: string;
   arrowTypeStart?: string;
+  cssCompiledStyles?: string[];
   // Flowchart specific properties
   defaultInterpolate?: string;
   end?: string;
diff --git a/packages/mermaid/src/styles.ts b/packages/mermaid/src/styles.ts
index 78b514c40..2cb11f146 100644
--- a/packages/mermaid/src/styles.ts
+++ b/packages/mermaid/src/styles.ts
@@ -27,7 +27,28 @@ const getStyles = (
     font-size: ${options.fontSize};
     fill: ${options.textColor}
   }
-
+  @keyframes edge-animation-frame {
+    from {
+      stroke-dashoffset: 0;
+    }
+  }
+  @keyframes dash {
+    to {
+      stroke-dashoffset: 0;
+    }
+  }
+  & .edge-animation-slow {
+    stroke-dasharray: 9,5 !important;
+    stroke-dashoffset: 900;
+    animation: dash 50s linear infinite;
+    stroke-linecap: round;
+  }
+  & .edge-animation-fast {
+    stroke-dasharray: 9,5 !important;
+    stroke-dashoffset: 900;
+    animation: dash 20s linear infinite;
+    stroke-linecap: round;
+  }
   /* Classes common for multiple diagrams */
 
   & .error-icon {
diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts
index 5587ca3f4..fdccae677 100644
--- a/packages/mermaid/src/types.ts
+++ b/packages/mermaid/src/types.ts
@@ -12,6 +12,11 @@ export interface NodeMetaData {
   assigned?: string;
   ticket?: string;
 }
+
+export interface EdgeMetaData {
+  animation?: 'fast' | 'slow';
+  animate?: boolean;
+}
 import type { MermaidConfig } from './config.type.js';
 
 export interface Point {
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index c1d674834..68b5e2889 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -937,8 +937,12 @@ export const getEdgeId = (
     counter?: number;
     prefix?: string;
     suffix?: string;
-  }
+  },
+  id?: string
 ) => {
+  if (id) {
+    return id;
+  }
   return `${prefix ? `${prefix}_` : ''}${from}_${to}_${counter}${suffix ? `_${suffix}` : ''}`;
 };
 

From c153d0455fdc89abd49f61d117a64aac1f3748b0 Mon Sep 17 00:00:00 2001
From: Knut Sveidqvist 
Date: Tue, 17 Dec 2024 16:28:38 +0100
Subject: [PATCH 08/11] #5574 Fixed issue linkStyles

---
 packages/mermaid/src/diagrams/flowchart/flowDb.ts           | 3 ---
 .../mermaid/src/rendering-util/rendering-elements/edges.js  | 6 +++---
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index ccb8a8e94..ffe46d398 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -297,9 +297,6 @@ export const updateLink = function (positions: ('default' | number)[], style: st
     if (pos === 'default') {
       edges.defaultStyle = style;
     } else {
-      // if (utils.isSubstringInArray('fill', style) === -1) {
-      //   style.push('fill:none');
-      // }
       edges[pos].style = style;
       // if edges[pos].style does have fill not set, set it to none
       if (
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
index 2581d342f..649686c0c 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
@@ -429,7 +429,6 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
   let pointsHasChanged = false;
   const tail = startNode;
   var head = endNode;
-
   const edgeClassStyles = [];
   for (const key in edge.cssCompiledStyles) {
     if (isLabelStyle(key)) {
@@ -510,6 +509,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
   let svgPath;
   let linePath = lineFunction(lineData);
   const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
+
   if (edge.look === 'handDrawn') {
     const rc = rough.svg(elem);
     Object.assign([], lineData);
@@ -531,7 +531,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
     elem.node().appendChild(svgPath.node());
   } else {
     const stylesFromClasses = edgeClassStyles.join(';');
-    const styles = edge.edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '';
+    const styles = edgeStyles ? edgeStyles.reduce((acc, style) => acc + style + ';', '') : '';
     let animationClass = '';
     if (edge.animate) {
       animationClass = ' edge-animation-fast';
@@ -550,7 +550,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
           (edge.classes ? ' ' + edge.classes : '') +
           (animationClass ? animationClass : '')
       )
-      .attr('style', stylesFromClasses + ';' + styles);
+      .attr('style', stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles);
   }
 
   // DEBUG code, DO NOT REMOVE

From 323b07a2e4255b8339a29a919634ce2e7b7322bf Mon Sep 17 00:00:00 2001
From: Knut Sveidqvist 
Date: Wed, 18 Dec 2024 11:56:48 +0100
Subject: [PATCH 09/11] Typescript fix and updating documentation

---
 .../setup/interfaces/mermaid.LayoutData.md    |  6 +-
 .../setup/interfaces/mermaid.ParseOptions.md  |  2 +-
 .../setup/interfaces/mermaid.ParseResult.md   |  4 +-
 .../setup/interfaces/mermaid.RenderResult.md  |  6 +-
 docs/syntax/flowchart.md                      | 85 +++++++++++++++++++
 .../mermaid/src/diagrams/flowchart/flowDb.ts  | 18 +++-
 packages/mermaid/src/docs/syntax/flowchart.md | 61 +++++++++++++
 packages/mermaid/src/rendering-util/types.ts  |  2 +
 8 files changed, 171 insertions(+), 13 deletions(-)

diff --git a/docs/config/setup/interfaces/mermaid.LayoutData.md b/docs/config/setup/interfaces/mermaid.LayoutData.md
index 5616e1c9a..552a16a8d 100644
--- a/docs/config/setup/interfaces/mermaid.LayoutData.md
+++ b/docs/config/setup/interfaces/mermaid.LayoutData.md
@@ -20,7 +20,7 @@
 
 #### Defined in
 
-[packages/mermaid/src/rendering-util/types.ts:144](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L144)
+[packages/mermaid/src/rendering-util/types.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L147)
 
 ---
 
@@ -30,7 +30,7 @@
 
 #### Defined in
 
-[packages/mermaid/src/rendering-util/types.ts:143](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L143)
+[packages/mermaid/src/rendering-util/types.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L146)
 
 ---
 
@@ -40,4 +40,4 @@
 
 #### Defined in
 
-[packages/mermaid/src/rendering-util/types.ts:142](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L142)
+[packages/mermaid/src/rendering-util/types.ts:145](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L145)
diff --git a/docs/config/setup/interfaces/mermaid.ParseOptions.md b/docs/config/setup/interfaces/mermaid.ParseOptions.md
index 717e35565..bac54b8ca 100644
--- a/docs/config/setup/interfaces/mermaid.ParseOptions.md
+++ b/docs/config/setup/interfaces/mermaid.ParseOptions.md
@@ -19,4 +19,4 @@ The `parseError` function will not be called.
 
 #### Defined in
 
-[packages/mermaid/src/types.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L59)
+[packages/mermaid/src/types.ts:64](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L64)
diff --git a/docs/config/setup/interfaces/mermaid.ParseResult.md b/docs/config/setup/interfaces/mermaid.ParseResult.md
index 9f90b6dd4..e2eb5df50 100644
--- a/docs/config/setup/interfaces/mermaid.ParseResult.md
+++ b/docs/config/setup/interfaces/mermaid.ParseResult.md
@@ -18,7 +18,7 @@ The config passed as YAML frontmatter or directives
 
 #### Defined in
 
-[packages/mermaid/src/types.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L70)
+[packages/mermaid/src/types.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L75)
 
 ---
 
@@ -30,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
 
 #### Defined in
 
-[packages/mermaid/src/types.ts:66](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L66)
+[packages/mermaid/src/types.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L71)
diff --git a/docs/config/setup/interfaces/mermaid.RenderResult.md b/docs/config/setup/interfaces/mermaid.RenderResult.md
index f882b7af4..cce7f6928 100644
--- a/docs/config/setup/interfaces/mermaid.RenderResult.md
+++ b/docs/config/setup/interfaces/mermaid.RenderResult.md
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
 
 #### Defined in
 
-[packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
+[packages/mermaid/src/types.ts:103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L103)
 
 ---
 
@@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
 
 #### Defined in
 
-[packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
+[packages/mermaid/src/types.ts:93](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L93)
 
 ---
 
@@ -63,4 +63,4 @@ The svg code for the rendered graph.
 
 #### Defined in
 
-[packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
+[packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89)
diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md
index 3837e77de..fefa12e02 100644
--- a/docs/syntax/flowchart.md
+++ b/docs/syntax/flowchart.md
@@ -1183,6 +1183,91 @@ flowchart TB
     B --> D
 ```
 
+### Attaching an ID to Edges
+
+Mermaid now supports assigning IDs to edges, similar to how IDs and metadata can be attached to nodes. This feature lays the groundwork for more advanced styling, classes, and animation capabilities on edges.
+
+**Syntax:**
+
+To give an edge an ID, prepend the edge syntax with the ID followed by an `@` character. For example:
+
+```mermaid-example
+flowchart LR
+  A e1@–> B
+```
+
+```mermaid
+flowchart LR
+  A e1@–> B
+```
+
+In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
+
+### Turning an Animation On
+
+Once you have assigned an ID to an edge, you can turn on animations for that edge by defining the edge’s properties:
+
+```mermaid-example
+flowchart LR
+  A e1@==> B
+  e1@{ animate: true }
+```
+
+```mermaid
+flowchart LR
+  A e1@==> B
+  e1@{ animate: true }
+```
+
+This tells Mermaid that the edge `e1` should be animated.
+
+### Selecting Type of Animation
+
+In the initial version, two animation speeds are supported: `fast` and `slow`. Selecting a specific animation type is a shorthand for enabling animation and setting the animation speed in one go.
+
+**Examples:**
+
+```mermaid-example
+flowchart LR
+  A e1@–> B
+  e1@{ animation: fast }
+```
+
+```mermaid
+flowchart LR
+  A e1@–> B
+  e1@{ animation: fast }
+```
+
+This is equivalent to `{ animate: true, animation: fast }`.
+
+### Using classDef Statements for Animations
+
+You can also animate edges by assigning a class to them and then defining animation properties in a `classDef` statement. For example:
+
+```mermaid-example
+flowchart LR
+  A e1@–> B
+  classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+```
+
+```mermaid
+flowchart LR
+  A e1@–> B
+  classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+```
+
+In this snippet:
+
+- `e1@-->` creates an edge with ID `e1`.
+- `classDef animate` defines a class named `animate` with styling and animation properties.
+- `class e1 animate` applies the `animate` class to the edge `e1`.
+
+**Note on Escaping Commas:**
+When setting the `stroke-dasharray` property, remember to escape commas as `\,` since commas are used as delimiters in Mermaid’s style definitions.
+
 ## New arrow types
 
 There are new types of arrows supported:
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index ffe46d398..931347a4d 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -249,11 +249,21 @@ You have to call mermaid.initialize.`
   }
 };
 
+interface LinkData {
+  id: string;
+}
+
+function isLinkData(value: unknown): value is LinkData {
+  return (
+    value !== null &&
+    typeof value === 'object' &&
+    'id' in value &&
+    typeof (value as LinkData).id === 'string'
+  );
+}
+
 export const addLink = function (_start: string[], _end: string[], linkData: unknown) {
-  const id =
-    linkData && typeof linkData === 'object' && 'id' in linkData
-      ? linkData.id?.replace('@', '')
-      : undefined;
+  const id = isLinkData(linkData) ? linkData.id.replace('@', '') : undefined;
 
   log.info('addLink', _start, _end, id);
 
diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md
index 829b71c2d..6c9db197a 100644
--- a/packages/mermaid/src/docs/syntax/flowchart.md
+++ b/packages/mermaid/src/docs/syntax/flowchart.md
@@ -711,6 +711,67 @@ flowchart TB
     B --> D
 ```
 
+### Attaching an ID to Edges
+
+Mermaid now supports assigning IDs to edges, similar to how IDs and metadata can be attached to nodes. This feature lays the groundwork for more advanced styling, classes, and animation capabilities on edges.
+
+**Syntax:**
+
+To give an edge an ID, prepend the edge syntax with the ID followed by an `@` character. For example:
+
+```mermaid
+flowchart LR
+  A e1@–> B
+```
+
+In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
+
+### Turning an Animation On
+
+Once you have assigned an ID to an edge, you can turn on animations for that edge by defining the edge’s properties:
+
+```mermaid
+flowchart LR
+  A e1@==> B
+  e1@{ animate: true }
+```
+
+This tells Mermaid that the edge `e1` should be animated.
+
+### Selecting Type of Animation
+
+In the initial version, two animation speeds are supported: `fast` and `slow`. Selecting a specific animation type is a shorthand for enabling animation and setting the animation speed in one go.
+
+**Examples:**
+
+```mermaid
+flowchart LR
+  A e1@–> B
+  e1@{ animation: fast }
+```
+
+This is equivalent to `{ animate: true, animation: fast }`.
+
+### Using classDef Statements for Animations
+
+You can also animate edges by assigning a class to them and then defining animation properties in a `classDef` statement. For example:
+
+```mermaid
+flowchart LR
+  A e1@–> B
+  classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+```
+
+In this snippet:
+
+- `e1@-->` creates an edge with ID `e1`.
+- `classDef animate` defines a class named `animate` with styling and animation properties.
+- `class e1 animate` applies the `animate` class to the edge `e1`.
+
+**Note on Escaping Commas:**
+When setting the `stroke-dasharray` property, remember to escape commas as `\,` since commas are used as delimiters in Mermaid’s style definitions.
+
 ## New arrow types
 
 There are new types of arrows supported:
diff --git a/packages/mermaid/src/rendering-util/types.ts b/packages/mermaid/src/rendering-util/types.ts
index d64594218..1f84c66c3 100644
--- a/packages/mermaid/src/rendering-util/types.ts
+++ b/packages/mermaid/src/rendering-util/types.ts
@@ -96,6 +96,8 @@ export interface Edge {
   label?: string;
   classes?: string;
   style?: string[];
+  animate?: boolean;
+  animation?: 'fast' | 'slow';
   // Properties common to both Flowchart and State Diagram edges
   arrowhead?: string;
   arrowheadStyle?: string;

From ec0d9c389aa6018043187654044c1e0b5aa4f600 Mon Sep 17 00:00:00 2001
From: Knut Sveidqvist 
Date: Wed, 18 Dec 2024 11:59:39 +0100
Subject: [PATCH 10/11] Adding changeset

---
 .changeset/many-brooms-promise.md | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .changeset/many-brooms-promise.md

diff --git a/.changeset/many-brooms-promise.md b/.changeset/many-brooms-promise.md
new file mode 100644
index 000000000..fec442b34
--- /dev/null
+++ b/.changeset/many-brooms-promise.md
@@ -0,0 +1,5 @@
+---
+'mermaid': minor
+---
+
+Adding support for animation of flowchart edges

From 7809b5a93fae127f45727071f5ff14325222c518 Mon Sep 17 00:00:00 2001
From: Ashish Jain 
Date: Tue, 14 Jan 2025 14:07:25 +0100
Subject: [PATCH 11/11] #6186 Fixes the flowchaart node metadata syntax bugs

---
 .changeset/great-ghosts-rule.md               |  8 ++++++
 .../rendering/flowchart-v2.spec.js            | 28 +++++++++++++++++++
 .../src/diagrams/flowchart/flowDiagram.ts     |  3 +-
 .../flowchart/parser/flow-arrows.spec.js      |  2 +-
 .../flowchart/parser/flow-comments.spec.js    |  2 +-
 .../flowchart/parser/flow-direction.spec.js   |  2 +-
 .../flowchart/parser/flow-edges.spec.js       |  2 +-
 .../flowchart/parser/flow-huge.spec.js        |  2 +-
 .../parser/flow-interactions.spec.js          |  2 +-
 .../flowchart/parser/flow-lines.spec.js       |  2 +-
 .../flowchart/parser/flow-md-string.spec.js   |  2 +-
 .../flowchart/parser/flow-node-data.spec.js   | 26 ++++++++++++++++-
 .../flowchart/parser/flow-singlenode.spec.js  |  2 +-
 .../flowchart/parser/flow-style.spec.js       |  2 +-
 .../flowchart/parser/flow-text.spec.js        |  2 +-
 .../parser/flow-vertice-chaining.spec.js      |  2 +-
 .../src/diagrams/flowchart/parser/flow.jison  |  4 +--
 .../diagrams/flowchart/parser/flow.spec.js    |  2 +-
 .../diagrams/flowchart/parser/flowParser.ts   | 12 ++++++++
 .../flowchart/parser/subgraph.spec.js         |  2 +-
 20 files changed, 91 insertions(+), 18 deletions(-)
 create mode 100644 .changeset/great-ghosts-rule.md
 create mode 100644 packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts

diff --git a/.changeset/great-ghosts-rule.md b/.changeset/great-ghosts-rule.md
new file mode 100644
index 000000000..f11c6e2a9
--- /dev/null
+++ b/.changeset/great-ghosts-rule.md
@@ -0,0 +1,8 @@
+---
+'mermaid': minor
+---
+
+Flowchart new syntax for node metadata bugs
+
+- Incorrect label mapping for nodes when using `&`
+- Syntax error when `}` with trailing spaces before new line
diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index 66452f4b2..bb37914cb 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -1076,4 +1076,32 @@ end
       );
     });
   });
+  describe('New @ sytax for node metadata edge cases', () => {
+    it('should be possible to use @  syntax to add labels on multi nodes', () => {
+      imgSnapshotTest(
+        `flowchart TB
+       n2["label for n2"] &   n4@{ label: "labe for n4"}   & n5@{ label: "labe for n5"}
+        `,
+        {}
+      );
+    });
+    it('should be possible to use @  syntax to add labels with trail spaces and &', () => {
+      imgSnapshotTest(
+        `flowchart TB
+       n2["label for n2"] &   n4@{ label: "labe for n4"}   & n5@{ label: "labe for n5"}   
+        `,
+        {}
+      );
+    });
+    it('should be possible to use @  syntax to add labels with trail spaces', () => {
+      imgSnapshotTest(
+        `flowchart TB
+       n2["label for n2"]
+       n4@{ label: "labe for n4"}
+       n5@{ label: "labe for n5"}  
+        `,
+        {}
+      );
+    });
+  });
 });
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
index 67cdf918f..ddb2eb3ed 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
@@ -3,7 +3,8 @@ import { setConfig } from '../../diagram-api/diagramAPI.js';
 import flowDb from './flowDb.js';
 import renderer from './flowRenderer-v3-unified.js';
 // @ts-ignore: JISON doesn't support types
-import flowParser from './parser/flow.jison';
+//import flowParser from './parser/flow.jison';
+import flowParser from './parser/flowParser.ts';
 import flowStyles from './styles.js';
 
 export const diagram = {
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.js
index e89398ab4..50236b60c 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
index 9c2a740af..487705cd7 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 import { cleanupComments } from '../../../diagram-api/comments.js';
 
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js
index ce6b0b0c4..fcf3aa21f 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
index 5682c9bed..a23528fd6 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-huge.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-huge.spec.js
index 8931c6ee1..7fef14f5b 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-huge.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-huge.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js
index cb3f48cca..893a5e757 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 import { vi } from 'vitest';
 const spyOn = vi.spyOn;
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js
index ec157e646..116878f6f 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
index 55e749a22..3c39dbc4f 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
index 1669cfada..df3782a6b 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
@@ -290,4 +290,28 @@ describe('when parsing directions', function () {
     expect(data4Layout.nodes[0].shape).toEqual('squareRect');
     expect(data4Layout.nodes[0].label).toEqual('This is a string with}');
   });
+
+  it(' should be possible to use @  syntax to add labels on multi nodes', function () {
+    const res = flow.parser.parse(`flowchart TB
+       n2["label for n2"] &   n4@{ label: "labe for n4"}   & n5@{ label: "labe for n5"}
+      `);
+
+    const data4Layout = flow.parser.yy.getData();
+    expect(data4Layout.nodes.length).toBe(3);
+    expect(data4Layout.nodes[0].label).toEqual('label for n2');
+    expect(data4Layout.nodes[1].label).toEqual('labe for n4');
+    expect(data4Layout.nodes[2].label).toEqual('labe for n5');
+  });
+  it.skip(' should be possible to use @  syntax to add labels with trail spaces', function () {
+    const res = flow.parser.parse(
+      `flowchart TB
+       n2["label for n2"] &   n4@{ label: "labe for n4"}   & n5@{ label: "labe for n5"} `
+    );
+
+    const data4Layout = flow.parser.yy.getData();
+    expect(data4Layout.nodes.length).toBe(3);
+    expect(data4Layout.nodes[0].label).toEqual('label for n2');
+    expect(data4Layout.nodes[1].label).toEqual('labe for n4');
+    expect(data4Layout.nodes[2].label).toEqual('labe for n5');
+  });
 });
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js
index f6ed123d7..bf152c9bf 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js
index 22fd48a33..c24873e7c 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js
index 3754766f4..61774d7c0 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js
index a5b6a2b6d..95494ab44 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index fbd30fa9e..ade90382b 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -407,7 +407,7 @@ vertexStatement: vertexStatement link node shapeData
     |node spaceList { /*console.warn('vertexStatement: node spaceList', $node);*/ $$ = {stmt: $node, nodes:$node }}
     |node shapeData {
         /*console.warn('vertexStatement: node shapeData', $node[0], $shapeData);*/
-        yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData);
+        yy.addVertex($node[$node.length-1],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData);
         $$ = {stmt: $node, nodes:$node, shapeData: $shapeData}
     }
     |node { /* console.warn('vertexStatement: single node', $node); */ $$ = {stmt: $node, nodes:$node }}
@@ -416,7 +416,7 @@ vertexStatement: vertexStatement link node shapeData
 node: styledVertex
         { /*console.warn('nod', $styledVertex);*/ $$ = [$styledVertex];}
     | node shapeData spaceList AMP spaceList styledVertex
-        {  yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/  }
+        {  yy.addVertex($node[$node.length-1],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/  }
     | node spaceList AMP spaceList styledVertex
         { $$ = $node.concat($styledVertex); /*console.warn('pip', $node[0], $styledVertex, $$);*/  }
     ;
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
index 8081c8fe4..e9d646a5a 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { cleanupComments } from '../../../diagram-api/comments.js';
 import { setConfig } from '../../../config.js';
 
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts b/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts
new file mode 100644
index 000000000..acf4fa525
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts
@@ -0,0 +1,12 @@
+// @ts-ignore: JISON doesn't support types
+import flowJisonParser from './flow.jison';
+
+const newParser = Object.assign({}, flowJisonParser);
+
+newParser.parse = (src: string): unknown => {
+  // remove the trailing whitespace after closing curly braces when ending a line break
+  const newSrc = src.replace(/}\s*\n/g, '}\n');
+  return flowJisonParser.parse(newSrc);
+};
+
+export default newParser;
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
index 12b2e4a39..e2dd59284 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
@@ -1,5 +1,5 @@
 import flowDb from '../flowDb.js';
-import flow from './flow.jison';
+import flow from './flowParser.ts';
 import { setConfig } from '../../../config.js';
 
 setConfig({