#4220 Create text utility functions handling new lines and applying them on mindmap

This commit is contained in:
Knut Sveidqvist
2023-03-14 13:52:20 +01:00
parent f3a9f81bfb
commit 853d9b7f98
6 changed files with 219 additions and 22 deletions

View File

@@ -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"}} }%%

View File

@@ -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') {

View File

@@ -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,

View File

@@ -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();}

View File

@@ -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) {

View 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;
}
};