Adding support for markdown string in flowchart-elk

This commit is contained in:
Knut Sveidqvist
2023-03-29 16:01:08 +02:00
parent 89193d7360
commit 4caf7d7c7b
13 changed files with 147 additions and 83 deletions

View File

@@ -57,11 +57,11 @@
</style> </style>
</head> </head>
<body> <body>
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
%% %%
graph BT graph LR
a("`The **cat** in the hat} -- 1o --> b a{"`The **cat** in the hat`"} -- 1o --> b
a -- 2o --> c a -- 2o --> c
a -- 3o --> d a -- 3o --> d
g --2i--> a g --2i--> a
@@ -69,39 +69,52 @@ d --1i--> a
h --3i -->a h --3i -->a
b --> d(The dog in the hog) b --> d(The dog in the hog)
c --> d c --> d
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% </pre
</pre> >
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart LR flowchart LR
b("`The dog in **the** hog... a a a a *very long text* about it b("`The dog in **the** hog.(1)
NL`") --"`1o **bold**`"--> c
</pre
>
<pre id="diagram" class="mermaid">
flowchart-elk LR
b("`The dog in **the** hog.(1)
NL`") --"`1o **bold**`"--> c
</pre
>
<pre id="diagram" class="mermaid">
flowchart-elk LR
b("`The dog in **the** hog.(1).. a a a a *very long text* about it
Word! Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `") Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `") --> c
</pre </pre
> >
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"htmlLabels": true}} }%% %%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR flowchart-elk LR
b("`The dog in **the** hog... a a a a *very long text* about it b("`The dog in **the** hog(2)... a a a a *very long text* about it
Word! Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `") Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `")
</pre </pre
> >
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"htmlLabels": false}} }%% %%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR flowchart-elk LR
b("The dog in the hog... a very<br/>long text about it<br/>Word!") b("The dog in the hog... a very<br/>long text about it<br/>Word!")
</pre> </pre>
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"htmlLabels": true}} }%% %%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR flowchart-elk LR
b("The dog in the hog... a very<br/>long text about it<br/>Word!") b("The dog in the hog... a very<br/>long text about it<br/>Word!")
</pre> </pre>
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
flowchart LR flowchart-elk LR
subgraph "One" subgraph "One"
a("`The **cat** a("`The **cat**
in the hat`") -- 1o --> b{{"`The **dog** in the hog`"}} in the hat`") -- "1o" --> b{{"`The **dog** in the hog`"}}
end end
subgraph "`**Two**`" subgraph "`**Two**`"
c("`The **cat** c("`The **cat**
@@ -129,7 +142,7 @@ mindmap
id2["`The dog in **the** hog... a *very long text* about it id2["`The dog in **the** hog... a *very long text* about it
Word!`"] Word!`"]
</pre> </pre>
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid %% I could not figure out how to use double quotes in labels in Mermaid
@@ -185,7 +198,7 @@ flowchart TB
</pre </pre
> >
<br /> <br />
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
flowchart TB flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid %% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU] subgraph ibm[IBM Espresso CPU]
@@ -241,7 +254,7 @@ flowchart TB
> >
<br /> <br />
&nbsp; &nbsp;
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
flowchart LR flowchart LR
B1 --be be--x B2 B1 --be be--x B2
B1 --bo bo--o B3 B1 --bo bo--o B3
@@ -274,7 +287,7 @@ flowchart TB
B6 --> B5 B6 --> B5
</pre </pre
> >
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
sequenceDiagram sequenceDiagram
Customer->>+Stripe: Makes a payment request Customer->>+Stripe: Makes a payment request
Stripe->>+Bank: Forwards the payment request to the bank Stripe->>+Bank: Forwards the payment request to the bank
@@ -287,7 +300,7 @@ sequenceDiagram
Customer->>+Merchant: Receives goods or services Customer->>+Merchant: Receives goods or services
</pre </pre
> >
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
mindmap mindmap
root((mindmap)) root((mindmap))
Origins Origins
@@ -307,7 +320,7 @@ mindmap
Mermaid Mermaid
</pre> </pre>
<br /> <br />
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
example-diagram example-diagram
</pre> </pre>
@@ -332,8 +345,8 @@ mindmap
flowchart: { flowchart: {
// defaultRenderer: 'elk', // defaultRenderer: 'elk',
useMaxWidth: false, useMaxWidth: false,
// htmlLabels: false,
htmlLabels: false, htmlLabels: false,
// htmlLabels: true,
}, },
htmlLabels: false, htmlLabels: false,
gantt: { gantt: {

View File

@@ -14,7 +14,7 @@
#### Defined in #### Defined in
[defaultConfig.ts:2093](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2093) [defaultConfig.ts:2105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2105)
--- ---

View File

@@ -44,7 +44,6 @@ export class Diagram {
// calls diagram.db.clear(), which would reset anything set by // calls diagram.db.clear(), which would reset anything set by
// extractFrontMatter(). // extractFrontMatter().
this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db)); this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
// this.parser.parse = (text: string) => { // this.parser.parse = (text: string) => {
// console.log('parse called'); // console.log('parse called');
// try { // try {

View File

@@ -385,6 +385,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
curve?: string; curve?: string;
padding?: number; padding?: number;
defaultRenderer?: string; defaultRenderer?: string;
wrappingWidth?: number;
} }
export interface FontConfig { export interface FontConfig {

View File

@@ -19,7 +19,11 @@ export const insertEdgeLabel = (elem, edge) => {
// Create the actual text element // Create the actual text element
const labelElement = const labelElement =
edge.labelType === 'markdown' edge.labelType === 'markdown'
? createText(elem, edge.label, { style: edge.labelStyle, useHtmlLabels }) ? createText(elem, edge.label, {
style: edge.labelStyle,
useHtmlLabels,
addSvgBackground: true,
})
: createLabel(edge.label, edge.labelStyle); : createLabel(edge.label, edge.labelStyle);
log.info('abc82', edge, edge.labelType); log.info('abc82', edge, edge.labelType);

View File

@@ -997,6 +997,7 @@ export const insertNode = (elem, node, dir) => {
el.attr('class', 'node default ' + node.class); el.attr('class', 'node default ' + node.class);
} }
/* MC: 7e790808-9c49-4f74-93de-15c22872377f */
nodeElems[node.id] = newEl; nodeElems[node.id] = newEl;
if (node.haveCallback) { if (node.haveCallback) {

View File

@@ -34,7 +34,7 @@ export const labelHelper = (parent, node, _classes, isNode) => {
// text = textNode; // text = textNode;
text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), { text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
useHtmlLabels: getConfig().flowchart.htmlLabels, useHtmlLabels: getConfig().flowchart.htmlLabels,
width: node.width || 200, width: node.width || getConfig().flowchart.wrappingWidth,
classes: 'markdown-node-label', classes: 'markdown-node-label',
}); });
} else { } else {
@@ -67,7 +67,7 @@ export const labelHelper = (parent, node, _classes, isNode) => {
} else { } else {
label.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')'); label.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
} }
label.insert('rect', ':first-child');
return { shapeSvg, bbox, halfPadding, label }; return { shapeSvg, bbox, halfPadding, label };
}; };

View File

@@ -258,6 +258,18 @@ const config: Partial<MermaidConfig> = {
* Default value: 'dagre-wrapper' * Default value: 'dagre-wrapper'
*/ */
defaultRenderer: 'dagre-wrapper', defaultRenderer: 'dagre-wrapper',
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ----------- | ------- | -------- | ----------------------- |
* | wrappingWidth | See notes | number | 4 | width of nodes where text is wrapped |
*
* **Notes:**
*
* When using markdown strings the text ius wrapped automatically, this
* value sets the max width of a text before it continues on a new line.
* Default value: 'dagre-wrapper'
*/
wrappingWidth: 200,
}, },
/** The object containing configurations specific for sequence diagrams */ /** The object containing configurations specific for sequence diagrams */

View File

@@ -3,6 +3,7 @@ import { insertNode } from '../../../dagre-wrapper/nodes.js';
import insertMarkers from '../../../dagre-wrapper/markers.js'; import insertMarkers from '../../../dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils'; import { findCommonAncestor } from './render-utils';
import { labelHelper } from '../../../dagre-wrapper/shapes/util';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { getConfig } from '../../../config'; import { getConfig } from '../../../config';
import { log } from '../../../logger'; import { log } from '../../../logger';
@@ -52,7 +53,7 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
if (vertex.classes.length > 0) { if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' '); classStr = vertex.classes.join(' ');
} }
classStr = classStr + ' flowchart-label';
const styles = getStylesFromArray(vertex.styles); const styles = getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition // Use vertex id as text in the box if no text is provided by the graph definition
@@ -61,40 +62,6 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
// We create a SVG label, either by delegating to addHtmlLabel or manually // We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode; let vertexNode;
const labelData = { width: 0, height: 0 }; const labelData = { width: 0, height: 0 };
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
};
vertexNode = addHtmlLabel(svg, node).node();
const bbox = vertexNode.getBBox();
labelData.width = bbox.width;
labelData.height = bbox.height;
labelData.labelNode = vertexNode;
vertexNode.parentNode.removeChild(vertexNode);
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
const rows = vertexText.split(common.lineBreakRegex);
for (const row of rows) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1');
tspan.textContent = row;
svgLabel.appendChild(tspan);
}
vertexNode = svgLabel;
const bbox = vertexNode.getBBox();
labelData.width = bbox.width;
labelData.height = bbox.height;
labelData.labelNode = vertexNode;
}
const ports = [ const ports = [
{ {
@@ -186,11 +153,13 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
default: default:
_shape = 'rect'; _shape = 'rect';
} }
// Add the node // Add the node
const node = { const node = {
labelStyle: styles.labelStyle, labelStyle: styles.labelStyle,
shape: _shape, shape: _shape,
labelText: vertexText, labelText: vertexText,
labelType: vertex.labelType,
rx: radious, rx: radious,
ry: radious, ry: radious,
class: classStr, class: classStr,
@@ -209,10 +178,33 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
}; };
let boundingBox; let boundingBox;
let nodeEl; let nodeEl;
// Add the element to the DOM
if (node.type !== 'group') { if (node.type !== 'group') {
nodeEl = insertNode(nodes, node, vertex.dir); nodeEl = insertNode(nodes, node, vertex.dir);
boundingBox = nodeEl.node().getBBox(); boundingBox = nodeEl.node().getBBox();
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
// svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
// const rows = vertexText.split(common.lineBreakRegex);
// for (const row of rows) {
// const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
// tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
// tspan.setAttribute('dy', '1em');
// tspan.setAttribute('x', '1');
// tspan.textContent = row;
// svgLabel.appendChild(tspan);
// }
// vertexNode = svgLabel;
// const bbox = vertexNode.getBBox();
const { shapeSvg, bbox } = labelHelper(nodes, node, undefined, true);
labelData.width = bbox.width;
labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
labelData.height = bbox.height;
labelData.labelNode = shapeSvg.node();
node.labelData = labelData;
} }
// const { shapeSvg, bbox } = labelHelper(svg, node, undefined, true);
const data = { const data = {
id: vertex.id, id: vertex.id,
@@ -520,7 +512,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
edgeData.labelpos = 'c'; edgeData.labelpos = 'c';
} }
edgeData.labelType = 'text'; edgeData.labelType = edge.labelType;
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) { if (edge.style === undefined) {
@@ -845,9 +837,17 @@ export const draw = async function (text, id, _version, diagObj) {
log.info('Subgraphs - ', subGraphs); log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) { for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i]; subG = subGraphs[i];
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir); diagObj.db.addVertex(
subG.id,
{ text: subG.title, type: subG.labelType },
'group',
undefined,
subG.classes,
subG.dir
);
} }
// debugger;
// Add an element in the svg to be used to hold the subgraphs container // Add an element in the svg to be used to hold the subgraphs container
// elements // elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs'); const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
@@ -860,7 +860,7 @@ export const draw = async function (text, id, _version, diagObj) {
// in order to get the size of the node. You can't get the size of a node // in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size // that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs // we will position the nodes when we get the layout from elkjs
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph); graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph, svg);
// Time for the edges, we start with adding an element in the node to hold the edges // Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath'); const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
@@ -887,6 +887,8 @@ export const draw = async function (text, id, _version, diagObj) {
}, },
width: node.labelData.width, width: node.labelData.width,
height: node.labelData.height, height: node.labelData.height,
// width: 100,
// height: 100,
}, },
]; ];
delete node.x; delete node.x;
@@ -895,6 +897,7 @@ export const draw = async function (text, id, _version, diagObj) {
delete node.height; delete node.height;
} }
}); });
insertChildren(graph.children, parentLookupDb); insertChildren(graph.children, parentLookupDb);
log.info('after layout', JSON.stringify(graph, null, 2)); log.info('after layout', JSON.stringify(graph, null, 2));
const g = await elk.layout(graph); const g = await elk.layout(graph);

View File

@@ -81,7 +81,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
.edgeLabel { .edgeLabel {
background-color: ${options.edgeLabelBackground}; background-color: ${options.edgeLabelBackground};
rect { rect {
opacity: 0.5; opacity: 0.85;
background-color: ${options.edgeLabelBackground}; background-color: ${options.edgeLabelBackground};
fill: ${options.edgeLabelBackground}; fill: ${options.edgeLabelBackground};
} }
@@ -132,6 +132,11 @@ const getStyles = (options: FlowChartStyleOptions) =>
// fill:#ccc; // fill:#ccc;
// // stroke:black; // // stroke:black;
// } // }
.flowchart-label text {
text-anchor: middle;
}
${genSections(options)} ${genSections(options)}
`; `;

View File

@@ -143,6 +143,7 @@ export const addSingleLink = function (_start, _end, type) {
// log.info('Got edge...', start, end); // log.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' }; const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
log.info('abc78 Got edge...', edge);
const linkTextObj = type.text; const linkTextObj = type.text;
if (linkTextObj !== undefined) { if (linkTextObj !== undefined) {
@@ -163,6 +164,7 @@ export const addSingleLink = function (_start, _end, type) {
edges.push(edge); edges.push(edge);
}; };
export const addLink = function (_start, _end, type) { export const addLink = function (_start, _end, type) {
log.info('addLink (abc78)', _start, _end, type);
let i, j; let i, j;
for (i = 0; i < _start.length; i++) { for (i = 0; i < _start.length; i++) {
for (j = 0; j < _end.length; j++) { for (j = 0; j < _end.length; j++) {

View File

@@ -440,7 +440,7 @@ arrowText:
text: textToken text: textToken
{ $$={text:$1, type: 'text'};} { $$={text:$1, type: 'text'};}
| text textToken | text textToken
{$$={text:$1.text+''+$2, type: 'text'};} { $$={text:$1.text+''+$2, type: $1.type};}
| STR | STR
{ $$={text: $1, type: 'text'};} { $$={text: $1, type: 'text'};}
| MD_STR | MD_STR

View File

@@ -83,11 +83,13 @@ function createTspan(textElement, lineIndex, lineHeight) {
* @param {number} width - The maximum allowed width of the text. * @param {number} width - The maximum allowed width of the text.
* @param {object} g - The parent group element to append the formatted text. * @param {object} g - The parent group element to append the formatted text.
* @param {Array} structuredText - The structured text data to format. * @param {Array} structuredText - The structured text data to format.
* @param addBackground
*/ */
function createFormattedText(width, g, structuredText) { function createFormattedText(width, g, structuredText, addBackground = false) {
const lineHeight = 1.1; const lineHeight = 1.1;
const labelGroup = g.append('g');
const textElement = g.append('text').attr('y', '-10.1'); let bkg = labelGroup.insert('rect').attr('class', 'background');
const textElement = labelGroup.append('text').attr('y', '-10.1');
// .attr('dominant-baseline', 'middle') // .attr('dominant-baseline', 'middle')
// .attr('text-anchor', 'middle'); // .attr('text-anchor', 'middle');
// .attr('text-anchor', 'middle'); // .attr('text-anchor', 'middle');
@@ -118,8 +120,20 @@ function createFormattedText(width, g, structuredText) {
} }
} }
}); });
if (addBackground) {
const bbox = textElement.node().getBBox();
const padding = 2;
bkg
.attr('x', -padding)
.attr('y', -padding)
.attr('width', bbox.width + 2 * padding)
.attr('height', bbox.height + 2 * padding);
// .style('fill', 'red');
return labelGroup.node();
} else {
return textElement.node(); return textElement.node();
// return g.node(); }
} }
/** /**
@@ -183,9 +197,17 @@ function updateTextContentAndStyles(tspan, wrappedLine) {
export const createText = ( export const createText = (
el, el,
text = '', text = '',
{ style = '', isTitle = false, classes = '', useHtmlLabels = true, isNode = true, width } = {} {
style = '',
isTitle = false,
classes = '',
useHtmlLabels = true,
isNode = true,
width,
addSvgBackground = false,
} = {}
) => { ) => {
log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode); log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground);
if (useHtmlLabels) { if (useHtmlLabels) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
// text = text.replace(/\\n|\n/g, '<br />'); // text = text.replace(/\\n|\n/g, '<br />');
@@ -203,6 +225,8 @@ export const createText = (
return vertexNode; return vertexNode;
} else { } else {
const structuredText = markdownToLines(text); const structuredText = markdownToLines(text);
return createFormattedText(width, el, structuredText);
const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground);
return svgLabel;
} }
}; };