mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-24 09:49:46 +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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<pre id="diagram" class="mermaid">
|
<pre id="diagram" class="mermaid2">
|
||||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||||
graph BT
|
graph BT
|
||||||
a{The cat in the hat} -- 1o --> b
|
a{The cat in the hat} -- 1o --> b
|
||||||
@@ -66,12 +66,13 @@ h --3i -->a
|
|||||||
b --> d(The dog in the hog)
|
b --> d(The dog in the hog)
|
||||||
c --> d
|
c --> d
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram" class="mermaid2">
|
<pre id="diagram" class="mermaid">
|
||||||
flowchart-elk TB
|
mindmap
|
||||||
a --> b
|
id1["`Start
|
||||||
a --> c
|
second line 😎`"]
|
||||||
b --> d
|
id2[Child]
|
||||||
c --> d
|
id3[Child]
|
||||||
|
id4[Child]
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram" class="mermaid2">
|
<pre id="diagram" class="mermaid2">
|
||||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||||
|
@@ -41,7 +41,13 @@ function addHtmlLabel(node) {
|
|||||||
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||||
return fo.node();
|
return fo.node();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param _vertexText
|
||||||
|
* @param style
|
||||||
|
* @param isTitle
|
||||||
|
* @param isNode
|
||||||
|
* @deprecated svg-util/createText instead
|
||||||
|
*/
|
||||||
const createLabel = (_vertexText, style, isTitle, isNode) => {
|
const createLabel = (_vertexText, style, isTitle, isNode) => {
|
||||||
let vertexText = _vertexText || '';
|
let vertexText = _vertexText || '';
|
||||||
if (typeof vertexText === 'object') {
|
if (typeof vertexText === 'object') {
|
||||||
|
@@ -33,7 +33,7 @@ export const addNode = (level, id, descr, type) => {
|
|||||||
id: cnt++,
|
id: cnt++,
|
||||||
nodeId: sanitizeText(id),
|
nodeId: sanitizeText(id),
|
||||||
level,
|
level,
|
||||||
descr: sanitizeText(descr),
|
descr: sanitizeText(descr).replace(/\n/g, '<br />'),
|
||||||
type,
|
type,
|
||||||
children: [],
|
children: [],
|
||||||
width: getConfig().mindmap.maxNodeWidth,
|
width: getConfig().mindmap.maxNodeWidth,
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
%}
|
%}
|
||||||
%x NODE
|
%x NODE
|
||||||
%x NSTR
|
%x NSTR
|
||||||
|
%x NSTR2
|
||||||
%x ICON
|
%x ICON
|
||||||
%x CLASS
|
%x CLASS
|
||||||
|
|
||||||
@@ -41,6 +42,9 @@
|
|||||||
// !(-\() return 'NODE_ID';
|
// !(-\() return 'NODE_ID';
|
||||||
[^\(\[\n\-\)\{\}]+ return 'NODE_ID';
|
[^\(\[\n\-\)\{\}]+ return 'NODE_ID';
|
||||||
<<EOF>> return 'EOF';
|
<<EOF>> return 'EOF';
|
||||||
|
<NODE>["][`] { this.begin("NSTR2");}
|
||||||
|
<NSTR2>[^`"]+ { return "NODE_DESCR";}
|
||||||
|
<NSTR2>[`]["] { this.popState();}
|
||||||
<NODE>["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
|
<NODE>["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
|
||||||
<NSTR>[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
|
<NSTR>[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
|
||||||
<NSTR>["] {this.popState();}
|
<NSTR>["] {this.popState();}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { select } from 'd3';
|
import { select } from 'd3';
|
||||||
import * as db from './mindmapDb';
|
import * as db from './mindmapDb';
|
||||||
|
import { createText, setSize } from '../../rendering-util/createText';
|
||||||
const MAX_SECTIONS = 12;
|
const MAX_SECTIONS = 12;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,7 +12,7 @@ function wrap(text, width) {
|
|||||||
var text = select(this),
|
var text = select(this),
|
||||||
words = text
|
words = text
|
||||||
.text()
|
.text()
|
||||||
.split(/(\s+|<br>)/)
|
.split(/(\s+|<br\/>)/)
|
||||||
.reverse(),
|
.reverse(),
|
||||||
word,
|
word,
|
||||||
line = [],
|
line = [],
|
||||||
@@ -28,10 +29,10 @@ function wrap(text, width) {
|
|||||||
word = words[words.length - 1 - j];
|
word = words[words.length - 1 - j];
|
||||||
line.push(word);
|
line.push(word);
|
||||||
tspan.text(line.join(' ').trim());
|
tspan.text(line.join(' ').trim());
|
||||||
if (tspan.node().getComputedTextLength() > width || word === '<br>') {
|
if (tspan.node().getComputedTextLength() > width || word === '<br/>') {
|
||||||
line.pop();
|
line.pop();
|
||||||
tspan.text(line.join(' ').trim());
|
tspan.text(line.join(' ').trim());
|
||||||
if (word === '<br>') {
|
if (word === '<br/>') {
|
||||||
line = [''];
|
line = [''];
|
||||||
} else {
|
} else {
|
||||||
line = [word];
|
line = [word];
|
||||||
@@ -203,6 +204,7 @@ const roundedRectBkg = function (elem, node) {
|
|||||||
* @returns {number} The height nodes dom element
|
* @returns {number} The height nodes dom element
|
||||||
*/
|
*/
|
||||||
export const drawNode = function (elem, node, fullSection, conf) {
|
export const drawNode = function (elem, node, fullSection, conf) {
|
||||||
|
const htmlLabels = false;
|
||||||
const section = fullSection % (MAX_SECTIONS - 1);
|
const section = fullSection % (MAX_SECTIONS - 1);
|
||||||
const nodeElem = elem.append('g');
|
const nodeElem = elem.append('g');
|
||||||
node.section = section;
|
node.section = section;
|
||||||
@@ -215,15 +217,29 @@ export const drawNode = function (elem, node, fullSection, conf) {
|
|||||||
|
|
||||||
// Create the wrapped text element
|
// Create the wrapped text element
|
||||||
const textElem = nodeElem.append('g');
|
const textElem = nodeElem.append('g');
|
||||||
const txt = textElem
|
|
||||||
.append('text')
|
const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels });
|
||||||
.text(node.descr)
|
const txt = textElem.node().appendChild(newEl);
|
||||||
.attr('dy', '1em')
|
// const txt = textElem.append(newEl);
|
||||||
.attr('alignment-baseline', 'middle')
|
// const txt = textElem
|
||||||
.attr('dominant-baseline', 'middle')
|
// .append('text')
|
||||||
.attr('text-anchor', 'middle')
|
// .text(node.descr)
|
||||||
.call(wrap, node.width);
|
// .attr('dy', '1em')
|
||||||
const bbox = txt.node().getBBox();
|
// .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;
|
const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||||
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||||
node.width = bbox.width + 2 * node.padding;
|
node.width = bbox.width + 2 * node.padding;
|
||||||
@@ -267,7 +283,16 @@ export const drawNode = function (elem, node, fullSection, conf) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
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