diff --git a/__mocks__/d3.ts b/__mocks__/d3.ts deleted file mode 100644 index 67f09b6f4f..0000000000 --- a/__mocks__/d3.ts +++ /dev/null @@ -1,67 +0,0 @@ -// @ts-nocheck TODO: Fix TS -import { vi } from 'vitest'; - -const NewD3 = function () { - /** - * - */ - function returnThis() { - return this; - } - return { - append: function () { - return NewD3(); - }, - lower: returnThis, - attr: returnThis, - style: returnThis, - text: returnThis, - 0: { - 0: { - getBBox: function () { - return { - height: 10, - width: 20, - }; - }, - }, - }, - }; -}; - -export const select = function () { - return new NewD3(); -}; - -export const selectAll = function () { - return new NewD3(); -}; - -export const curveBasis = 'basis'; -export const curveLinear = 'linear'; -export const curveCardinal = 'cardinal'; - -export const MockD3 = (name, parent) => { - const children = []; - const elem = { - get __children() { - return children; - }, - get __name() { - return name; - }, - get __parent() { - return parent; - }, - }; - elem.append = (name) => { - const mockElem = MockD3(name, elem); - children.push(mockElem); - return mockElem; - }; - elem.lower = vi.fn(() => elem); - elem.attr = vi.fn(() => elem); - elem.text = vi.fn(() => elem); - elem.style = vi.fn(() => elem); - return elem; -}; diff --git a/__mocks__/dagre-d3.ts b/__mocks__/dagre-d3.ts deleted file mode 100644 index a1a6775916..0000000000 --- a/__mocks__/dagre-d3.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { vi } from 'vitest'; - -// export const render = vi.fn(); diff --git a/__mocks__/entity-decode/browser.ts b/__mocks__/entity-decode/browser.ts deleted file mode 100644 index bd82d79fd9..0000000000 --- a/__mocks__/entity-decode/browser.ts +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function (txt: string) { - return txt; -}; diff --git a/demos/1871-1732.html b/demos/1871-1732.html new file mode 100644 index 0000000000..1d76617f8d --- /dev/null +++ b/demos/1871-1732.html @@ -0,0 +1,26 @@ + +
++ graph LR + A>Test]; + A <--> B; + + C[Test]; + C <--> D; ++ + + + + + diff --git a/demos/1871-c4.html b/demos/1871-c4.html new file mode 100644 index 0000000000..67ae8b4d73 --- /dev/null +++ b/demos/1871-c4.html @@ -0,0 +1,28 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"personBorder":"red"}}}%% + C4Context + Person(A, "A", "") + System(B, "B", "") + Rel(A, B, "Uses") ++ + + + + + diff --git a/demos/1871-class-v2.html b/demos/1871-class-v2.html new file mode 100644 index 0000000000..e8c81584ba --- /dev/null +++ b/demos/1871-class-v2.html @@ -0,0 +1,30 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + classDiagram-v2 + Animal <|-- Duck ++ + Marker should be blue: +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%% + classDiagram-v2 + Animal <|-- Duck ++ + + + + + diff --git a/demos/1871-class.html b/demos/1871-class.html new file mode 100644 index 0000000000..73e5988c65 --- /dev/null +++ b/demos/1871-class.html @@ -0,0 +1,34 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + classDiagram + A <|--|> B + C <--> D + E *--* F + G o--o H ++ Marker should be blue: + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%% + classDiagram + A <|--|> B + C <--> D + E *--* F + G o--o H ++ + + + + + diff --git a/demos/1871-er.html b/demos/1871-er.html new file mode 100644 index 0000000000..9d95497ce3 --- /dev/null +++ b/demos/1871-er.html @@ -0,0 +1,21 @@ + + + +
+ %%{init:{"theme":"base"}}%% + erDiagram + A ||--o{ B : label ++ + + + + diff --git a/demos/1871-flowchart.html b/demos/1871-flowchart.html new file mode 100644 index 0000000000..facbcf74cd --- /dev/null +++ b/demos/1871-flowchart.html @@ -0,0 +1,27 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + flowchart LR + A <--> B x--x C o--o D ++ Markers should be blue: +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%% + flowchart LR + A <--> B x--x C o--o D ++ + + + + + diff --git a/demos/1871-gantt.html b/demos/1871-gantt.html new file mode 100644 index 0000000000..a86dbb526a --- /dev/null +++ b/demos/1871-gantt.html @@ -0,0 +1,29 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + gantt + title A Gantt Diagram + dateFormat YYYY-MM-DD + section Section + A task :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d > ++ + + + + + diff --git a/demos/1871-git.html b/demos/1871-git.html new file mode 100644 index 0000000000..8d712fdd10 --- /dev/null +++ b/demos/1871-git.html @@ -0,0 +1,25 @@ + + +
+ %% {init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + gitGraph + commit + branch A + checkout A + commit ++ + + + + + diff --git a/demos/1871-graph.html b/demos/1871-graph.html new file mode 100644 index 0000000000..892b3f1cf5 --- /dev/null +++ b/demos/1871-graph.html @@ -0,0 +1,37 @@ + + +
+ graph LR + A>Test]; + A <--> B; + + C[Test]; + C <--> D; ++
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + graph LR + A <--> B x--x C o--o D ++ Markers should be blue: +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%% + graph LR + A <--> B x--x C o--o D ++ + + + + + diff --git a/demos/1871-journey.html b/demos/1871-journey.html new file mode 100644 index 0000000000..08c9a6605c --- /dev/null +++ b/demos/1871-journey.html @@ -0,0 +1,36 @@ + + + +
+ %%{init:{"theme":"base", "themeVariables": {"textColor":"red"}}}%% + journey + A: B ++ + Marker should be blue: +
+ %%{init:{"theme":"base", "themeVariables": {"textColor":"blue"}}}%% + journey + A: B ++ + + + + + + diff --git a/demos/1871-requirement.html b/demos/1871-requirement.html new file mode 100644 index 0000000000..cc633305da --- /dev/null +++ b/demos/1871-requirement.html @@ -0,0 +1,37 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + requirementDiagram + + requirement B { + id: 1 + text: the test text. + risk: high + verifymethod: test + } + + element A { + type: simulation + } + + A - satisfies -> B ++ + + + + + diff --git a/demos/1871-sequence.html b/demos/1871-sequence.html new file mode 100644 index 0000000000..998dfa6799 --- /dev/null +++ b/demos/1871-sequence.html @@ -0,0 +1,43 @@ + + + +
+ %%{init:{"theme":"base", "themeVariables": {"actorBorder":"red"}}}%% + sequenceDiagram + participant Alice + participant Bob + Alice ->> Bob: Hello Bob, how are you? ++
+ %%{init:{"theme":"base", "themeVariables": {"actorBorder":"blue"}}}%% + sequenceDiagram + participant Alice + participant Bob + Alice ->> Bob: Hello Bob, how are you? ++ + + + + + diff --git a/demos/1871-state-v2.html b/demos/1871-state-v2.html new file mode 100644 index 0000000000..37d9d851c5 --- /dev/null +++ b/demos/1871-state-v2.html @@ -0,0 +1,35 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + stateDiagram-v2 + [*] --> A + state A { + [*] --> B + }+ + Markers should be blue: +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%% + stateDiagram-v2 + [*] --> A + state A { + [*] --> B + } ++ + + + + + diff --git a/demos/1871-state.html b/demos/1871-state.html new file mode 100644 index 0000000000..7d80ba5d3e --- /dev/null +++ b/demos/1871-state.html @@ -0,0 +1,35 @@ + + +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%% + stateDiagram + [*] --> A + state A { + [*] --> B + }+ +
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%% + stateDiagram + [*] --> A + state A { + [*] --> B + } ++ + + + + + + diff --git a/packages/mermaid/src/__mocks__/mermaidAPI.ts b/packages/mermaid/src/__mocks__/mermaidAPI.ts deleted file mode 100644 index f15db139f4..0000000000 --- a/packages/mermaid/src/__mocks__/mermaidAPI.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Mocks for `./mermaidAPI`. - * - * We can't easily use `vi.spyOn(mermaidAPI, "function")` since the object is frozen with `Object.freeze()`. - */ -import * as configApi from '../config'; -import { vi } from 'vitest'; -import { addDiagrams } from '../diagram-api/diagram-orchestration'; -import Diagram from '../Diagram'; - -// Normally, we could just do the following to get the original `parse()` -// implementation, however, requireActual returns a promise and it's not documented how to use withing mock file. - -let hasLoadedDiagrams = false; -/** - * @param text - * @param parseError - */ -// eslint-disable-next-line @typescript-eslint/ban-types -function parse(text: string, parseError?: Function): boolean { - if (!hasLoadedDiagrams) { - addDiagrams(); - hasLoadedDiagrams = true; - } - const diagram = new Diagram(text, parseError); - return diagram.parse(text, parseError); -} - -// original version cannot be modified since it was frozen with `Object.freeze()` -export const mermaidAPI = { - render: vi.fn(), - parse, - parseDirective: vi.fn(), - initialize: vi.fn(), - getConfig: configApi.getConfig, - setConfig: configApi.setConfig, - getSiteConfig: configApi.getSiteConfig, - updateSiteConfig: configApi.updateSiteConfig, - reset: () => { - configApi.reset(); - }, - globalReset: () => { - configApi.reset(configApi.defaultConfig); - }, - defaultConfig: configApi.defaultConfig, -}; - -export default mermaidAPI; diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 6ed08e924f..4c50675322 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -3,6 +3,7 @@ import createLabel from './createLabel'; import { line, curveBasis, select } from 'd3'; import { getConfig } from '../config'; import utils from '../utils'; +import markers from '../markers'; import { evaluate } from '../diagrams/common/common'; let edgeLabels = {}; @@ -453,12 +454,14 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph break; } - const svgPath = elem + elem .append('path') .attr('d', lineFunction(lineData)) .attr('id', edge.id) .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '')) - .attr('style', edge.style); + .attr('style', edge.style) + .attr('marker-start', markers.markerUrl(elem, startMarkerName(edge.arrowTypeStart))) + .attr('marker-end', markers.markerUrl(elem, endMarkerName(edge.arrowTypeEnd))); // DEBUG code, adds a red circle at each edge coordinate // edge.points.forEach(point => { @@ -471,81 +474,9 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph // .attr('cy', point.y); // }); - let url = ''; - // // TODO: Can we load this config only from the rendered graph type? - if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); - } log.info('arrowTypeStart', edge.arrowTypeStart); log.info('arrowTypeEnd', edge.arrowTypeEnd); - switch (edge.arrowTypeStart) { - case 'arrow_cross': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')'); - break; - case 'arrow_point': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')'); - break; - case 'arrow_barb': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')'); - break; - case 'arrow_circle': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')'); - break; - case 'aggregation': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')'); - break; - case 'extension': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')'); - break; - case 'composition': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')'); - break; - case 'dependency': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')'); - break; - case 'lollipop': - svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')'); - break; - default: - } - switch (edge.arrowTypeEnd) { - case 'arrow_cross': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')'); - break; - case 'arrow_point': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')'); - break; - case 'arrow_barb': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')'); - break; - case 'arrow_circle': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')'); - break; - case 'aggregation': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')'); - break; - case 'extension': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')'); - break; - case 'composition': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')'); - break; - case 'dependency': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')'); - break; - case 'lollipop': - svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')'); - break; - default: - } let paths = {}; if (pointsHasChanged) { paths.updatedPath = points; @@ -553,3 +484,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph paths.originalPath = edge.points; return paths; }; + +const markerName = (arrowType) => arrowType && arrowType.replace('arrow_', ''); +const startMarkerName = (arrowType) => arrowType && markerName(arrowType) + 'Start'; +const endMarkerName = (arrowType) => arrowType && markerName(arrowType) + 'End'; diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index c231eb3e5b..92e665e7e6 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -1,20 +1,16 @@ /** Setup arrow head and define the marker. The result is appended to the svg. */ -import { log } from '../logger'; +import { appendMarker } from '../markers'; // Only add the number of markers that the diagram needs -const insertMarkers = (elem, markerArray, type, id) => { +export const insertMarkers = (elem, markerArray, type, id) => { markerArray.forEach((markerName) => { markers[markerName](elem, type, id); }); }; -const extension = (elem, type, id) => { - log.trace('Making markers for ', id); - elem - .append('defs') - .append('marker') - .attr('id', type + '-extensionStart') +const extension = (elem, type) => { + appendMarker(elem, 'extensionStart') .attr('class', 'marker extension ' + type) .attr('refX', 0) .attr('refY', 7) @@ -24,10 +20,7 @@ const extension = (elem, type, id) => { .append('path') .attr('d', 'M 1,7 L18,13 V 1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', type + '-extensionEnd') + appendMarker(elem, 'extensionEnd') .attr('class', 'marker extension ' + type) .attr('refX', 19) .attr('refY', 7) @@ -39,10 +32,7 @@ const extension = (elem, type, id) => { }; const composition = (elem, type) => { - elem - .append('defs') - .append('marker') - .attr('id', type + '-compositionStart') + appendMarker(elem, 'compositionStart') .attr('class', 'marker composition ' + type) .attr('refX', 0) .attr('refY', 7) @@ -52,10 +42,7 @@ const composition = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', type + '-compositionEnd') + appendMarker(elem, 'compositionEnd') .attr('class', 'marker composition ' + type) .attr('refX', 19) .attr('refY', 7) @@ -65,11 +52,9 @@ const composition = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); }; + const aggregation = (elem, type) => { - elem - .append('defs') - .append('marker') - .attr('id', type + '-aggregationStart') + appendMarker(elem, 'aggregationStart') .attr('class', 'marker aggregation ' + type) .attr('refX', 0) .attr('refY', 7) @@ -79,10 +64,7 @@ const aggregation = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', type + '-aggregationEnd') + appendMarker(elem, 'aggregationEnd') .attr('class', 'marker aggregation ' + type) .attr('refX', 19) .attr('refY', 7) @@ -92,11 +74,9 @@ const aggregation = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); }; + const dependency = (elem, type) => { - elem - .append('defs') - .append('marker') - .attr('id', type + '-dependencyStart') + appendMarker(elem, 'dependencyStart') .attr('class', 'marker dependency ' + type) .attr('refX', 0) .attr('refY', 7) @@ -106,10 +86,7 @@ const dependency = (elem, type) => { .append('path') .attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', type + '-dependencyEnd') + appendMarker(elem, 'dependencyEnd') .attr('class', 'marker dependency ' + type) .attr('refX', 19) .attr('refY', 7) @@ -119,11 +96,9 @@ const dependency = (elem, type) => { .append('path') .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); }; + const lollipop = (elem, type) => { - elem - .append('defs') - .append('marker') - .attr('id', type + '-lollipopStart') + appendMarker(elem, 'lollipopStart') .attr('class', 'marker lollipop ' + type) .attr('refX', 0) .attr('refY', 7) @@ -137,11 +112,10 @@ const lollipop = (elem, type) => { .attr('cy', 7) .attr('r', 6); }; + const point = (elem, type) => { - elem - .append('marker') - .attr('id', type + '-pointEnd') - .attr('class', 'marker ' + type) + appendMarker(elem, 'pointEnd') + .attr('class', 'marker point ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', 10) .attr('refY', 5) @@ -154,10 +128,9 @@ const point = (elem, type) => { .attr('class', 'arrowMarkerPath') .style('stroke-width', 1) .style('stroke-dasharray', '1,0'); - elem - .append('marker') - .attr('id', type + '-pointStart') - .attr('class', 'marker ' + type) + + appendMarker(elem, 'pointStart') + .attr('class', 'marker point ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', 0) .attr('refY', 5) @@ -171,10 +144,9 @@ const point = (elem, type) => { .style('stroke-width', 1) .style('stroke-dasharray', '1,0'); }; + const circle = (elem, type) => { - elem - .append('marker') - .attr('id', type + '-circleEnd') + appendMarker(elem, 'circleEnd') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', 11) @@ -191,9 +163,7 @@ const circle = (elem, type) => { .style('stroke-width', 1) .style('stroke-dasharray', '1,0'); - elem - .append('marker') - .attr('id', type + '-circleStart') + appendMarker(elem, 'circleStart') .attr('class', 'marker ' + type) .attr('viewBox', '0 0 10 10') .attr('refX', -1) @@ -210,10 +180,9 @@ const circle = (elem, type) => { .style('stroke-width', 1) .style('stroke-dasharray', '1,0'); }; + const cross = (elem, type) => { - elem - .append('marker') - .attr('id', type + '-crossEnd') + appendMarker(elem, 'crossEnd') .attr('class', 'marker cross ' + type) .attr('viewBox', '0 0 11 11') .attr('refX', 12) @@ -229,9 +198,7 @@ const cross = (elem, type) => { .style('stroke-width', 2) .style('stroke-dasharray', '1,0'); - elem - .append('marker') - .attr('id', type + '-crossStart') + appendMarker(elem, 'crossStart') .attr('class', 'marker cross ' + type) .attr('viewBox', '0 0 11 11') .attr('refX', -1) @@ -247,11 +214,10 @@ const cross = (elem, type) => { .style('stroke-width', 2) .style('stroke-dasharray', '1,0'); }; + const barb = (elem, type) => { - elem - .append('defs') - .append('marker') - .attr('id', type + '-barbEnd') + appendMarker(elem, 'barbEnd') + .attr('class', 'marker barb ' + type) .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) @@ -259,7 +225,8 @@ const barb = (elem, type) => { .attr('markerUnits', 'strokeWidth') .attr('orient', 'auto') .append('path') - .attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z'); + .attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z') + .attr('class', 'arrowMarkerPath'); }; // TODO rename the class diagram markers to something shape descriptive and semantic free @@ -274,4 +241,5 @@ const markers = { cross, barb, }; + export default insertMarkers; diff --git a/packages/mermaid/src/dagre-wrapper/patterns.js b/packages/mermaid/src/dagre-wrapper/patterns.js index 75afa8bcc0..b4bc4a41c2 100644 --- a/packages/mermaid/src/dagre-wrapper/patterns.js +++ b/packages/mermaid/src/dagre-wrapper/patterns.js @@ -1,3 +1,4 @@ +import { appendMarker } from '../markers'; /** Setup arrow head and define the marker. The result is appended to the svg. */ // import { log } from '../logger'; @@ -30,11 +31,8 @@ const insertPatterns = (elem, patternArray, type, id) => { ; */ } -const dots = (elem, type) => { - elem - .append('defs') - .append('marker') - .attr('id', type + '-barbEnd') +const dots = (elem) => { + appendMarker(elem, 'barbEnd') .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index 5666d9f844..1946822d7b 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -1,5 +1,6 @@ import common from '../common/common'; import { sanitizeUrl } from '@braintree/sanitize-url'; +import { appendMarker, markerUrl } from '../../markers'; export const drawRect = function (elem, rectData) { const rectElem = elem.append('rect'); @@ -220,7 +221,6 @@ export const drawRels = (elem, rels, conf) => { let offsetX = rel.offsetX ? parseInt(rel.offsetX) : 0; let offsetY = rel.offsetY ? parseInt(rel.offsetY) : 0; - let url = ''; if (i === 0) { let line = relsElem.append('line'); line.attr('x1', rel.startPoint.x); @@ -231,9 +231,9 @@ export const drawRels = (elem, rels, conf) => { line.attr('stroke-width', '1'); line.attr('stroke', strokeColor); line.style('fill', 'none'); - if (rel.type !== 'rel_b') line.attr('marker-end', 'url(' + url + '#arrowhead)'); + if (rel.type !== 'rel_b') line.attr('marker-end', markerUrl(elem, 'arrowhead)')); if (rel.type === 'birel' || rel.type === 'rel_b') - line.attr('marker-start', 'url(' + url + '#arrowend)'); + line.attr('marker-start', markerUrl(elem, 'arrowend')); i = -1; } else { let line = relsElem.append('path'); @@ -256,9 +256,9 @@ export const drawRels = (elem, rels, conf) => { .replaceAll('stopx', rel.endPoint.x) .replaceAll('stopy', rel.endPoint.y) ); - if (rel.type !== 'rel_b') line.attr('marker-end', 'url(' + url + '#arrowhead)'); + if (rel.type !== 'rel_b') line.attr('marker-end', markerUrl(elem, 'arrowhead)')); if (rel.type === 'birel' || rel.type === 'rel_b') - line.attr('marker-start', 'url(' + url + '#arrowend)'); + line.attr('marker-start', markerUrl(elem, 'arrowend)')); } let messageConf = conf.messageFont(); @@ -633,10 +633,7 @@ export const insertClockIcon = function (elem) { * @param elem */ export const insertArrowHead = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'arrowhead') + appendMarker(elem, 'arrowhead') .attr('refX', 9) .attr('refY', 5) .attr('markerUnits', 'userSpaceOnUse') @@ -647,10 +644,7 @@ export const insertArrowHead = function (elem) { .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead }; export const insertArrowEnd = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'arrowend') + appendMarker(elem, 'arrowend') .attr('refX', 1) .attr('refY', 5) .attr('markerUnits', 'userSpaceOnUse') @@ -666,10 +660,7 @@ export const insertArrowEnd = function (elem) { * @param {any} elem */ export const insertArrowFilledHead = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'filled-head') + appendMarker(elem, 'filled-head') .attr('refX', 18) .attr('refY', 7) .attr('markerWidth', 20) @@ -684,10 +675,7 @@ export const insertArrowFilledHead = function (elem) { * @param {any} elem */ export const insertDynamicNumber = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'sequencenumber') + appendMarker(elem, 'sequencenumber') .attr('refX', 15) .attr('refY', 15) .attr('markerWidth', 60) @@ -705,10 +693,7 @@ export const insertDynamicNumber = function (elem) { * @param {any} elem */ export const insertArrowCrossHead = function (elem) { - const defs = elem.append('defs'); - const marker = defs - .append('marker') - .attr('id', 'crosshead') + const marker = appendMarker(elem, 'crosshead') .attr('markerWidth', 15) .attr('markerHeight', 8) .attr('orient', 'auto') diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js index c1236afea7..839807662a 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer.js +++ b/packages/mermaid/src/diagrams/class/classRenderer.js @@ -6,6 +6,7 @@ import svgDraw from './svgDraw'; import { configureSvgSize } from '../../setupGraphViewbox'; import { getConfig } from '../../config'; import addSVGAccessibilityFields from '../../accessibility'; +import { appendMarker } from '../../markers'; let idCache = {}; const padding = 20; @@ -30,10 +31,7 @@ const getGraphId = function (label) { * @param {SVGSVGElement} elem The SVG element to append to */ const insertMarkers = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'extensionStart') + appendMarker(elem, 'extensionStart') .attr('class', 'extension') .attr('refX', 0) .attr('refY', 7) @@ -43,10 +41,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 1,7 L18,13 V 1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', 'extensionEnd') + appendMarker(elem, 'extensionEnd') .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) @@ -55,10 +50,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead - elem - .append('defs') - .append('marker') - .attr('id', 'compositionStart') + appendMarker(elem, 'compositionStart') .attr('class', 'extension') .attr('refX', 0) .attr('refY', 7) @@ -68,10 +60,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', 'compositionEnd') + appendMarker(elem, 'compositionEnd') .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) @@ -80,10 +69,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', 'aggregationStart') + appendMarker(elem, 'aggregationStart') .attr('class', 'extension') .attr('refX', 0) .attr('refY', 7) @@ -93,10 +79,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', 'aggregationEnd') + appendMarker(elem, 'aggregationEnd') .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) @@ -105,10 +88,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', 'dependencyStart') + appendMarker(elem, 'dependencyStart') .attr('class', 'extension') .attr('refX', 0) .attr('refY', 7) @@ -118,10 +98,7 @@ const insertMarkers = function (elem) { .append('path') .attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z'); - elem - .append('defs') - .append('marker') - .attr('id', 'dependencyEnd') + appendMarker(elem, 'dependencyEnd') .attr('refX', 19) .attr('refY', 7) .attr('markerWidth', 20) diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 3d44e94b45..c82bb76d7c 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -2,6 +2,7 @@ import { line, curveBasis } from 'd3'; import utils from '../../utils'; import { log } from '../../logger'; import { parseGenericTypes } from '../common/common'; +import { markerUrl } from '../../markers'; let edgeCount = 0; export const drawEdge = function (elem, path, relation, conf, diagObj) { @@ -40,32 +41,15 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { .attr('d', lineFunction(lineData)) .attr('id', 'edge' + edgeCount) .attr('class', 'relation'); - let url = ''; - if (conf.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); - } if (relation.relation.lineType == 1) { svgPath.attr('class', 'relation dashed-line'); } if (relation.relation.type1 !== 'none') { - svgPath.attr( - 'marker-start', - 'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')' - ); + svgPath.attr('marker-start', markerUrl(elem, getRelationType(relation.relation.type1))); } if (relation.relation.type2 !== 'none') { - svgPath.attr( - 'marker-end', - 'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')' - ); + svgPath.attr('marker-end', markerUrl(elem, getRelationType(relation.relation.type2))); } let x, y; diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 9f6ae2cdbb..8c26262123 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -99,28 +99,6 @@ const breakToPlaceholder = (s: string): string => { return s.replace(lineBreakRegex, '#br#'); }; -/** - * Gets the current URL - * - * @param {boolean} useAbsolute Whether to return the absolute URL or not - * @returns {string} The current URL - */ -const getUrl = (useAbsolute: boolean): string => { - let url = ''; - if (useAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replaceAll(/\(/g, '\\('); - url = url.replaceAll(/\)/g, '\\)'); - } - - return url; -}; - /** * Converts a string/boolean into a boolean * @@ -161,6 +139,5 @@ export default { splitBreaks, lineBreakRegex, removeScript, - getUrl, evaluate, }; diff --git a/packages/mermaid/src/diagrams/er/erMarkers.js b/packages/mermaid/src/diagrams/er/erMarkers.js index 9484117725..5eb7d01aff 100644 --- a/packages/mermaid/src/diagrams/er/erMarkers.js +++ b/packages/mermaid/src/diagrams/er/erMarkers.js @@ -1,3 +1,5 @@ +import { appendMarker } from '../../markers'; + const ERMarkers = { ONLY_ONE_START: 'ONLY_ONE_START', ONLY_ONE_END: 'ONLY_ONE_END', @@ -18,10 +20,7 @@ const ERMarkers = { const insertMarkers = function (elem, conf) { let marker; - elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ONLY_ONE_START) + appendMarker(elem, ERMarkers.ONLY_ONE_START) .attr('refX', 0) .attr('refY', 9) .attr('markerWidth', 18) @@ -32,10 +31,7 @@ const insertMarkers = function (elem, conf) { .attr('fill', 'none') .attr('d', 'M9,0 L9,18 M15,0 L15,18'); - elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ONLY_ONE_END) + appendMarker(elem, ERMarkers.ONLY_ONE_END) .attr('refX', 18) .attr('refY', 9) .attr('markerWidth', 18) @@ -46,10 +42,7 @@ const insertMarkers = function (elem, conf) { .attr('fill', 'none') .attr('d', 'M3,0 L3,18 M9,0 L9,18'); - marker = elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ZERO_OR_ONE_START) + marker = appendMarker(elem, ERMarkers.ZERO_OR_ONE_START) .attr('refX', 0) .attr('refY', 9) .attr('markerWidth', 30) @@ -64,10 +57,7 @@ const insertMarkers = function (elem, conf) { .attr('r', 6); marker.append('path').attr('stroke', conf.stroke).attr('fill', 'none').attr('d', 'M9,0 L9,18'); - marker = elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ZERO_OR_ONE_END) + marker = appendMarker(elem, ERMarkers.ZERO_OR_ONE_END) .attr('refX', 30) .attr('refY', 9) .attr('markerWidth', 30) @@ -82,10 +72,7 @@ const insertMarkers = function (elem, conf) { .attr('r', 6); marker.append('path').attr('stroke', conf.stroke).attr('fill', 'none').attr('d', 'M21,0 L21,18'); - elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ONE_OR_MORE_START) + appendMarker(elem, ERMarkers.ONE_OR_MORE_START) .attr('refX', 18) .attr('refY', 18) .attr('markerWidth', 45) @@ -96,10 +83,7 @@ const insertMarkers = function (elem, conf) { .attr('fill', 'none') .attr('d', 'M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27'); - elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ONE_OR_MORE_END) + appendMarker(elem, ERMarkers.ONE_OR_MORE_END) .attr('refX', 27) .attr('refY', 18) .attr('markerWidth', 45) @@ -110,10 +94,7 @@ const insertMarkers = function (elem, conf) { .attr('fill', 'none') .attr('d', 'M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18'); - marker = elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ZERO_OR_MORE_START) + marker = appendMarker(elem, ERMarkers.ZERO_OR_MORE_START) .attr('refX', 18) .attr('refY', 18) .attr('markerWidth', 57) @@ -132,10 +113,7 @@ const insertMarkers = function (elem, conf) { .attr('fill', 'none') .attr('d', 'M0,18 Q18,0 36,18 Q18,36 0,18'); - marker = elem - .append('defs') - .append('marker') - .attr('id', ERMarkers.ZERO_OR_MORE_END) + marker = appendMarker(elem, ERMarkers.ZERO_OR_MORE_END) .attr('refX', 39) .attr('refY', 18) .attr('markerWidth', 57) diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index a6277f27da..602447a134 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -1,4 +1,4 @@ -import graphlib from 'graphlib'; +import { Graph } from 'graphlib'; import { line, curveBasis, select } from 'd3'; import dagre from 'dagre'; import { getConfig } from '../../config'; @@ -7,6 +7,7 @@ import erMarkers from './erMarkers'; import { configureSvgSize } from '../../setupGraphViewbox'; import addSVGAccessibilityFields from '../../accessibility'; import { parseGenericTypes } from '../common/common'; +import { markerUrl } from '../../markers'; import { v4 as uuid4 } from 'uuid'; /** Regex used to remove chars from the entity name so the result can be used in an id */ @@ -470,59 +471,37 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) { svgPath.attr('stroke-dasharray', '8,8'); } - // TODO: Understand this better - let url = ''; - if (conf.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); - } - // Decide which start and end markers it needs. It may be possible to be more concise here // by reversing a start marker to make an end marker...but this will do for now // Note that the 'A' entity's marker is at the end of the relationship and the 'B' entity's marker is at the start switch (rel.relSpec.cardA) { case diagObj.db.Cardinality.ZERO_OR_ONE: - svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')'); + svgPath.attr('marker-end', markerUrl(svgPath, erMarkers.ERMarkers.ZERO_OR_ONE_END)); break; case diagObj.db.Cardinality.ZERO_OR_MORE: - svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); + svgPath.attr('marker-end', markerUrl(svgPath, erMarkers.ERMarkers.ZERO_OR_MORE_END)); break; case diagObj.db.Cardinality.ONE_OR_MORE: - svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); + svgPath.attr('marker-end', markerUrl(svgPath, erMarkers.ERMarkers.ONE_OR_MORE_END)); break; case diagObj.db.Cardinality.ONLY_ONE: - svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); + svgPath.attr('marker-end', markerUrl(svgPath, erMarkers.ERMarkers.ONLY_ONE_END)); break; } switch (rel.relSpec.cardB) { case diagObj.db.Cardinality.ZERO_OR_ONE: - svgPath.attr( - 'marker-start', - 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')' - ); + svgPath.attr('marker-start', markerUrl(svgPath, erMarkers.ERMarkers.ZERO_OR_ONE_STAR)); break; case diagObj.db.Cardinality.ZERO_OR_MORE: - svgPath.attr( - 'marker-start', - 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')' - ); + svgPath.attr('marker-start', markerUrl(svgPath, erMarkers.ERMarkers.ZERO_OR_MORE_STAR)); break; case diagObj.db.Cardinality.ONE_OR_MORE: - svgPath.attr( - 'marker-start', - 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')' - ); + svgPath.attr('marker-start', markerUrl(svgPath, erMarkers.ERMarkers.ONE_OR_MORE_STAR)); break; case diagObj.db.Cardinality.ONLY_ONE: - svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); + svgPath.attr('marker-start', markerUrl(svgPath, erMarkers.ERMarkers.ONLY_ONE_START)); break; } @@ -623,7 +602,7 @@ export const draw = function (text, id, _version, diagObj) { // the direction from parent to child in a one-to-many as this influences graphlib to // put the parent above the child (does it?), which is intuitive. Most relationships // in ER diagrams are one-to-many. - g = new graphlib.Graph({ + g = new Graph({ multigraph: true, directed: true, compound: false, diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js index 41868e2035..49ecaa921c 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js @@ -2,6 +2,8 @@ import flowDb from './flowDb'; import flowParser from './parser/flow'; import flowRenderer from './flowRenderer'; import Diagram from '../../Diagram'; +import * as d3 from 'd3'; + import { addDiagrams } from '../../diagram-api/diagram-orchestration'; addDiagrams(); @@ -31,7 +33,6 @@ describe('when using mermaid and ', function () { it('should handle edges without text', function () { const diag = new Diagram('graph TD;A-->B;'); - diag.db.getVertices(); const edges = diag.db.getEdges(); const mockG = { @@ -62,21 +63,18 @@ describe('when using mermaid and ', function () { }); it('should handle edges with styles defined', function () { - const diag = new Diagram('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;'); - diag.db.getVertices(); - const edges = diag.db.getEdges(); + const diagram = new Diagram('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;'); + const edges = diagram.db.getEdges(); const mockG = { - setEdge: function (start, end, options) { - expect(start).toContain('flowchart-A-'); - expect(end).toContain('flowchart-B-'); - expect(options.arrowhead).toBe('none'); + setEdge: function (_, __, options) { expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;'); }, }; - flowRenderer.addEdges(edges, mockG, diag); + flowRenderer.addEdges(edges, mockG, diagram); }); + it('should handle edges with interpolation defined', function () { const diag = new Diagram('graph TD;A---B; linkStyle 0 interpolate basis'); diag.db.getVertices(); @@ -84,10 +82,10 @@ describe('when using mermaid and ', function () { const mockG = { setEdge: function (start, end, options) { - expect(start).toContain('flowchart-A-'); - expect(end).toContain('flowchart-B-'); + expect(start).toMatch(/^flowchart-A-\d+$/); + expect(end).toMatch(/^flowchart-B-\d+$/); expect(options.arrowhead).toBe('none'); - expect(options.curve).toBe('basis'); // mocked as string + expect(options.curve).toBe(d3.curveBasis); }, }; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 0c3aa3623e..e6bc90426b 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -9,6 +9,7 @@ import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import flowChartShapes from './flowChartShapes'; import addSVGAccessibilityFields from '../../accessibility'; +import { appendMarker } from '../../markers'; const conf = {}; export const setConf = function (cnf) { @@ -377,9 +378,7 @@ export const draw = function (text, id, _version, diagObj) { // Add our custom arrow - an empty arrowhead render.arrows().none = function normal(parent, id, edge, type) { - const marker = parent - .append('marker') - .attr('id', id) + const marker = appendMarker(parent, id) .attr('viewBox', '0 0 10 10') .attr('refX', 9) .attr('refY', 5) @@ -394,9 +393,7 @@ export const draw = function (text, id, _version, diagObj) { // Override normal arrowhead defined in d3. Remove style & add class to allow css styling. render.arrows().normal = function normal(parent, id) { - const marker = parent - .append('marker') - .attr('id', id) + const marker = appendMarker(parent, id) .attr('viewBox', '0 0 10 10') .attr('refX', 9) .attr('refY', 5) diff --git a/packages/mermaid/src/diagrams/requirement/requirementMarkers.js b/packages/mermaid/src/diagrams/requirement/requirementMarkers.js index 96e42d78da..3bc5c679d6 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementMarkers.js +++ b/packages/mermaid/src/diagrams/requirement/requirementMarkers.js @@ -1,13 +1,12 @@ +import { appendMarker } from '../../markers'; + const ReqMarkers = { CONTAINS: 'contains', ARROW: 'arrow', }; const insertLineEndings = (parentNode, conf) => { - let containsNode = parentNode - .append('defs') - .append('marker') - .attr('id', ReqMarkers.CONTAINS + '_line_ending') + let containsNode = appendMarker(parentNode, ReqMarkers.CONTAINS + '_line_ending') .attr('refX', 0) .attr('refY', conf.line_height / 2) .attr('markerWidth', conf.line_height) @@ -42,10 +41,7 @@ const insertLineEndings = (parentNode, conf) => { // .attr('stroke', conf.rect_border_color) .attr('stroke-width', 1); - parentNode - .append('defs') - .append('marker') - .attr('id', ReqMarkers.ARROW + '_line_ending') + appendMarker(parentNode, ReqMarkers.ARROW + '_line_ending') .attr('refX', conf.line_height) .attr('refY', 0.5 * conf.line_height) .attr('markerWidth', conf.line_height) diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index d10c43066c..fc53ef5014 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js +++ b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js @@ -3,10 +3,10 @@ import dagre from 'dagre'; import graphlib from 'graphlib'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; -import common from '../common/common'; import markers from './requirementMarkers'; import { getConfig } from '../../config'; import addSVGAccessibilityFields from '../../accessibility'; +import { markerUrl } from '../../markers'; let conf = {}; let relCnt = 0; @@ -170,21 +170,10 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) { .attr('fill', 'none'); if (rel.type == diagObj.db.Relationships.CONTAINS) { - svgPath.attr( - 'marker-start', - 'url(' + common.getUrl(conf.arrowMarkerAbsolute) + '#' + rel.type + '_line_ending' + ')' - ); + svgPath.attr('marker-start', markerUrl(svg, rel.type + '_line_ending')); } else { svgPath.attr('stroke-dasharray', '10,7'); - svgPath.attr( - 'marker-end', - 'url(' + - common.getUrl(conf.arrowMarkerAbsolute) + - '#' + - markers.ReqMarkers.ARROW + - '_line_ending' + - ')' - ); + svgPath.attr('marker-end', markerUrl(svg, markers.ReqMarkers.ARROW + '_line_ending')); } addEdgeLabel(svg, svgPath, conf, `<<${rel.type}>>`); diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 5aebd1e3a5..18bbd2f0da 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1808,15 +1808,6 @@ describe('when rendering a sequenceDiagram with directives', function () { mermaidAPI.initialize({ sequence: conf }); }); - let conf; - beforeEach(function () { - mermaidAPI.reset(); - // diagram.db = sequenceDb; - diagram.db.clear(); - conf = diagram.db.getConfig(); - diagram.renderer.bounds.init(); - }); - it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', function () { const str = ` %%{init: { "theme": "dark", "logLevel": 1 } }%% diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 19352ca723..a180202262 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; import addSVGAccessibilityFields from '../../accessibility'; +import { markerUrl } from '../../markers'; let conf = {}; @@ -238,7 +239,7 @@ const drawNote = function (elem, noteModel) { const textHeight = Math.round( textElem - .map((te) => (te._groups || te)[0][0].getBBox().height) + .map((te) => (te._groups || te)[0][0]?.getBBox().height || 0) .reduce((acc, curr) => acc + curr) ); @@ -408,35 +409,23 @@ const drawMessage = function (diagram, msgModel, lineStarty, diagObj) { line.attr('class', 'messageLine0'); } - let url = ''; - if (conf.arrowMarkerAbsolute) { - url = - window.location.protocol + - '//' + - window.location.host + - window.location.pathname + - window.location.search; - url = url.replace(/\(/g, '\\('); - url = url.replace(/\)/g, '\\)'); - } - line.attr('stroke-width', 2); line.attr('stroke', 'none'); // handled by theme/css anyway line.style('fill', 'none'); // remove any fill colour if (type === diagObj.db.LINETYPE.SOLID || type === diagObj.db.LINETYPE.DOTTED) { - line.attr('marker-end', 'url(' + url + '#arrowhead)'); + line.attr('marker-end', markerUrl(line, 'arrowhead)')); } if (type === diagObj.db.LINETYPE.SOLID_POINT || type === diagObj.db.LINETYPE.DOTTED_POINT) { - line.attr('marker-end', 'url(' + url + '#filled-head)'); + line.attr('marker-end', markerUrl(line, 'ailled-head)')); } if (type === diagObj.db.LINETYPE.SOLID_CROSS || type === diagObj.db.LINETYPE.DOTTED_CROSS) { - line.attr('marker-end', 'url(' + url + '#crosshead)'); + line.attr('marker-end', markerUrl(line, 'crosshead)')); } // add node number if (sequenceVisible || conf.showSequenceNumbers) { - line.attr('marker-start', 'url(' + url + '#sequencenumber)'); + line.attr('marker-start', markerUrl(line, 'sequencenumber)')); diagram .append('text') .attr('x', startx) diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index fd70871e04..90e45a97dd 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -1,31 +1,20 @@ import common from '../common/common'; import { addFunction } from '../../interactionDb'; import { sanitizeUrl } from '@braintree/sanitize-url'; +import { appendMarker } from '../../markers'; -export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (typeof rectData.class !== 'undefined') { - rectElem.attr('class', rectData.class); - } - - return rectElem; -}; - -// const sanitizeUrl = function (s) { -// return s -// .replace(/&/g, '&') -// .replace(/ + elem + .append('rect') + .attr('x', data.x) + .attr('y', data.y) + .attr('fill', data.fill) + .attr('stroke', data.stroke) + .attr('width', data.width) + .attr('height', data.height) + .attr('rx', data.rx) + .attr('ry', data.ry) + .attr('class', data.class); const addPopupInteraction = (id, actorCnt) => { addFunction(() => { @@ -267,7 +256,7 @@ export const drawText = function (elem, textData) { typeof textData.textMargin !== 'undefined' && textData.textMargin > 0 ) { - textHeight += (textElem._groups || textElem)[0][0].getBBox().height; + textHeight += (textElem._groups || textElem)[0][0]?.getBBox().height || 0; prevTextHeight = textHeight; } @@ -400,14 +389,9 @@ const drawActorTypeParticipant = function (elem, actor, conf) { conf ); - let height = actor.height; - if (rectElem.node) { - const bounds = rectElem.node().getBBox(); - actor.height = bounds.height; - height = bounds.height; - } + actor.height = rectElem.node()?.getBBox().height || actor.height; - return height; + return actor.height; }; const drawActorTypeActor = function (elem, actor, conf) { @@ -642,6 +626,7 @@ export const drawBackgroundRect = function (elem, bounds) { class: 'rect', }); rectElem.lower(); + return rectElem; }; export const insertDatabaseIcon = function (elem) { @@ -695,10 +680,7 @@ export const insertClockIcon = function (elem) { * @param elem */ export const insertArrowHead = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'arrowhead') + appendMarker(elem, 'arrowhead') .attr('refX', 9) .attr('refY', 5) .attr('markerUnits', 'userSpaceOnUse') @@ -714,10 +696,7 @@ export const insertArrowHead = function (elem) { * @param {any} elem */ export const insertArrowFilledHead = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'filled-head') + appendMarker(elem, 'filled-head') .attr('refX', 18) .attr('refY', 7) .attr('markerWidth', 20) @@ -732,10 +711,7 @@ export const insertArrowFilledHead = function (elem) { * @param {any} elem */ export const insertSequenceNumber = function (elem) { - elem - .append('defs') - .append('marker') - .attr('id', 'sequencenumber') + appendMarker(elem, 'sequencenumber') .attr('refX', 15) .attr('refY', 15) .attr('markerWidth', 60) @@ -753,10 +729,7 @@ export const insertSequenceNumber = function (elem) { * @param {any} elem */ export const insertArrowCrossHead = function (elem) { - const defs = elem.append('defs'); - const marker = defs - .append('marker') - .attr('id', 'crosshead') + const marker = appendMarker(elem, 'crosshead') .attr('markerWidth', 15) .attr('markerHeight', 8) .attr('orient', 'auto') diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index 580dafe896..078a6ee53f 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -1,135 +1,118 @@ import svgDraw from './svgDraw'; -import { MockD3 } from 'd3'; +import { select } from 'd3'; describe('svgDraw', function () { - describe('drawRect', function () { - it('should append a rectangle', function () { - const svg = MockD3('svg'); - svgDraw.drawRect(svg, { - x: 10, - y: 10, - fill: '#ccc', - stroke: 'red', - width: '20', - height: '20', - rx: '10', - ry: '10', - class: 'unitTestRectangleClass', - }); - expect(svg.__children.length).toBe(1); - const rect = svg.__children[0]; - expect(rect.__name).toBe('rect'); - expect(rect.attr).toHaveBeenCalledWith('x', 10); - expect(rect.attr).toHaveBeenCalledWith('y', 10); - expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc'); - expect(rect.attr).toHaveBeenCalledWith('stroke', 'red'); - expect(rect.attr).toHaveBeenCalledWith('width', '20'); - expect(rect.attr).toHaveBeenCalledWith('height', '20'); - expect(rect.attr).toHaveBeenCalledWith('rx', '10'); - expect(rect.attr).toHaveBeenCalledWith('ry', '10'); - expect(rect.attr).toHaveBeenCalledWith('class', 'unitTestRectangleClass'); - }); - it('should not add the class attribute if a class isn`t provided', () => { - const svg = MockD3('svg'); - svgDraw.drawRect(svg, { - x: 10, - y: 10, - fill: '#ccc', - stroke: 'red', - width: '20', - height: '20', - rx: '10', - ry: '10', + let svg; + + beforeEach(() => { + document.body.innerHTML = ''; + svg = select('svg'); + }); + + describe('drawRect', () => { + it('should append a rectangle', () => { + const rect = svgDraw.drawRect(svg, { + x: '10', + y: '20', + width: '30', + height: '40', + rx: '50', + ry: '60', + fill: 'red', + stroke: 'blue', + class: 'test-class', }); - expect(svg.__children.length).toBe(1); - const rect = svg.__children[0]; - expect(rect.__name).toBe('rect'); - expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc'); - expect(rect.attr).not.toHaveBeenCalledWith('class', expect.anything()); + + expect(svg.select('rect').size()).toBe(1); + + expect(rect.attr('x')).toBe('10'); + expect(rect.attr('y')).toBe('20'); + expect(rect.attr('width')).toBe('30'); + expect(rect.attr('height')).toBe('40'); + expect(rect.attr('rx')).toBe('50'); + expect(rect.attr('ry')).toBe('60'); + expect(rect.attr('fill')).toBe('red'); + expect(rect.attr('stroke')).toBe('blue'); + expect(rect.attr('class')).toBe('test-class'); }); }); + describe('drawText', function () { - it('should append a single element', function () { - const svg = MockD3('svg'); - svgDraw.drawText(svg, { - x: 10, - y: 10, + it('should append a single text element', function () { + const texts = svgDraw.drawText(svg, { + x: '10', + y: '10', dy: '1em', text: 'One fine text message', - class: 'noteText', + class: 'test-class', fontFamily: 'courier', fontSize: '10px', fontWeight: '500', }); - expect(svg.__children.length).toBe(1); - const text = svg.__children[0]; - expect(text.__name).toBe('text'); - expect(text.attr).toHaveBeenCalledWith('x', 10); - expect(text.attr).toHaveBeenCalledWith('y', 10); - expect(text.attr).toHaveBeenCalledWith('dy', '1em'); - expect(text.attr).toHaveBeenCalledWith('class', 'noteText'); - expect(text.text).toHaveBeenCalledWith('One fine text message'); - expect(text.style).toHaveBeenCalledWith('font-family', 'courier'); - expect(text.style).toHaveBeenCalledWith('font-size', '10px'); - expect(text.style).toHaveBeenCalledWith('font-weight', '500'); + + expect(texts.length).toBe(1); + expect(svg.selectAll('text').size()).toBe(1); + + expect(texts[0].attr('x')).toBe('10'); + expect(texts[0].attr('y')).toBe('10'); + expect(texts[0].attr('dy')).toBe('1em'); + expect(texts[0].attr('class')).toBe('test-class'); + expect(texts[0].text()).toBe('One fine text message'); + expect(texts[0].style('font-family')).toBe('courier'); + expect(texts[0].style('font-size')).toBe('10px'); + expect(texts[0].style('font-weight')).toBe('500'); }); - it('should append a multiple elements', function () { - const svg = MockD3('svg'); - svgDraw.drawText(svg, { - x: 10, - y: 10, - text: 'One fine text message