From 6d0794130cc993e1da976f366784fc5a3ee0fcd0 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 2 Jul 2023 17:44:23 -0700 Subject: [PATCH 1/6] add implementation/realization edge type, fix arrow heads to be hollow --- packages/mermaid/src/dagre-wrapper/edges.js | 7 ++ packages/mermaid/src/dagre-wrapper/markers.js | 79 ++++++++++++++++++- .../mermaid/src/diagrams/class/classDb.ts | 1 + .../src/diagrams/class/classRenderer-v2.ts | 5 +- .../diagrams/class/parser/classDiagram.jison | 3 +- .../mermaid/src/diagrams/class/svgDraw.js | 2 + 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 1581658b05..daee74ee90 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -469,6 +469,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph default: strokeClasses = ''; } + switch (edge.pattern) { case 'solid': strokeClasses += ' edge-pattern-solid'; @@ -533,6 +534,9 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph case 'extension': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')'); break; + case 'realization': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-realizationStart' + ')'); + break; case 'composition': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')'); break; @@ -563,6 +567,9 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph case 'extension': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')'); break; + case 'realization': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-realizationEnd' + ')'); + break; case 'composition': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')'); break; diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index 57d092fdfe..e42c0cad4d 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -2,6 +2,43 @@ import { log } from '../logger.js'; +const getSvgParent = (elem) => { + let container = elem; + + // the intent here is to find the first parent element that is NOT part of the SVG element + // I know there has to be a better way, but could not find one that worked + // tried using checking if elem was instanceof SVGGraphicsElement or SVGElement, but it failed to detect correctly + if (container._groups) { + container = container._groups[0][0]; + } + if (container.tagName.toLowerCase() === 'g') { + container = container.parentElement; + } + if (container.localName.toLowerCase() === 'svg') { + container = container.parentElement; + } + return container; +}; + +const getBackgroundColor = (elem) => { + let parent = getSvgParent(elem); + + let backgroundColor; + while (parent && parent.tagName.toLowerCase() !== 'body') { + if(parent instanceof Element) { + const computedStyle = getComputedStyle(parent); + backgroundColor = computedStyle.backgroundColor; + + if (backgroundColor !== 'rgba(0, 0, 0, 0)') { + break; + } + parent = parent.parentNode; + } + } + + return backgroundColor === 'rgba(0, 0, 0, 0)' ? 'white' : backgroundColor; +}; + // Only add the number of markers that the diagram needs const insertMarkers = (elem, markerArray, type, id) => { markerArray.forEach((markerName) => { @@ -11,6 +48,8 @@ const insertMarkers = (elem, markerArray, type, id) => { const extension = (elem, type, id) => { log.trace('Making markers for ', id); + let backgroundColor = getBackgroundColor(elem); + elem .append('defs') .append('marker') @@ -22,7 +61,8 @@ const extension = (elem, type, id) => { .attr('markerHeight', 240) .attr('orient', 'auto') .append('path') - .attr('d', 'M 1,7 L18,13 V 1 Z'); + .attr('d', 'M 1,7 L18,13 V 2 Z') + .attr('fill', backgroundColor); elem .append('defs') @@ -35,7 +75,41 @@ const extension = (elem, type, id) => { .attr('markerHeight', 28) .attr('orient', 'auto') .append('path') - .attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead + .attr('d', 'M 1,1 V 13 L18,7 Z') + .attr('fill', backgroundColor); // this is actual shape for arrowhead +}; + +const realization = (elem, type, id) => { + log.trace('Making markers for ', id); + let backgroundColor = getBackgroundColor(elem); + + elem + .append('defs') + .append('marker') + .attr('id', type + '-realizationStart') + .attr('class', 'marker realization ' + type) + .attr('refX', 0) + .attr('refY', 7) + .attr('markerWidth', 190) + .attr('markerHeight', 240) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 1,7 L18,13 V 2 Z') + .attr('fill', backgroundColor); + + elem + .append('defs') + .append('marker') + .attr('id', type + '-realizationEnd') + .attr('class', 'marker realization ' + type) + .attr('refX', 19) + .attr('refY', 7) + .attr('markerWidth', 20) + .attr('markerHeight', 28) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 1,1 V 13 L18,7 Z') + .attr('fill', backgroundColor); // this is actual shape for arrowhead }; const composition = (elem, type) => { @@ -265,6 +339,7 @@ const barb = (elem, type) => { // TODO rename the class diagram markers to something shape descriptive and semantic free const markers = { extension, + realization, composition, aggregation, dependency, diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 7b74aa819f..a69a13a182 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -362,6 +362,7 @@ export const relationType = { COMPOSITION: 2, DEPENDENCY: 3, LOLLIPOP: 4, + REALIZATION: 5, }; const setupToolTips = function (element: Element) { diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 6197fe8ace..fb0b9c83b1 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -361,7 +361,7 @@ export const draw = async function (text: string, id: string, _version: string, await render( element, g, - ['aggregation', 'extension', 'composition', 'dependency', 'lollipop'], + ['aggregation', 'extension', 'realization', 'composition', 'dependency', 'lollipop'], 'classDiagram', id ); @@ -413,6 +413,9 @@ function getArrowMarker(type: number) { case 4: marker = 'lollipop'; break; + case 5: + marker = 'realization'; + break; default: marker = 'none'; } diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 7788fcc0c0..88b46a6661 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -124,7 +124,7 @@ Function arguments are optional: 'call ()' simply executes 'callb <*>"_top" return 'LINK_TARGET'; <*>\s*\<\| return 'EXTENSION'; -<*>\s*\|\> return 'EXTENSION'; +<*>\s*\|\> return 'REALIZATION'; <*>\s*\> return 'DEPENDENCY'; <*>\s*\< return 'DEPENDENCY'; <*>\s*\* return 'COMPOSITION'; @@ -370,6 +370,7 @@ relation relationType : AGGREGATION { $$=yy.relationType.AGGREGATION;} | EXTENSION { $$=yy.relationType.EXTENSION;} + | REALIZATION { $$=yy.relationType.REALIZATION;} | COMPOSITION { $$=yy.relationType.COMPOSITION;} | DEPENDENCY { $$=yy.relationType.DEPENDENCY;} | LOLLIPOP { $$=yy.relationType.LOLLIPOP;} diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index e4afe21368..9ecc741578 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -9,6 +9,8 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { switch (type) { case diagObj.db.relationType.AGGREGATION: return 'aggregation'; + case diagObj.db.relationType.REALIZATION: + return 'realization'; case diagObj.db.relationType.EXTENSION: return 'extension'; case diagObj.db.relationType.COMPOSITION: From 824feb544dee0020a2aebc835edd790a0b1c160e Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 3 Jul 2023 08:54:34 -0700 Subject: [PATCH 2/6] Update packages/mermaid/src/dagre-wrapper/markers.js Co-authored-by: Alois Klink --- packages/mermaid/src/dagre-wrapper/markers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index e42c0cad4d..69082fc8c1 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -48,7 +48,7 @@ const insertMarkers = (elem, markerArray, type, id) => { const extension = (elem, type, id) => { log.trace('Making markers for ', id); - let backgroundColor = getBackgroundColor(elem); + const backgroundColor = getBackgroundColor(elem); elem .append('defs') From 96e5151aec87211abd86d17f5b71debfe56ac591 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 3 Jul 2023 09:44:22 -0700 Subject: [PATCH 3/6] prettier fixes --- packages/mermaid/src/dagre-wrapper/markers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index 69082fc8c1..0c4e56be7d 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -25,7 +25,7 @@ const getBackgroundColor = (elem) => { let backgroundColor; while (parent && parent.tagName.toLowerCase() !== 'body') { - if(parent instanceof Element) { + if (parent instanceof Element) { const computedStyle = getComputedStyle(parent); backgroundColor = computedStyle.backgroundColor; From 65c0c0acb727880e0416bd3e3ba8d119ec70a82a Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 3 Jul 2023 09:54:28 -0700 Subject: [PATCH 4/6] small test fix --- packages/mermaid-zenuml/README.md | 2 +- .../mermaid/src/diagrams/class/classDiagram.spec.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/mermaid-zenuml/README.md b/packages/mermaid-zenuml/README.md index e807400636..4300aecbe0 120000 --- a/packages/mermaid-zenuml/README.md +++ b/packages/mermaid-zenuml/README.md @@ -1 +1 @@ -../mermaid/src/docs/syntax/zenuml.md \ No newline at end of file +../mermaid/src/docs/syntax/zenuml.md diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 2182e8c34e..7d98aba87d 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1044,16 +1044,18 @@ describe('given a class diagram with relationships, ', function () { }); it('should handle relation definitions with type only on right side', function () { - const str = 'classDiagram\n' + 'Class1 --|> Class02'; + const str = 'classDiagram\n' + 'Class1 --|> Class2'; parser.parse(str); const relations = parser.yy.getRelations(); + const class1 = parser.yy.getClass('Class1'); + const class2 = parser.yy.getClass('Class2'); - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(class1.id).toBe('Class1'); + expect(class2.id).toBe('Class2'); expect(relations[0].relation.type1).toBe('none'); - expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe(classDb.relationType.REALIZATION); expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); }); From c323293fe9bf5c7ec46521b822a621b4830bddbd Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sat, 26 Aug 2023 14:12:40 -0700 Subject: [PATCH 5/6] add comment --- packages/mermaid/src/dagre-wrapper/markers.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index 0c4e56be7d..a2abbe6f3c 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -20,6 +20,22 @@ const getSvgParent = (elem) => { return container; }; +/** + * Finds the parent background color of an SVG element. + * + * Used to make a "hollow" arrowhead for an arrow. + * + * We can't use a transparent fill, + * because the arrow line is behind the arrowhead + * (ideally we'd stop drawing the line behind the arrowhead, + * but this is pretty complicated to do). + * + * **Limitations**: + * - If the parent background color is a partial transparency, + * the arrowhead will also be partially transparent. + * - More complicated backgrounds like pictures/animations won't work. + * @param elem + */ const getBackgroundColor = (elem) => { let parent = getSvgParent(elem); From b93ad3220246fe12a92b309a379b347f8d4fcfb3 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 28 Aug 2023 04:42:29 -0700 Subject: [PATCH 6/6] update edge IDs to reference classes --- packages/mermaid/src/diagrams/class/classRenderer-v2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 59667bbdc6..cc30881135 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -230,7 +230,7 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr //Set relationship style and line type classes: 'relation', pattern: edge.relation.lineType == 1 ? 'dashed' : 'solid', - id: 'id' + cnt, + id: 'id_' + edge.id1 + '_' + edge.id2, // Set link type for rendering arrowhead: edge.type === 'arrow_open' ? 'none' : 'normal', //Set edge extra labels