mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-24 17:59:39 +02:00
#4220 Create text utility functions handling new lines and applying them on mindmap
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="diagram" class="mermaid">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
graph BT
|
||||
a{The cat in the hat} -- 1o --> b
|
||||
@@ -66,12 +66,13 @@ h --3i -->a
|
||||
b --> d(The dog in the hog)
|
||||
c --> d
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
flowchart-elk TB
|
||||
a --> b
|
||||
a --> c
|
||||
b --> d
|
||||
c --> d
|
||||
<pre id="diagram" class="mermaid">
|
||||
mindmap
|
||||
id1["`Start
|
||||
second line 😎`"]
|
||||
id2[Child]
|
||||
id3[Child]
|
||||
id4[Child]
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
|
@@ -41,7 +41,13 @@ function addHtmlLabel(node) {
|
||||
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
return fo.node();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _vertexText
|
||||
* @param style
|
||||
* @param isTitle
|
||||
* @param isNode
|
||||
* @deprecated svg-util/createText instead
|
||||
*/
|
||||
const createLabel = (_vertexText, style, isTitle, isNode) => {
|
||||
let vertexText = _vertexText || '';
|
||||
if (typeof vertexText === 'object') {
|
||||
|
@@ -33,7 +33,7 @@ export const addNode = (level, id, descr, type) => {
|
||||
id: cnt++,
|
||||
nodeId: sanitizeText(id),
|
||||
level,
|
||||
descr: sanitizeText(descr),
|
||||
descr: sanitizeText(descr).replace(/\n/g, '<br />'),
|
||||
type,
|
||||
children: [],
|
||||
width: getConfig().mindmap.maxNodeWidth,
|
||||
|
@@ -12,6 +12,7 @@
|
||||
%}
|
||||
%x NODE
|
||||
%x NSTR
|
||||
%x NSTR2
|
||||
%x ICON
|
||||
%x CLASS
|
||||
|
||||
@@ -41,6 +42,9 @@
|
||||
// !(-\() return 'NODE_ID';
|
||||
[^\(\[\n\-\)\{\}]+ return 'NODE_ID';
|
||||
<<EOF>> return 'EOF';
|
||||
<NODE>["][`] { this.begin("NSTR2");}
|
||||
<NSTR2>[^`"]+ { return "NODE_DESCR";}
|
||||
<NSTR2>[`]["] { this.popState();}
|
||||
<NODE>["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
|
||||
<NSTR>[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
|
||||
<NSTR>["] {this.popState();}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { select } from 'd3';
|
||||
import * as db from './mindmapDb';
|
||||
import { createText, setSize } from '../../rendering-util/createText';
|
||||
const MAX_SECTIONS = 12;
|
||||
|
||||
/**
|
||||
@@ -11,7 +12,7 @@ function wrap(text, width) {
|
||||
var text = select(this),
|
||||
words = text
|
||||
.text()
|
||||
.split(/(\s+|<br>)/)
|
||||
.split(/(\s+|<br\/>)/)
|
||||
.reverse(),
|
||||
word,
|
||||
line = [],
|
||||
@@ -28,10 +29,10 @@ function wrap(text, width) {
|
||||
word = words[words.length - 1 - j];
|
||||
line.push(word);
|
||||
tspan.text(line.join(' ').trim());
|
||||
if (tspan.node().getComputedTextLength() > width || word === '<br>') {
|
||||
if (tspan.node().getComputedTextLength() > width || word === '<br/>') {
|
||||
line.pop();
|
||||
tspan.text(line.join(' ').trim());
|
||||
if (word === '<br>') {
|
||||
if (word === '<br/>') {
|
||||
line = [''];
|
||||
} else {
|
||||
line = [word];
|
||||
@@ -203,6 +204,7 @@ const roundedRectBkg = function (elem, node) {
|
||||
* @returns {number} The height nodes dom element
|
||||
*/
|
||||
export const drawNode = function (elem, node, fullSection, conf) {
|
||||
const htmlLabels = false;
|
||||
const section = fullSection % (MAX_SECTIONS - 1);
|
||||
const nodeElem = elem.append('g');
|
||||
node.section = section;
|
||||
@@ -215,15 +217,29 @@ export const drawNode = function (elem, node, fullSection, conf) {
|
||||
|
||||
// Create the wrapped text element
|
||||
const textElem = nodeElem.append('g');
|
||||
const txt = textElem
|
||||
.append('text')
|
||||
.text(node.descr)
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.call(wrap, node.width);
|
||||
const bbox = txt.node().getBBox();
|
||||
|
||||
const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels });
|
||||
const txt = textElem.node().appendChild(newEl);
|
||||
// const txt = textElem.append(newEl);
|
||||
// const txt = textElem
|
||||
// .append('text')
|
||||
// .text(node.descr)
|
||||
// .attr('dy', '1em')
|
||||
// .attr('alignment-baseline', 'middle')
|
||||
// .attr('dominant-baseline', 'middle')
|
||||
// .attr('text-anchor', 'middle')
|
||||
// .call(wrap, node.width);
|
||||
// const newerEl = textElem.node().appendChild(newEl);
|
||||
// setSize(textElem);
|
||||
if (!htmlLabels) {
|
||||
textElem
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle');
|
||||
}
|
||||
// .call(wrap, node.width);
|
||||
const bbox = textElem.node().getBBox();
|
||||
const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||
node.width = bbox.width + 2 * node.padding;
|
||||
@@ -267,7 +283,16 @@ export const drawNode = function (elem, node, fullSection, conf) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
|
||||
if (!htmlLabels) {
|
||||
const dx = node.width / 2;
|
||||
const dy = node.padding / 2;
|
||||
textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')');
|
||||
// textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
|
||||
} else {
|
||||
const dx = (node.width - bbox.width) / 2;
|
||||
const dy = (node.height - bbox.height) / 2;
|
||||
textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')');
|
||||
}
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
|
161
packages/mermaid/src/rendering-util/createText.js
Normal file
161
packages/mermaid/src/rendering-util/createText.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import { select } from 'd3';
|
||||
import { log } from '../logger';
|
||||
import { getConfig } from '../config';
|
||||
import { evaluate } from '../diagrams/common/common';
|
||||
import { decodeEntities } from '../mermaidAPI';
|
||||
|
||||
/**
|
||||
* @param dom
|
||||
* @param styleFn
|
||||
*/
|
||||
function applyStyle(dom, styleFn) {
|
||||
if (styleFn) {
|
||||
dom.attr('style', styleFn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param element
|
||||
* @param {any} node
|
||||
* @returns {SVGForeignObjectElement} Node
|
||||
*/
|
||||
function addHtmlSpan(element, node) {
|
||||
const fo = element.append('foreignObject');
|
||||
const newEl = 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 +
|
||||
'" ' +
|
||||
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
|
||||
'>' +
|
||||
label +
|
||||
'</span>'
|
||||
);
|
||||
|
||||
applyStyle(div, node.labelStyle);
|
||||
div.style('display', 'inline-block');
|
||||
const bbox = div.node().getBoundingClientRect();
|
||||
fo.style('width', bbox.width);
|
||||
fo.style('height', bbox.height);
|
||||
|
||||
const divNode = div.node();
|
||||
window.divNode = divNode;
|
||||
// Fix for firefox
|
||||
div.style('white-space', 'nowrap');
|
||||
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
return fo.node();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text The text to be wrapped
|
||||
* @param {number} width The max width of the text
|
||||
*/
|
||||
function wrap(text, width) {
|
||||
text.each(function () {
|
||||
var text = select(this),
|
||||
words = text
|
||||
.text()
|
||||
.split(/(\s+|<br\/>)/)
|
||||
.reverse(),
|
||||
word,
|
||||
line = [],
|
||||
lineHeight = 1.1, // ems
|
||||
y = text.attr('y'),
|
||||
dy = parseFloat(text.attr('dy')),
|
||||
tspan = text
|
||||
.text(null)
|
||||
.append('tspan')
|
||||
.attr('x', 0)
|
||||
.attr('y', y)
|
||||
.attr('dy', dy + 'em');
|
||||
for (let j = 0; j < words.length; j++) {
|
||||
word = words[words.length - 1 - j];
|
||||
line.push(word);
|
||||
tspan.text(line.join(' ').trim());
|
||||
if (tspan.node().getComputedTextLength() > width || word === '<br/>') {
|
||||
line.pop();
|
||||
tspan.text(line.join(' ').trim());
|
||||
if (word === '<br/>') {
|
||||
line = [''];
|
||||
} else {
|
||||
line = [word];
|
||||
}
|
||||
|
||||
tspan = text
|
||||
.append('tspan')
|
||||
.attr('x', 0)
|
||||
.attr('y', y)
|
||||
.attr('dy', lineHeight + 'em')
|
||||
.text(word);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param el
|
||||
* @param {*} text
|
||||
* @param {*} param1
|
||||
* @param root0
|
||||
* @param root0.style
|
||||
* @param root0.isTitle
|
||||
* @param root0.classes
|
||||
* @param root0.useHtmlLabels
|
||||
* @param root0.isNode
|
||||
* @returns
|
||||
*/
|
||||
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel'
|
||||
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
|
||||
export const createText = (
|
||||
el,
|
||||
text = '',
|
||||
{ style = '', isTitle = false, classes = '', useHtmlLabels = true, isNode = true } = {}
|
||||
) => {
|
||||
if (useHtmlLabels) {
|
||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||
text = text.replace(/\\n|\n/g, '<br />');
|
||||
log.info('text' + text);
|
||||
const node = {
|
||||
isNode,
|
||||
label: decodeEntities(text).replace(
|
||||
/fa[blrs]?:fa-[\w-]+/g,
|
||||
(s) => `<i class='${s.replace(':', ' ')}'></i>`
|
||||
),
|
||||
labelStyle: style.replace('fill:', 'color:'),
|
||||
};
|
||||
let vertexNode = addHtmlSpan(el, node);
|
||||
return vertexNode;
|
||||
} else {
|
||||
const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
svgText.setAttribute('style', style.replace('color:', 'fill:'));
|
||||
// el.attr('style', style.replace('color:', 'fill:'));
|
||||
let rows = [];
|
||||
if (typeof text === 'string') {
|
||||
rows = text.split(/\\n|\n|<br\s*\/?>/gi);
|
||||
} else if (Array.isArray(text)) {
|
||||
rows = text;
|
||||
} else {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
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 = row.trim();
|
||||
svgText.appendChild(tspan);
|
||||
}
|
||||
return svgText;
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user