Skip to content

Commit 9279166

Browse files
committed
feat: Add markup support to timeline diagrams
1 parent 54b78ac commit 9279166

File tree

8 files changed

+134
-16
lines changed

8 files changed

+134
-16
lines changed

packages/mermaid/src/config.type.ts

+5
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,11 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
983983
sectionFills?: string[];
984984
sectionColours?: string[];
985985
disableMulticolor?: boolean;
986+
/**
987+
* Flag for setting whether or not a html tag should be used for rendering labels.
988+
*
989+
*/
990+
htmlLabels?: boolean;
986991
}
987992
/**
988993
* The object containing configurations specific for gantt diagrams

packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import flowDb from './flowDb.js';
22
import { parser } from './parser/flow.jison';
33
import flowRenderer from './flowRenderer.js';
44
import { addDiagrams } from '../../diagram-api/diagram-orchestration.js';
5+
import { curveBasis } from 'd3';
56

67
const diag = {
78
db: flowDb,
@@ -90,7 +91,7 @@ describe('when using mermaid and ', function () {
9091
expect(start).toContain('flowchart-A-');
9192
expect(end).toContain('flowchart-B-');
9293
expect(options.arrowhead).toBe('none');
93-
expect(options.curve).toBe('basis'); // mocked as string
94+
expect(options.curve().prototype).toBe(curveBasis().prototype); // D3 Basis function overrides its prototype and wipes the type so we referentially compare prototypes instead
9495
},
9596
};
9697

packages/mermaid/src/diagrams/timeline/styles.js

+12
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,17 @@ const getStyles = (options) =>
7777
.eventWrapper {
7878
filter: brightness(120%);
7979
}
80+
.timeline-node-label {
81+
dy: 1em;
82+
alignment-baseline: middle;
83+
text-anchor: middle;
84+
dominant-baseline: middle;
85+
text-align: center;
86+
}
87+
88+
.timeline-node-label > p {
89+
margin-top: 0;
90+
margin-bottom: 0;
91+
}
8092
`;
8193
export default getStyles;

packages/mermaid/src/diagrams/timeline/svgDraw.js

+28-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { arc as d3arc, select } from 'd3';
2+
import { createText } from '../../rendering-util/createText.js';
23
const MAX_SECTIONS = 12;
34

45
export const drawRect = function (elem, rectData) {
@@ -523,23 +524,40 @@ export const drawNode = function (elem, node, fullSection, conf) {
523524

524525
// Create the wrapped text element
525526
const textElem = nodeElem.append('g');
527+
const description = node.descr.replace(/(<br\/*>)/g, '\n');
528+
const htmlLabels = conf.timeline.htmlLabels;
529+
530+
createText(textElem, description, {
531+
useHtmlLabels: htmlLabels,
532+
width: node.width,
533+
classes: 'timeline-node-label',
534+
});
535+
536+
if (!htmlLabels) {
537+
textElem
538+
.attr('dy', '1em')
539+
.attr('alignment-baseline', 'middle')
540+
.attr('dominant-baseline', 'middle')
541+
.attr('text-anchor', 'middle');
542+
}
543+
544+
const bbox = textElem.node().getBBox();
526545

527-
const txt = textElem
528-
.append('text')
529-
.text(node.descr)
530-
.attr('dy', '1em')
531-
.attr('alignment-baseline', 'middle')
532-
.attr('dominant-baseline', 'middle')
533-
.attr('text-anchor', 'middle')
534-
.call(wrap, node.width);
535-
const bbox = txt.node().getBBox();
536546
const fontSize =
537547
conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
538548
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
539549
node.height = Math.max(node.height, node.maxHeight);
540550
node.width = node.width + 2 * node.padding;
541551

542-
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
552+
if (!htmlLabels) {
553+
const dx = node.width / 2;
554+
const dy = node.padding / 2;
555+
textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')');
556+
} else {
557+
const dx = (node.width - bbox.width) / 2;
558+
const dy = (node.height - bbox.height) / 2;
559+
textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')');
560+
}
543561

544562
// Create the background element
545563
defaultBkg(bkgElem, node, section, conf);

packages/mermaid/src/diagrams/timeline/timeline.spec.js

+81
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import * as timelineDB from './timelineDb.js';
33
// import { injectUtils } from './mermaidUtils.js';
44
import * as _commonDb from '../../commonDb.js';
55
import { parseDirective as _parseDirective } from '../../directiveUtils.js';
6+
import * as svgDraw from './svgDraw';
7+
import { select } from 'd3';
68

79
import {
810
log,
@@ -23,11 +25,40 @@ import {
2325
// );
2426

2527
describe('when parsing a timeline ', function () {
28+
// Some of the tests are using d3 drawing and node doesn't support getComputedTextLength or getBBox
29+
// because it isn't attached to a real DOM - so we need to mock them.
30+
const originalGetComputedTextLength = SVGElement.prototype.getComputedTextLength;
31+
const originalSVGGetBBox = SVGElement.prototype.getBBox;
32+
const originalHTMLGetBBox = HTMLUnknownElement.prototype.getBBox;
33+
2634
beforeEach(function () {
2735
timeline.yy = timelineDB;
2836
timelineDB.clear();
2937
setLogLevel('trace');
38+
SVGElement.prototype.getComputedTextLength = () => {
39+
return 150;
40+
};
41+
42+
SVGElement.prototype.getBBox = HTMLUnknownElement.prototype.getBBox = () => {
43+
return {
44+
x: 0,
45+
y: 0,
46+
bottom: 0,
47+
height: 0,
48+
left: 0,
49+
right: 0,
50+
top: 0,
51+
width: 0,
52+
};
53+
};
54+
});
55+
56+
afterEach(function () {
57+
SVGElement.prototype.getComputedTextLength = originalGetComputedTextLength;
58+
SVGElement.prototype.getBBox = originalSVGGetBBox;
59+
HTMLUnknownElement.prototype.getBBox = originalHTMLGetBBox;
3060
});
61+
3162
describe('Timeline', function () {
3263
it('TL-1 should handle a simple section definition abc-123', function () {
3364
let str = `timeline
@@ -119,4 +150,54 @@ describe('when parsing a timeline ', function () {
119150
});
120151
});
121152
});
153+
154+
it('TL-6 should render markup as html if htmlLabel option is true', function () {
155+
// Assemble
156+
const element = select(document.createElement('div'), 'div');
157+
const conf = {
158+
timeline: {
159+
htmlLabels: true,
160+
},
161+
};
162+
163+
// Act
164+
svgDraw.drawNode(
165+
element,
166+
{
167+
descr: '__this should be strong__',
168+
},
169+
1,
170+
conf
171+
);
172+
173+
// Assert
174+
expect(element.html()).to.contain('<strong>this should be strong</strong>');
175+
});
176+
177+
it('TL-6 should render markup as svg if htmlLabel option is false', function () {
178+
// Assemble
179+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
180+
const element = select(svg, 'svg');
181+
182+
const conf = {
183+
timeline: {
184+
htmlLabels: false,
185+
},
186+
};
187+
188+
// Act
189+
svgDraw.drawNode(
190+
element,
191+
{
192+
descr: '__this should be strong__',
193+
},
194+
1,
195+
conf
196+
);
197+
198+
// Assert
199+
expect(element.html()).to.contain(
200+
'<tspan font-style="normal" class="text-inner-tspan" font-weight="bold"> strong</tspan>'
201+
);
202+
});
122203
});

packages/mermaid/src/schemas/config.schema.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,7 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
13241324
- messageAlign
13251325
- bottomMarginAdj
13261326
- useMaxWidth
1327+
- htmlLabels
13271328
properties:
13281329
diagramMarginX:
13291330
$ref: '#/$defs/C4DiagramConfig/properties/diagramMarginX'
@@ -1434,6 +1435,11 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
14341435
# added by https://github.com/mermaid-js/mermaid/commit/652a42fe1aed7911a781a84716940a973b995639
14351436
type: boolean
14361437
default: false
1438+
htmlLabels:
1439+
description: |
1440+
Flag for setting whether or not a html tag should be used for rendering labels.
1441+
type: boolean
1442+
default: true
14371443

14381444
GanttDiagramConfig:
14391445
title: Gantt Diagram Config

packages/mermaid/src/tests/setup.ts

-3
This file was deleted.

vite.config.ts

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ export default defineConfig({
1616
test: {
1717
environment: 'jsdom',
1818
globals: true,
19-
// TODO: should we move this to a mermaid-core package?
20-
setupFiles: ['packages/mermaid/src/tests/setup.ts'],
2119
coverage: {
2220
provider: 'v8',
2321
reporter: ['text', 'json', 'html', 'lcov'],

0 commit comments

Comments
 (0)