From 2ed4469029874a3750d3962ee37731e1c2d6b39e Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Thu, 4 Jan 2024 14:15:30 +0100 Subject: [PATCH] #3358 Adding arrows to rendering --- cypress/platform/knsv2.html | 12 ++-- packages/mermaid/src/dagre-wrapper/edges.js | 3 +- .../mermaid/src/diagrams/block/blockDB.ts | 69 +++++++++++++++++-- .../src/diagrams/block/blockRenderer.ts | 14 +++- .../mermaid/src/diagrams/block/blockTypes.ts | 8 +++ .../src/diagrams/block/parser/block.jison | 11 ++- .../src/diagrams/block/renderHelpers.ts | 68 ++++++++++++++++++ 7 files changed, 169 insertions(+), 16 deletions(-) diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index ab5c0aa3c..80512993a 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -72,7 +72,10 @@ block-beta
 block-beta
-      A("APA") --> B("GORILLA")
+      columns 3
+      A space space:3 B("GORILLA")
+      A("APA") --o B
+
     
 block-beta
@@ -144,11 +147,8 @@ block-beta
   %% end
     
-block-beta
-  id1
-  block
-  id2
-  end
+      flowchart LR
+      a1 -- apa --> b1
     
 block-beta
diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js
index 1581658b0..656390329 100644
--- a/packages/mermaid/src/dagre-wrapper/edges.js
+++ b/packages/mermaid/src/dagre-wrapper/edges.js
@@ -371,11 +371,12 @@ const cutPathAtIntersect = (_points, boundryNode) => {
 //(edgePaths, e, edge, clusterDb, diagramtype, graph)
 export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) {
   let points = edge.points;
+  log.info('abc88 InsertEdge: edge=', edge, 'e=', e);
   let pointsHasChanged = false;
   const tail = graph.node(e.v);
   var head = graph.node(e.w);
+  log.info('abc88 InsertEdge (head & tail): ', head, tail);
 
-  log.info('abc88 InsertEdge: ', edge);
   if (head.intersect && tail.intersect) {
     points = points.slice(1, edge.points.length - 1);
     points.unshift(tail.intersect(points[0]));
diff --git a/packages/mermaid/src/diagrams/block/blockDB.ts b/packages/mermaid/src/diagrams/block/blockDB.ts
index 86f0b78bb..f5876849e 100644
--- a/packages/mermaid/src/diagrams/block/blockDB.ts
+++ b/packages/mermaid/src/diagrams/block/blockDB.ts
@@ -17,12 +17,21 @@ import clone from 'lodash-es/clone.js';
 
 // Initialize the node database for simple lookups
 let blockDatabase: Record = {};
-
+let edgeList: Block[] = [];
+let edgeCount: Record = {};
 const populateBlockDatabase = (blockList: Block[], parent: Block): void => {
   const children = [];
   for (const block of blockList) {
     if (block.type === 'column-setting') {
       parent.columns = block.columns || -1;
+    } else if (block.type === 'edge') {
+      if (edgeCount[block.id]) {
+        edgeCount[block.id]++;
+      } else {
+        edgeCount[block.id] = 1;
+      }
+      block.id = edgeCount[block.id] + '-' + block.id;
+      edgeList.push(block);
     } else {
       if (!block.label) {
         if (block.type === 'composite') {
@@ -31,7 +40,18 @@ const populateBlockDatabase = (blockList: Block[], parent: Block): void => {
           block.label = block.id;
         }
       }
-      blockDatabase[block.id] = block;
+      const newBlock = !blockDatabase[block.id];
+      if (newBlock) {
+        blockDatabase[block.id] = block;
+      } else {
+        // Add newer relevant data to aggregated node
+        if (block.type !== 'na') {
+          blockDatabase[block.id].type = block.type;
+        }
+        if (block.label !== block.id) {
+          blockDatabase[block.id].label = block.label;
+        }
+      }
 
       if (block.children) {
         populateBlockDatabase(block.children, block);
@@ -46,7 +66,9 @@ const populateBlockDatabase = (blockList: Block[], parent: Block): void => {
           children.push(newBlock);
         }
       } else {
-        children.push(block);
+        if (newBlock) {
+          children.push(block);
+        }
       }
     }
   }
@@ -63,6 +85,9 @@ const clear = (): void => {
   rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
   blockDatabase = { root: rootBlock };
   blocks = [] as Block[];
+
+  edgeList = [];
+  edgeCount = {};
 };
 
 type ITypeStr2Type = (typeStr: string) => BlockType;
@@ -101,7 +126,29 @@ export function typeStr2Type(typeStr: string): BlockType {
     case '<[]>':
       return 'block_arrow';
     default:
-      return 'square';
+      return 'na';
+  }
+}
+
+type IEdgeTypeStr2Type = (typeStr: string) => string;
+export function edgeTypeStr2Type(typeStr: string): string {
+  log.debug('typeStr2Type', typeStr);
+  switch (typeStr) {
+    case '==':
+      return 'thick';
+    default:
+      return 'normal';
+  }
+}
+type IEdgeStrToEdgeDataType = (typeStr: string) => string;
+export function edgeStrToEdgeData(typeStr: string): string {
+  switch (typeStr.trim()) {
+    case '--x':
+      return 'arrow_cross';
+    case '--o':
+      return 'arrow_circle';
+    default:
+      return 'arrow_point';
   }
 }
 
@@ -114,10 +161,10 @@ export const generateId = () => {
 
 type ISetHierarchy = (block: Block[]) => void;
 const setHierarchy = (block: Block[]): void => {
-  // log.debug('The hierarchy', JSON.stringify(block, null, 2));
+  log.debug('The hierarchy', JSON.stringify(block, null, 2));
   rootBlock.children = block;
   populateBlockDatabase(block, rootBlock);
-  // log.debug('The hierarchy', JSON.stringify(rootBlock, null, 2));
+  log.debug('The hierarchy', JSON.stringify(rootBlock, null, 2));
   blocks = rootBlock.children;
 };
 
@@ -146,6 +193,10 @@ type IGetBlocks = () => Block[];
 const getBlocks: IGetBlocks = () => {
   return blocks || [];
 };
+type IGetEdges = () => Block[];
+const getEdges: IGetEdges = () => {
+  return edgeList;
+};
 type IGetBlock = (id: string) => Block | undefined;
 const getBlock: IGetBlock = (id: string) => {
   return blockDatabase[id];
@@ -166,12 +217,15 @@ export interface BlockDB extends DiagramDB {
   getConfig: () => BlockConfig | undefined;
   addLink: IAddLink;
   getLogger: IGetLogger;
+  getEdges: IGetEdges;
   getBlocks: IGetBlocks;
   getBlock: IGetBlock;
   setBlock: ISetBlock;
   getLinks: IGetLinks;
   getColumns: IGetColumns;
   typeStr2Type: ITypeStr2Type;
+  edgeTypeStr2Type: IEdgeTypeStr2Type;
+  edgeStrToEdgeData: IEdgeStrToEdgeDataType;
   setHierarchy: ISetHierarchy;
   generateId: IGenerateId;
 }
@@ -180,8 +234,11 @@ const db: BlockDB = {
   getConfig: () => configApi.getConfig().block,
   addLink: addLink,
   typeStr2Type: typeStr2Type,
+  edgeTypeStr2Type: edgeTypeStr2Type,
+  edgeStrToEdgeData,
   getLogger, // TODO: remove
   getBlocks,
+  getEdges,
   getLinks,
   setHierarchy,
   getBlock,
diff --git a/packages/mermaid/src/diagrams/block/blockRenderer.ts b/packages/mermaid/src/diagrams/block/blockRenderer.ts
index 2b691358c..991adda3f 100644
--- a/packages/mermaid/src/diagrams/block/blockRenderer.ts
+++ b/packages/mermaid/src/diagrams/block/blockRenderer.ts
@@ -1,8 +1,9 @@
 import { Diagram } from '../../Diagram.js';
 import * as configApi from '../../config.js';
-import { calculateBlockSizes, insertBlocks } from './renderHelpers.js';
+import { calculateBlockSizes, insertBlocks, insertEdges } from './renderHelpers.js';
 import { layout } from './layout.js';
 import { setupGraphViewbox } from '../../setupGraphViewbox.js';
+import insertMarkers from '../../dagre-wrapper/markers.js';
 import {
   select as d3select,
   scaleOrdinal as d3scaleOrdinal,
@@ -36,13 +37,22 @@ export const draw = async function (
   // @ts-ignore TODO root.select is not callable
   const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
 
+  // Define the supported markers for the diagram
+  const markers = ['point', 'circle', 'cross'];
+
+  // Add the marker definitions to the svg as marker tags
+  // insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
+  insertMarkers(svg, markers, diagObj.type, true);
+
   const bl = db.getBlocks();
+  const edges = db.getEdges();
 
   const nodes = svg.insert('g').attr('class', 'block');
   await calculateBlockSizes(nodes, bl, db);
   const bounds = layout(db);
-  log.debug('Here blocks', bl);
+  // log.debug('Here be blocks', bl);
   await insertBlocks(nodes, bl, db);
+  await insertEdges(nodes, edges, bl, db);
 
   // log.debug('Here', bl);
 
diff --git a/packages/mermaid/src/diagrams/block/blockTypes.ts b/packages/mermaid/src/diagrams/block/blockTypes.ts
index a65e5db32..4be03d959 100644
--- a/packages/mermaid/src/diagrams/block/blockTypes.ts
+++ b/packages/mermaid/src/diagrams/block/blockTypes.ts
@@ -5,7 +5,9 @@ export interface BlockConfig extends BaseDiagramConfig {
 }
 
 export type BlockType =
+  | 'na'
   | 'column-setting'
+  | 'edge'
   | 'round'
   | 'block_arrow'
   | 'space'
@@ -29,9 +31,15 @@ export type BlockType =
   | 'composite';
 
 export interface Block {
+  // When the block have the type edge, the start and end are the id of the source and target blocks
+  start?: string;
+  end?: string;
+  arrowTypeEnd?: string;
+  arrowTypeStart?: string;
   width?: number;
   id: string;
   label?: string;
+  intersect?: any;
   parent?: Block;
   type?: BlockType;
   children: Block[];
diff --git a/packages/mermaid/src/diagrams/block/parser/block.jison b/packages/mermaid/src/diagrams/block/parser/block.jison
index 448ce0f41..d5e0ff828 100644
--- a/packages/mermaid/src/diagrams/block/parser/block.jison
+++ b/packages/mermaid/src/diagrams/block/parser/block.jison
@@ -191,10 +191,19 @@ statement
 	;
 
 nodeStatement
-  : nodeStatement link node { yy.getLogger().info('Rule: (nodeStatement link node) ', $1, $2, $3); $$ = [{id: $1.id, label: $1.label, type:$1.type, directions: $1.directions}, {id: $3.id, label: $3.label, type: yy.typeStr2Type($3.typeStr), directions: $3.directions}]; }
+  : nodeStatement link node {
+    yy.getLogger().info('Rule: (nodeStatement link node) ', $1, $2, $3, 'abc88 typestr =>',$2);
+    const edgeData = yy.edgeStrToEdgeData($2)
+    $$ = [
+      {id: $1.id, label: $1.label, type:$1.type, directions: $1.directions},
+      {id: $1.id + '-' + $3.id, start: $1.id, end: $3.id, label: $3.label, type: 'edge', directions: $3.directions, arrowTypeEnd: edgeData, arrowTypeStart: 'arrow_open' },
+      {id: $3.id, label: $3.label, type: yy.typeStr2Type($3.typeStr), directions: $3.directions}
+      ];
+    }
   | node { yy.getLogger().info('Rule: nodeStatement (node) ', $1); $$ = {id: $1.id, label: $1.label, type: yy.typeStr2Type($1.typeStr), directions: $1.directions}; }
   ;
 
+
 columnsStatement
   : COLUMNS { yy.getLogger().info('APA123', this? this:'na'); yy.getLogger().info("COLUMNS: ", $1); $$ = {type: 'column-setting', columns: $1 === 'auto'?-1:parseInt($1) } }
   ;
diff --git a/packages/mermaid/src/diagrams/block/renderHelpers.ts b/packages/mermaid/src/diagrams/block/renderHelpers.ts
index 125cd2997..73933426d 100644
--- a/packages/mermaid/src/diagrams/block/renderHelpers.ts
+++ b/packages/mermaid/src/diagrams/block/renderHelpers.ts
@@ -1,5 +1,7 @@
 import { getStylesFromArray } from '../../utils.js';
 import { insertNode, positionNode } from '../../dagre-wrapper/nodes.js';
+import { insertEdge } from '../../dagre-wrapper/edges.js';
+import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
 import { getConfig } from '../../config.js';
 import { ContainerElement } from 'd3';
 import type { Block } from './blockTypes.js';
@@ -127,6 +129,7 @@ function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) {
     x: bounds.x,
     y: bounds.y,
     positioned,
+    intersect: undefined,
     type: vertex.type,
     // props: vertex.props,
     padding: padding ?? (getConfig()?.flowchart?.padding || 0),
@@ -158,6 +161,7 @@ export async function insertBlockPositioned(elem: any, block: Block, db: any) {
   const obj = db.getBlock(node.id);
   if (obj.type !== 'space') {
     const nodeEl = await insertNode(elem, node);
+    block.intersect = node?.intersect;
     positionNode(node);
   }
 }
@@ -183,3 +187,67 @@ export async function calculateBlockSizes(elem: ContainerElement, blocks: Block[
 export async function insertBlocks(elem: ContainerElement, blocks: Block[], db: BlockDB) {
   await performOperations(elem, blocks, db, insertBlockPositioned);
 }
+
+export async function insertEdges(
+  elem: ContainerElement,
+  edges: Block[],
+  blocks: Block[],
+  db: BlockDB
+) {
+  const g = new graphlib.Graph({
+    multigraph: true,
+    compound: true,
+  });
+  g.setGraph({
+    rankdir: 'TB',
+    nodesep: 10,
+    ranksep: 10,
+    marginx: 8,
+    marginy: 8,
+  });
+
+  for (const block of blocks) {
+    if (block.size) {
+      g.setNode(block.id, {
+        width: block.size.width,
+        height: block.size.height,
+        intersect: block.intersect,
+      });
+    }
+  }
+
+  // log.debug('abc88 edges', edges);
+  for (const edge of edges) {
+    // elem, e, edge, clusterDb, diagramType, graph;
+    if (edge.start && edge.end) {
+      const startBlock = db.getBlock(edge.start);
+      const endBlock = db.getBlock(edge.end);
+
+      if (startBlock?.size && endBlock?.size) {
+        const start = startBlock.size;
+        const end = endBlock.size;
+        const points = [
+          { x: start.x, y: start.y },
+          { x: start.x + (end.x - start.x) / 2, y: start.y + (end.y - start.y) / 2 },
+          { x: end.x, y: end.y },
+        ];
+        // edge.points = points;
+        await insertEdge(
+          elem,
+          { v: edge.start, w: edge.end, name: edge.id },
+          {
+            ...edge,
+            // arrowHead: 'normal',
+            arrowTypeEnd: edge.arrowTypeEnd,
+            arrowTypeStart: edge.arrowTypeStart,
+            points,
+            classes: 'edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1',
+          },
+          undefined,
+          'block',
+          g
+        );
+      }
+    }
+  }
+}