From 49bdd110114e580abe61be7cf950ef2f48f9b87e Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Fri, 24 Jan 2025 11:44:05 +0100 Subject: [PATCH] #MC-2558 Handling animations for look neo --- cypress/platform/knsv2.html | 206 +++++++++++++++++- packages/mermaid-layout-elk/src/render.ts | 8 +- .../layout-algorithms/dagre/index.js | 8 +- .../layout-algorithms/fixed/index.js | 16 +- .../rendering-elements/edgeMarker.ts | 19 +- .../rendering-elements/edges.js | 12 +- .../rendering-elements/markers.js | 117 +++++++++- 7 files changed, 342 insertions(+), 44 deletions(-) diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 90ecd20ea..26ca690f3 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -105,11 +105,11 @@ -
+    
       flowchart LR
         AB["apa@apa@"] --> B(("`apa@apa`"))
     
-
+    
       flowchart
         D(("for D"))
     
@@ -119,6 +119,208 @@ e1@{ animate: true}
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@==> BB
+        e1@{ animate: true}
+    
+
+
+      flowchart LR
+        A e1@==> B
+        e1@{ animate: false}
+    
+
+---
+config:
+  theme: neo
+  look: handDrawn
+---
+      flowchart LR
+        A e1@==> BB
+        e1@{ animate: false}
+    
+ + +
+
+      flowchart LR
+        slow e2@--> B
+        e2@{ animation: slow}
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@--> BB
+        e1@{ animation: fast}
+    
+
+
+      flowchart LR
+        A e1@==> B
+          classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@==> BB
+          classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+ +
+ +
+      flowchart LR
+        A e1@==o B
+        e1@{ animate: true}
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@==o BB
+        e1@{ animate: true}
+    
+
+
+      flowchart LR
+        A e1@==o B
+        e1@{ animate: false}
+    
+
+---
+config:
+  theme: neo
+  look: handDrawn
+---
+      flowchart LR
+        A e1@==o BB
+        e1@{ animate: false}
+    
+ + +
+
+      flowchart LR
+        slow e2@--o B
+        e2@{ animation: slow}
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@--o BB
+        e1@{ animation: fast}
+    
+
+
+      flowchart LR
+        A e1@==o B
+          classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@==o BB
+          classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+ +
+ +
+      flowchart LR
+        A e1@==x B
+        e1@{ animate: true}
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@==x BB
+        e1@{ animate: true}
+    
+
+
+      flowchart LR
+        A e1@==x B
+        e1@{ animate: false}
+    
+
+---
+config:
+  theme: neo
+  look: handDrawn
+---
+      flowchart LR
+        A e1@==x BB
+        e1@{ animate: false}
+    
+ + +
+
+      flowchart LR
+        slow e2@--x B
+        e2@{ animation: slow}
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@--x BB
+        e1@{ animation: fast}
+    
+
+
+      flowchart LR
+        A e1@==x B
+          classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+
+---
+config:
+  theme: forest
+  look: classic
+---
+      flowchart LR
+        A e1@==x BB
+          classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
+  class e1 animate
+    
+
 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
diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts
index 3817d15fb..26d720fad 100644
--- a/packages/mermaid-layout-elk/src/render.ts
+++ b/packages/mermaid-layout-elk/src/render.ts
@@ -781,13 +781,7 @@ export const render = async (
   const elk = new ELK();
   const element = svg.select('g');
   // Add the arrowheads to the svg
-  insertMarkers(
-    element,
-    data4Layout.markers,
-    data4Layout.type,
-    data4Layout.diagramId,
-    data4Layout.config
-  );
+  insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
 
   // Setup the graph with the layout options and the data for the layout
   let elkGraph: any = {
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js b/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js
index 6be8ba9c3..c117fe6c2 100644
--- a/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js
@@ -290,13 +290,7 @@ export const render = async (data4Layout, svg) => {
       return {};
     });
   const element = svg.select('g');
-  insertMarkers(
-    element,
-    data4Layout.markers,
-    data4Layout.type,
-    data4Layout.diagramId,
-    data4Layout.config
-  );
+  insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
   clearNodes();
   clearEdges();
   clearClusters();
diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/fixed/index.js b/packages/mermaid/src/rendering-util/layout-algorithms/fixed/index.js
index 88ce71e05..145424e48 100644
--- a/packages/mermaid/src/rendering-util/layout-algorithms/fixed/index.js
+++ b/packages/mermaid/src/rendering-util/layout-algorithms/fixed/index.js
@@ -94,10 +94,10 @@ export const calcNodeIntersections = async (targetNodeId, _node1, _node2) => {
   }
 
   // Insert node will not give any widths as the element is not in the DOM
-  node1.width = _node1.width || 50;
-  node1.height = _node1.height || 50;
-  node2.width = _node2.width || 50;
-  node2.height = _node2.height || 50;
+  node1.width = _node1.width ?? 50;
+  node1.height = _node1.height ?? 50;
+  node2.width = _node2.width ?? 50;
+  node2.height = _node2.height ?? 50;
 
   const startIntersection = calcIntersectionPoint(node1, { x: node2.x, y: node2.y });
   const endIntersection = calcIntersectionPoint(node2, { x: node1.x, y: node1.y });
@@ -402,13 +402,7 @@ const doRender = async (_elem, data4Layout, siteConfig, positions) => {
 export const render = async (data4Layout, svg, _internalHelpers, _algorithm, positions) => {
   const element = svg.select('g');
   // Org
-  insertMarkers(
-    element,
-    data4Layout.markers,
-    data4Layout.type,
-    data4Layout.diagramId,
-    data4Layout.config
-  );
+  insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
   clearNodes();
   clearEdges();
   clearClusters();
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edgeMarker.ts b/packages/mermaid/src/rendering-util/rendering-elements/edgeMarker.ts
index 5371ac32d..d28a33bbb 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/edgeMarker.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/edgeMarker.ts
@@ -15,13 +15,14 @@ export const addEdgeMarkers = (
   edge: Pick,
   url: string,
   id: string,
-  diagramType: string
+  diagramType: string,
+  useMargin = false
 ) => {
   if (edge.arrowTypeStart) {
-    addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
+    addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType, useMargin);
   }
   if (edge.arrowTypeEnd) {
-    addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
+    addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType, useMargin);
   }
 };
 
@@ -37,15 +38,19 @@ const arrowTypesMap = {
   lollipop: 'lollipop',
 } as const;
 
+const arrowTypesWithMarginSupport = ['cross', 'point', 'circle'];
+
 const addEdgeMarker = (
   svgPath: SVG,
   position: 'start' | 'end',
   arrowType: string,
   url: string,
   id: string,
-  diagramType: string
+  diagramType: string,
+  useMargin = false
 ) => {
   const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
+  const marginSupport = arrowTypesWithMarginSupport.includes(endMarkerType);
 
   if (!endMarkerType) {
     log.warn(`Unknown arrow type: ${arrowType}`);
@@ -53,5 +58,9 @@ const addEdgeMarker = (
   }
 
   const suffix = position === 'start' ? 'Start' : 'End';
-  svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`);
+  const offset = useMargin && marginSupport ? '-margin' : '';
+  svgPath.attr(
+    `marker-${position}`,
+    `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix}${offset})`
+  );
 };
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
index 767f85ddd..68e374305 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js
@@ -544,7 +544,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
       ? generateRoundedPath(applyMarkerOffsetsToPoints(lineData, edge), 5)
       : lineFunction(lineData);
   const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
-
+  let animatedEdge = false;
   if (edge.look === 'handDrawn') {
     const rc = rough.svg(elem);
     Object.assign([], lineData);
@@ -592,11 +592,13 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
           ';' +
           (edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '')
       );
+
+    animatedEdge =
+      edge.animate === true || !!edge.animation || stylesFromClasses.includes('animation');
     const len = svgPath.node().getTotalLength();
     const oValueS = markerOffsets2[edge.arrowTypeStart] || 0;
     const oValueE = markerOffsets2[edge.arrowTypeEnd] || 0;
-
-    if (edge.look === 'neo') {
+    if (edge.look === 'neo' && !animatedEdge) {
       const dashArray = `0 ${oValueS} ${len - oValueS - oValueE} ${oValueE}`;
 
       // No offset needed because we already start with a zero-length dash that effectively sets us up for a gap at the start.
@@ -646,7 +648,9 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
   log.info('arrowTypeStart', edge.arrowTypeStart);
   log.info('arrowTypeEnd', edge.arrowTypeEnd);
 
-  addEdgeMarkers(svgPath, edge, url, id, diagramType);
+  const useMargin = !animatedEdge && edge?.look === 'neo';
+
+  addEdgeMarkers(svgPath, edge, url, id, diagramType, useMargin);
 
   let paths = {};
   if (pointsHasChanged) {
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/markers.js b/packages/mermaid/src/rendering-util/rendering-elements/markers.js
index 98d918660..7797f13e8 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/markers.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/markers.js
@@ -2,9 +2,9 @@
 import { log } from '../../logger.js';
 
 // Only add the number of markers that the diagram needs
-const insertMarkers = (elem, markerArray, type, id, config) => {
+const insertMarkers = (elem, markerArray, type, id) => {
   markerArray.forEach((markerName) => {
-    markers[markerName](elem, type, id, config);
+    markers[markerName](elem, type, id);
   });
 };
 
@@ -153,13 +153,13 @@ const lollipop = (elem, type, id) => {
     .attr('cy', 7)
     .attr('r', 6);
 };
-const point = (elem, type, id, options) => {
+const point = (elem, type, id) => {
   elem
     .append('marker')
     .attr('id', id + '_' + type + '-pointEnd')
     .attr('class', 'marker ' + type)
     .attr('viewBox', '0 0 11.5 14')
-    .attr('refX', options?.look === 'neo' ? 11.5 : 7.75) // Adjust to position the arrowhead relative to the line
+    .attr('refX', 7.75) // Adjust to position the arrowhead relative to the line
     .attr('refY', 7) // Half of 14 for vertical center
     .attr('markerUnits', 'userSpaceOnUse')
     .attr('markerWidth', 10.5)
@@ -175,7 +175,39 @@ const point = (elem, type, id, options) => {
     .attr('id', id + '_' + type + '-pointStart')
     .attr('class', 'marker ' + type)
     .attr('viewBox', '0 0 11.5 14')
-    .attr('refX', options?.look === 'neo' ? 1 : 4)
+    .attr('refX', 4)
+    .attr('refY', 7)
+    .attr('markerUnits', 'userSpaceOnUse')
+    .attr('markerWidth', 11.5)
+    .attr('markerHeight', 14)
+    .attr('orient', 'auto')
+    .append('polygon')
+    .attr('points', '0,7 11.5,14 11.5,0')
+    .attr('class', 'arrowMarkerPath')
+    .style('stroke-width', 0)
+    .style('stroke-dasharray', '1,0');
+  elem
+    .append('marker')
+    .attr('id', id + '_' + type + '-pointEnd-margin')
+    .attr('class', 'marker ' + type)
+    .attr('viewBox', '0 0 11.5 14')
+    .attr('refX', 11.5) // Adjust to position the arrowhead relative to the line
+    .attr('refY', 7) // Half of 14 for vertical center
+    .attr('markerUnits', 'userSpaceOnUse')
+    .attr('markerWidth', 10.5)
+    .attr('markerHeight', 14)
+    .attr('orient', 'auto')
+    .append('path')
+    .attr('d', 'M 0 0 L 11.5 7 L 0 14 z')
+    .attr('class', 'arrowMarkerPath')
+    .style('stroke-width', 0)
+    .style('stroke-dasharray', '1,0');
+  elem
+    .append('marker')
+    .attr('id', id + '_' + type + '-pointStart-margin')
+    .attr('class', 'marker ' + type)
+    .attr('viewBox', '0 0 11.5 14')
+    .attr('refX', 1)
     .attr('refY', 7)
     .attr('markerUnits', 'userSpaceOnUse')
     .attr('markerWidth', 11.5)
@@ -187,14 +219,14 @@ const point = (elem, type, id, options) => {
     .style('stroke-width', 0)
     .style('stroke-dasharray', '1,0');
 };
-const circle = (elem, type, id, options) => {
+const circle = (elem, type, id) => {
   elem
     .append('marker')
     .attr('id', id + '_' + type + '-circleEnd')
     .attr('class', 'marker ' + type)
     .attr('viewBox', '0 0 10 10')
     .attr('refY', 5) // What!!!??
-    .attr('refX', options?.look === 'neo' ? 12.25 : 10.75)
+    .attr('refX', 10.75)
     .attr('markerUnits', 'userSpaceOnUse')
     .attr('markerWidth', 14)
     .attr('markerHeight', 14)
@@ -212,7 +244,44 @@ const circle = (elem, type, id, options) => {
     .attr('id', id + '_' + type + '-circleStart')
     .attr('class', 'marker ' + type)
     .attr('viewBox', '0 0 10 10')
-    .attr('refX', options?.look === 'neo' ? -2 : 0)
+    .attr('refX', 0)
+    .attr('refY', 5)
+    .attr('markerUnits', 'userSpaceOnUse')
+    .attr('markerWidth', 14)
+    .attr('markerHeight', 14)
+    .attr('orient', 'auto')
+    .append('circle')
+    .attr('cx', '5')
+    .attr('cy', '5')
+    .attr('r', '5')
+    .attr('class', 'arrowMarkerPath')
+    .style('stroke-width', 0)
+    .style('stroke-dasharray', '1,0');
+  elem
+    .append('marker')
+    .attr('id', id + '_' + type + '-circleEnd-margin')
+    .attr('class', 'marker ' + type)
+    .attr('viewBox', '0 0 10 10')
+    .attr('refY', 5) // What!!!??
+    .attr('refX', 12.25)
+    .attr('markerUnits', 'userSpaceOnUse')
+    .attr('markerWidth', 14)
+    .attr('markerHeight', 14)
+    .attr('orient', 'auto')
+    .append('circle')
+    .attr('cx', '5')
+    .attr('cy', '5')
+    .attr('r', '5')
+    .attr('class', 'arrowMarkerPath')
+    .style('stroke-width', 0)
+    .style('stroke-dasharray', '1,0');
+
+  elem
+    .append('marker')
+    .attr('id', id + '_' + type + '-circleStart-margin')
+    .attr('class', 'marker ' + type)
+    .attr('viewBox', '0 0 10 10')
+    .attr('refX', -2)
     .attr('refY', 5)
     .attr('markerUnits', 'userSpaceOnUse')
     .attr('markerWidth', 14)
@@ -259,6 +328,38 @@ const cross = (elem, type, id) => {
     .attr('class', 'arrowMarkerPath')
     .style('stroke-width', 2.5)
     .style('stroke-dasharray', '1,0');
+  elem
+    .append('marker')
+    .attr('id', id + '_' + type + '-crossEnd-margin')
+    .attr('class', 'marker cross ' + type)
+    .attr('viewBox', '0 0 15 15')
+    .attr('refX', 17.7)
+    .attr('refY', 7.5)
+    .attr('markerUnits', 'userSpaceOnUse')
+    .attr('markerWidth', 12)
+    .attr('markerHeight', 12)
+    .attr('orient', 'auto')
+    .append('path')
+    .attr('d', 'M 1,1 L 14,14 M 1,14 L 14,1')
+    .attr('class', 'arrowMarkerPath')
+    .style('stroke-width', 2.5);
+
+  elem
+    .append('marker')
+    .attr('id', id + '_' + type + '-crossStart-margin')
+    .attr('class', 'marker cross ' + type)
+    .attr('viewBox', '0 0 15 15')
+    .attr('refX', -3.5)
+    .attr('refY', 7.5)
+    .attr('markerUnits', 'userSpaceOnUse')
+    .attr('markerWidth', 12)
+    .attr('markerHeight', 12)
+    .attr('orient', 'auto')
+    .append('path')
+    .attr('d', 'M 1,1 L 14,14 M 1,14 L 14,1')
+    .attr('class', 'arrowMarkerPath')
+    .style('stroke-width', 2.5)
+    .style('stroke-dasharray', '1,0');
 };
 const barb = (elem, type, id) => {
   elem