Merge pull request #1432 from mermaid-js/1418_html_labels_rendering_engine

1418 html labels rendering engine
This commit is contained in:
Knut Sveidqvist 2020-05-27 22:34:26 +02:00 committed by GitHub
commit 8958b2aa1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 247 additions and 81 deletions

View File

@ -18,37 +18,28 @@
</head>
<body>
<h1>info below</h1>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart LR
a --> b
<div class="mermaid" style="width: 100%; height: 20%;">
stateDiagram-v2
subgraph b [Test]
c --> d -->e
end
[*] --> S1
state "Some long name" as S1: The
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart LR
a --> b
<div class="mermaid" style="width: 100%; height: 20%;">
stateDiagram-v2
subgraph id1 [Test]
a --apa--> c
b
c-->b
b-->H
end
G-->H
G-->c
[*] --> S1
state "Some long name" as S1: The description\nwith multiple lines
</div>
<div class="mermaid" style="width: 50%; height: 20%;">
<div class="mermaid2" style="width: 50%; height: 20%;">
flowchart LR
A{{A}}-->B{{B}};
A{{A}}-- apa -->B{{B}};
click A callback "Tooltip"
click B "http://www.github.com" "This is a link"
</div>
<div class="mermaid" style="width: 50%; height: 20%;">
flowchart LR
A{{A}}-->B{{B}};
<div class="mermaid2" style="width: 50%; height: 20%;">
graph LR
A{{A}}--apa-->B{{B}};
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
@ -264,7 +255,7 @@ stateDiagram-v2
// arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 0,
flowchart: { curve: 'linear', "htmlLabels": false },
flowchart: { curve: 'linear', "htmlLabels": true },
// gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50, showSequenceNumbers: true },
// sequenceDiagram: { actorMargin: 300 } // deprecated

View File

@ -1,6 +1,8 @@
import intersectRect from './intersect/intersect-rect';
import { logger as log } from '../logger'; // eslint-disable-line
import createLabel from './createLabel';
import { select } from 'd3';
import { getConfig } from '../config';
const rect = (parent, node) => {
log.trace('Creating subgraph rect for ', node.id, node);
@ -17,10 +19,20 @@ const rect = (parent, node) => {
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
const text = label
.node()
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
const bbox = text.getBBox();
let bbox = text.getBBox();
if (getConfig().flowchart.htmlLabels) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
const padding = 0 * node.padding;
const halfPadding = padding / 2;
@ -106,11 +118,20 @@ const roundedWithTitle = (parent, node) => {
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
const innerRect = shapeSvg.append('rect');
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
const text = label
.node()
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
const bbox = text.getBBox();
let bbox = text.getBBox();
if (getConfig().flowchart.htmlLabels) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
bbox = text.getBBox();
const padding = 0 * node.padding;
const halfPadding = padding / 2;
@ -134,7 +155,7 @@ const roundedWithTitle = (parent, node) => {
'translate(' +
(node.x - bbox.width / 2) +
', ' +
(node.y - node.height / 2 - node.padding / 3 + 3) +
(node.y - node.height / 2 - node.padding / 3 + (getConfig().flowchart.htmlLabels ? 5 : 3)) +
')'
);
@ -191,7 +212,7 @@ export const insertCluster = (elem, node) => {
clusterElems[node.id] = shapes[shape](elem, node);
};
export const getClusterTitleWidth = (elem, node) => {
const label = createLabel(node.labelText, node.labelStyle);
const label = createLabel(node.labelText, node.labelStyle, undefined, true);
elem.node().appendChild(label);
const width = label.getBBox().width;
elem.node().removeChild(label);

View File

@ -1,29 +1,123 @@
const createLabel = (vertexText, style, isTitle) => {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
let rows = [];
if (typeof vertexText === 'string') {
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
} else if (Array.isArray(vertexText)) {
rows = vertexText;
} else {
rows = [];
}
import { select } from 'd3';
import { logger } from '../logger'; // eslint-disable-line
// let vertexNode;
// if (getConfig().flowchart.htmlLabels) {
// // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
// const node = {
// label: vertexText.replace(/fa[lrsb]?:fa-[\w-]+/g, s => `<i class='${s.replace(':', ' ')}'></i>`)
// };
// vertexNode = addHtmlLabel(svg, node).node();
// vertexNode.parentNode.removeChild(vertexNode);
// } else {
// const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
// svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
for (let j = 0; j < rows.length; j++) {
const tspan = document.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', '0');
if (isTitle) {
tspan.setAttribute('class', 'title-row');
} else {
tspan.setAttribute('class', 'row');
}
tspan.textContent = rows[j].trim();
svgLabel.appendChild(tspan);
// const rows = vertexText.split(common.lineBreakRegex);
// for (let j = 0; j < rows.length; j++) {
// const tspan = document.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 = rows[j];
// svgLabel.appendChild(tspan);
// }
// vertexNode = svgLabel;
// }
import { getConfig } from '../config';
function applyStyle(dom, styleFn) {
if (styleFn) {
dom.attr('style', styleFn);
}
}
function addHtmlLabel(node) {
// var fo = root.append('foreignObject').attr('width', '100000');
// var div = fo.append('xhtml:div');
// div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
// var label = node.label;
// switch (typeof label) {
// case 'function':
// div.insert(label);
// break;
// case 'object':
// // Currently we assume this is a DOM object.
// div.insert(function() {
// return label;
// });
// break;
// default:
// div.html(label);
// }
// applyStyle(div, node.labelStyle);
// div.style('display', 'inline-block');
// // Fix for firefox
// div.style('white-space', 'nowrap');
// var client = div.node().getBoundingClientRect();
// fo.attr('width', client.width).attr('height', client.height);
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
const div = fo.append('xhtml:div');
const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html('<span class="' + labelClass + '">' + label + '</span>');
applyStyle(div, node.labelStyle);
div.style('display', 'inline-block');
// Fix for firefox
div.style('white-space', 'nowrap');
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
return fo.node();
}
const createLabel = (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || '';
if (getConfig().flowchart.htmlLabels) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
logger.info('vertexText' + vertexText);
const node = {
isNode,
label: vertexText.replace(
/fa[lrsb]?:fa-[\w-]+/g,
s => `<i class='${s.replace(':', ' ')}'></i>`
)
};
let vertexNode = addHtmlLabel(node);
// vertexNode.parentNode.removeChild(vertexNode);
return vertexNode;
} else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
let rows = [];
if (typeof vertexText === 'string') {
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
} else if (Array.isArray(vertexText)) {
rows = vertexText;
} else {
rows = [];
}
for (let j = 0; j < rows.length; j++) {
const tspan = document.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', '0');
if (isTitle) {
tspan.setAttribute('class', 'title-row');
} else {
tspan.setAttribute('class', 'row');
}
tspan.textContent = rows[j].trim();
svgLabel.appendChild(tspan);
}
return svgLabel;
}
return svgLabel;
};
export default createLabel;

View File

@ -1,6 +1,6 @@
import { logger } from '../logger'; // eslint-disable-line
import createLabel from './createLabel';
import { line, curveBasis } from 'd3';
import { line, curveBasis, select } from 'd3';
import { getConfig } from '../config';
let edgeLabels = {};
@ -21,7 +21,14 @@ export const insertEdgeLabel = (elem, edge) => {
label.node().appendChild(labelElement);
// Center the label
const bbox = labelElement.getBBox();
let bbox = labelElement.getBBox();
if (getConfig().flowchart.htmlLabels) {
const div = labelElement.children[0];
const dv = select(labelElement);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
// Make element accessible by id for positioning

View File

@ -2,11 +2,12 @@ import intersect from './intersect/index.js';
import { select } from 'd3';
import { logger } from '../logger'; // eslint-disable-line
import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util';
import { getConfig } from '../config';
import createLabel from './createLabel';
import note from './shapes/note';
const question = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -28,7 +29,7 @@ const question = (parent, node) => {
};
const hexagon = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const f = 4;
const h = bbox.height + node.padding;
@ -53,7 +54,7 @@ const hexagon = (parent, node) => {
};
const rect_left_inv_arrow = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -75,7 +76,7 @@ const rect_left_inv_arrow = (parent, node) => {
return shapeSvg;
};
const lean_right = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -97,7 +98,7 @@ const lean_right = (parent, node) => {
};
const lean_left = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -119,7 +120,7 @@ const lean_left = (parent, node) => {
};
const trapezoid = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -140,7 +141,7 @@ const trapezoid = (parent, node) => {
};
const inv_trapezoid = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -160,7 +161,7 @@ const inv_trapezoid = (parent, node) => {
return shapeSvg;
};
const rect_right_inv_arrow = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@ -181,7 +182,7 @@ const rect_right_inv_arrow = (parent, node) => {
return shapeSvg;
};
const cylinder = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const rx = w / 2;
@ -248,7 +249,7 @@ const cylinder = (parent, node) => {
};
const rect = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
logger.trace('Classes = ', node.classes);
// add the rect
@ -296,20 +297,54 @@ const rectWithTitle = (parent, node) => {
const text2 = node.labelText.flat();
logger.info('Label text', text2[0]);
const text = label.node().appendChild(createLabel(text2[0], node.labelStyle, true));
const text = label.node().appendChild(createLabel(text2[0], node.labelStyle, true, true));
let bbox;
if (getConfig().flowchart.htmlLabels) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
logger.info('Text 2', text2);
const textRows = text2.slice(1, text2.length);
let titleBox = text.getBBox();
const descr = label
.node()
.appendChild(createLabel(textRows.join('<br/>'), node.labelStyle, true));
.appendChild(createLabel(textRows.join('<br/>'), node.labelStyle, true, true));
logger.info(descr);
if (getConfig().flowchart.htmlLabels) {
const div = descr.children[0];
const dv = select(descr);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
// bbox = label.getBBox();
// logger.info(descr);
const halfPadding = node.padding / 2;
select(descr).attr('transform', 'translate( 0' + ', ' + (titleBox.height + halfPadding) + ')');
select(descr).attr(
'transform',
'translate( ' +
// (titleBox.width - bbox.width) / 2 +
(bbox.width > titleBox.width ? 0 : (titleBox.width - bbox.width) / 2) +
', ' +
(titleBox.height + halfPadding + 5) +
')'
);
select(text).attr(
'transform',
'translate( ' +
// (titleBox.width - bbox.width) / 2 +
(bbox.width < titleBox.width ? 0 : -(titleBox.width - bbox.width) / 2) +
', ' +
0 +
')'
);
// Get the size of the label
// Bounding box for title and text
const bbox = label.node().getBBox();
bbox = label.node().getBBox();
// Center the label
label.attr(
@ -341,7 +376,7 @@ const rectWithTitle = (parent, node) => {
};
const stadium = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const h = bbox.height + node.padding;
const w = bbox.width + h / 4 + node.padding;
@ -365,7 +400,7 @@ const stadium = (parent, node) => {
return shapeSvg;
};
const circle = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node);
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, undefined, true);
const circle = shapeSvg.insert('circle', ':first-child');
// center the circle around its coordinate
@ -386,7 +421,7 @@ const circle = (parent, node) => {
};
const subroutine = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node);
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;

View File

@ -3,7 +3,7 @@ import { logger } from '../../logger'; // eslint-disable-line
import intersect from '../intersect/index.js';
const note = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
logger.info('Classes = ', node.classes);
// add the rect

View File

@ -1,6 +1,7 @@
import createLabel from '../createLabel';
export const labelHelper = (parent, node, _classes) => {
import { getConfig } from '../../config';
import { select } from 'd3';
export const labelHelper = (parent, node, _classes, isNode) => {
let classes;
if (!_classes) {
classes = 'node default';
@ -16,10 +17,20 @@ export const labelHelper = (parent, node, _classes) => {
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'label');
const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
const text = label
.node()
.appendChild(createLabel(node.labelText, node.labelStyle, false, isNode));
// Get the size of the label
const bbox = text.getBBox();
let bbox = text.getBBox();
if (getConfig().flowchart.htmlLabels) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
const halfPadding = node.padding / 2;

View File

@ -148,6 +148,12 @@ export const clear = function() {
root: newDoc()
};
currentDocument = documents.root;
currentDocument = documents.root;
startCnt = 0;
endCnt = 0; // eslint-disable-line
classes = [];
};
export const getState = function(id) {
@ -213,7 +219,7 @@ const getDividerId = () => {
return 'divider-id-' + dividerCnt;
};
const classes = [];
let classes = [];
const getClasses = () => classes;

View File

@ -15,7 +15,7 @@ export const setConf = function(cnf) {
}
};
const nodeDb = {};
let nodeDb = {};
/**
* Returns the all the styles from classDef statements in the graph definition.
@ -203,6 +203,7 @@ const setupDoc = (g, parent, doc, altFlag) => {
export const draw = function(text, id) {
logger.info('Drawing state diagram (v2)', id);
stateDb.clear();
nodeDb = {};
const parser = state.parser;
parser.yy = stateDb;