Updating support for the new type of strings for flowcharts-v2

This commit is contained in:
Knut Sveidqvist
2023-03-28 15:28:52 +02:00
parent fbeb016398
commit 63160293c7
12 changed files with 256 additions and 75 deletions

View File

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

View File

@@ -1,12 +1,13 @@
import intersectRect from './intersect/intersect-rect';
import { log } from '../logger';
import createLabel from './createLabel';
import { createText } from '../rendering-util/createText';
import { select } from 'd3';
import { getConfig } from '../config';
import { evaluate } from '../diagrams/common/common';
const rect = (parent, node) => {
log.trace('Creating subgraph rect for ', node.id, node);
log.info('Creating subgraph rect for ', node.id, node);
// Add outer g element
const shapeSvg = parent
@@ -17,12 +18,18 @@ const rect = (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// 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, undefined, true));
// const text = label
// .node()
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
const text =
node.labelType === 'markdown'
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels })
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@@ -61,7 +68,7 @@ const rect = (parent, node) => {
'transform',
// This puts the labal on top of the box instead of inside it
// 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2 - bbox.height) + ')'
'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')'
'translate(' + node.x + ', ' + (node.y - node.height / 2) + ')'
);
const rectBox = rect.node().getBBox();

View File

@@ -1,5 +1,6 @@
import { log } from '../logger';
import createLabel from './createLabel';
import { createText } from '../rendering-util/createText';
import { line, curveBasis, select } from 'd3';
import { getConfig } from '../config';
import utils from '../utils';
@@ -14,8 +15,13 @@ export const clear = () => {
};
export const insertEdgeLabel = (elem, edge) => {
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// Create the actual text element
const labelElement = createLabel(edge.label, edge.labelStyle);
const labelElement =
edge.labelType === 'markdown'
? createText(elem, edge.label, { style: edge.labelStyle, useHtmlLabels })
: createLabel(edge.label, edge.labelStyle);
log.info('abc82', edge, edge.labelType);
// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
@@ -26,7 +32,7 @@ export const insertEdgeLabel = (elem, edge) => {
// Center the label
let bbox = labelElement.getBBox();
if (evaluate(getConfig().flowchart.htmlLabels)) {
if (useHtmlLabels) {
const div = labelElement.children[0];
const dv = select(labelElement);
bbox = div.getBoundingClientRect();

View File

@@ -313,19 +313,18 @@ const cylinder = (parent, node) => {
const rect = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
log.trace('Classes = ', node.classes);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
const totalWidth = bbox.width + node.padding * 2;
const totalHeight = bbox.height + node.padding * 2;
rect
.attr('class', 'basic label-container')
.attr('style', node.style)
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', -bbox.width / 2 - halfPadding)
.attr('y', -bbox.height / 2 - halfPadding)
.attr('x', -bbox.width / 2 - node.padding)
.attr('y', -bbox.height / 2 - node.padding)
.attr('width', totalWidth)
.attr('height', totalHeight);
@@ -352,7 +351,7 @@ const rect = (parent, node) => {
const labelRect = (parent, node) => {
const { shapeSvg } = labelHelper(parent, node, 'label', true);
log.trace('Classes = ', node.classes);
log.info('Classes = ', node.classes);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');

View File

@@ -1,4 +1,5 @@
import createLabel from '../createLabel';
import { createText } from '../../rendering-util/createText';
import { getConfig } from '../../config';
import { decodeEntities } from '../../mermaidAPI';
import { select } from 'd3';
@@ -27,9 +28,17 @@ export const labelHelper = (parent, node, _classes, isNode) => {
labelText = typeof node.labelText === 'string' ? node.labelText : node.labelText[0];
}
const text = label
.node()
.appendChild(
const textNode = label.node();
let text;
if (node.labelType === 'markdown') {
// text = textNode;
text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
useHtmlLabels: getConfig().flowchart.htmlLabels,
width: node.width || 200,
classes: 'markdown-node-label',
});
} else {
text = textNode.appendChild(
createLabel(
sanitizeText(decodeEntities(labelText), getConfig()),
node.labelStyle,
@@ -37,6 +46,7 @@ export const labelHelper = (parent, node, _classes, isNode) => {
isNode
)
);
}
// Get the size of the label
let bbox = text.getBBox();
@@ -52,7 +62,11 @@ export const labelHelper = (parent, node, _classes, isNode) => {
const halfPadding = node.padding / 2;
// Center the label
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
if (getConfig().flowchart.htmlLabels) {
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
} else {
label.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
}
return { shapeSvg, bbox, halfPadding, label };
};

View File

@@ -59,13 +59,14 @@ export const lookUpDomId = function (id) {
*
* @param _id
* @param text
* @param textObj
* @param type
* @param style
* @param classes
* @param dir
* @param props
*/
export const addVertex = function (_id, text, type, style, classes, dir, props = {}) {
export const addVertex = function (_id, textObj, type, style, classes, dir, props = {}) {
let txt;
let id = _id;
if (id === undefined) {
@@ -80,16 +81,17 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
if (vertices[id] === undefined) {
vertices[id] = {
id: id,
labelType: 'text',
domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter,
styles: [],
classes: [],
};
}
vertexCounter++;
if (text !== undefined) {
if (textObj !== undefined) {
config = configApi.getConfig();
txt = sanitizeText(text.trim());
txt = sanitizeText(textObj.text.trim());
vertices[id].labelType = textObj.type;
// strip quotes if string starts and ends with a quote
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
txt = txt.substring(1, txt.length - 1);
@@ -131,24 +133,26 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
* @param _end
* @param type
* @param linkText
* @param linkTextObj
*/
export const addSingleLink = function (_start, _end, type, linkText) {
export const addSingleLink = function (_start, _end, type) {
let start = _start;
let end = _end;
// if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
// if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
// log.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '' };
linkText = type.text;
const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
const linkTextObj = type.text;
if (linkText !== undefined) {
edge.text = sanitizeText(linkText.trim());
if (linkTextObj !== undefined) {
edge.text = sanitizeText(linkTextObj.text.trim());
// strip quotes if string starts and ends with a quote
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
edge.text = edge.text.substring(1, edge.text.length - 1);
}
edge.labelType = linkTextObj.type;
}
if (type !== undefined) {
@@ -158,11 +162,11 @@ export const addSingleLink = function (_start, _end, type, linkText) {
}
edges.push(edge);
};
export const addLink = function (_start, _end, type, linktext) {
export const addLink = function (_start, _end, type) {
let i, j;
for (i = 0; i < _start.length; i++) {
for (j = 0; j < _end.length; j++) {
addSingleLink(_start[i], _end[j], type, linktext);
addSingleLink(_start[i], _end[j], type);
}
}
};
@@ -457,10 +461,9 @@ export const defaultStyle = function () {
* @param _title
*/
export const addSubGraph = function (_id, list, _title) {
// console.log('addSubGraph', _id, list, _title);
let id = _id.trim();
let title = _title;
if (_id === _title && _title.match(/\s/)) {
let id = _id.text.trim();
let title = _title.text;
if (_id === _title && _title.text.match(/\s/)) {
id = undefined;
}
/** @param a */
@@ -502,7 +505,14 @@ export const addSubGraph = function (_id, list, _title) {
title = title || '';
title = sanitizeText(title);
subCount = subCount + 1;
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [], dir };
const subGraph = {
id: id,
nodes: nodeList,
title: title.trim(),
classes: [],
dir,
labelType: _title.type,
};
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);

View File

@@ -47,7 +47,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ');
}
classStr = classStr + ' flowchart-label';
const styles = getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
@@ -55,31 +55,36 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
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();
vertexNode.parentNode.removeChild(vertexNode);
log.info('vertex', vertex, vertex.labelType);
if (vertex.labelType === 'markdown') {
log.info('vertex', vertex, vertex.labelType);
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
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();
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);
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);
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;
}
vertexNode = svgLabel;
}
let radious = 0;
@@ -146,6 +151,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
@@ -165,6 +171,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
log.info('setNode', {
labelStyle: styles.labelStyle,
labelType: vertex.labelType,
shape: _shape,
labelText: vertexText,
rx: radious,
@@ -312,7 +319,7 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.labelpos = 'c';
}
edgeData.labelType = 'text';
edgeData.labelType = edge.labelType;
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
@@ -405,7 +412,14 @@ export const draw = function (text, id, _version, diagObj) {
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
log.info('Subgraph - ', subG);
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
);
}
// Fetch the vertices/nodes and edges/links from the parsed graph definition

View File

@@ -7,6 +7,7 @@
/* lexical grammar */
%lex
%x string
%x md_string
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -37,6 +38,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
// <acc_descr_multiline>.*[^\n]* { return "acc_descr_line"}
["][`] { this.begin("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
@@ -434,11 +438,13 @@ arrowText:
;
text: textToken
{$$=$1;}
{$$={text:$1, type: 'text'};}
| text textToken
{$$=$1+''+$2;}
{$$={text:$1.text+''+$2, type: 'text'};}
| STR
{$$=$1;}
{$$={text: $1, type: 'text'};}
| MD_STR
{$$={text: $1, type: 'markdown'};}
;

View File

@@ -41,6 +41,15 @@ const getStyles = (options: FlowChartStyleOptions) =>
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
.flowchart-label text {
text-anchor: middle;
}
// .flowchart-label .text-outer-tspan {
// text-anchor: middle;
// }
// .flowchart-label .text-inner-tspan {
// text-anchor: start;
// }
.node .label {
text-align: center;

View File

@@ -49,7 +49,7 @@ function addHtmlSpan(element, node, width, classes) {
if (bbox.width === width) {
div.style('display', 'table');
div.style('white-space', 'break-spaces');
div.style('width', '200px');
div.style('width', width + 'px');
bbox = div.node().getBoundingClientRect();
}
@@ -70,8 +70,9 @@ function addHtmlSpan(element, node, width, classes) {
function createTspan(textElement, lineIndex, lineHeight) {
return textElement
.append('tspan')
.attr('class', 'text-outer-tspan')
.attr('x', 0)
.attr('y', lineIndex * lineHeight + 'em')
.attr('y', lineIndex * lineHeight - 0.1 + 'em')
.attr('dy', lineHeight + 'em');
}
@@ -86,9 +87,13 @@ function createTspan(textElement, lineIndex, lineHeight) {
function createFormattedText(width, g, structuredText) {
const lineHeight = 1.1;
const textElement = g.append('text');
structuredText.forEach((line, lineIndex) => {
const textElement = g.append('text').attr('y', '-10.1');
// .attr('dominant-baseline', 'middle')
// .attr('text-anchor', 'middle');
// .attr('text-anchor', 'middle');
let lineIndex = -1;
structuredText.forEach((line) => {
lineIndex++;
let tspan = createTspan(textElement, lineIndex, lineHeight);
let words = [...line].reverse();
@@ -108,10 +113,13 @@ function createFormattedText(width, g, structuredText) {
updateTextContentAndStyles(tspan, wrappedLine);
wrappedLine = [];
tspan = createTspan(textElement, ++lineIndex, lineHeight);
lineIndex++;
tspan = createTspan(textElement, lineIndex, lineHeight);
}
}
});
return textElement.node();
// return g.node();
}
/**
@@ -124,12 +132,36 @@ function createFormattedText(width, g, structuredText) {
function updateTextContentAndStyles(tspan, wrappedLine) {
tspan.text('');
wrappedLine.forEach((word) => {
tspan
wrappedLine.forEach((word, index) => {
const innerTspan = tspan
.append('tspan')
.attr('font-style', word.type === 'em' ? 'italic' : 'normal')
.attr('font-weight', word.type === 'strong' ? 'bold' : 'normal')
.text(word.content + ' ');
.attr('class', 'text-inner-tspan')
.attr('font-weight', word.type === 'strong' ? 'bold' : 'normal');
const special = [
'<',
'>',
'&',
'"',
"'",
'.',
',',
':',
';',
'!',
'?',
'(',
')',
'[',
']',
'{',
'}',
];
if (index !== 0 && special.includes(word.content)) {
innerTspan.text(word.content);
} else {
innerTspan.text(' ' + word.content);
}
});
}

View File

@@ -169,6 +169,37 @@ it('markdownToLines - Mixed formatting', () => {
expect(output).toEqual(expectedOutput);
});
it('markdownToLines - Mixed formatting', () => {
const input = `The dog in **the** hog... a *very long text* about it
Word!`;
const expectedOutput = [
[
{ content: 'The', type: 'normal' },
{ content: 'dog', type: 'normal' },
{ content: 'in', type: 'normal' },
{ content: 'the', type: 'strong' },
{ content: 'hog', type: 'normal' },
{ content: '.', type: 'normal' },
{ content: '.', type: 'normal' },
{ content: '.', type: 'normal' },
{ content: 'a', type: 'normal' },
{ content: 'very', type: 'em' },
{ content: 'long', type: 'em' },
{ content: 'text', type: 'em' },
{ content: 'about', type: 'normal' },
{ content: 'it', type: 'normal' },
],
[
{ content: 'Word', type: 'normal' },
{ content: '!', type: 'normal' },
],
];
const output = markdownToLines(input);
expect(output).toEqual(expectedOutput);
});
test('markdownToHTML - Basic test', () => {
const input = `This is regular text
Here is a new line