diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 1581658b0..daee74ee9 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 57d092fdf..e42c0cad4 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 7b74aa819..a69a13a18 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 6197fe8ac..fb0b9c83b 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 7788fcc0c..88b46a666 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 e4afe2136..9ecc74157 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: