mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-11 19:39:43 +02:00
Adding text wrap and logic for placing nodes in the svg
This commit is contained in:
@@ -41,6 +41,16 @@ journey
|
|||||||
Go downstairs: 5: Me
|
Go downstairs: 5: Me
|
||||||
Sit down: 5: Mee
|
Sit down: 5: Mee
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mermaid" style="width: 50%;">
|
||||||
|
mindmap
|
||||||
|
root[
|
||||||
|
The root where the things
|
||||||
|
hap<br/>
|
||||||
|
hap<br/>
|
||||||
|
pen!
|
||||||
|
]
|
||||||
|
Child1
|
||||||
|
</div>
|
||||||
<div class="mermaid2" style="width: 50%;">
|
<div class="mermaid2" style="width: 50%;">
|
||||||
pie
|
pie
|
||||||
accTitle: My Pie Chart Accessibility Title
|
accTitle: My Pie Chart Accessibility Title
|
||||||
@@ -52,8 +62,8 @@ journey
|
|||||||
"Magnesium" : 10.01
|
"Magnesium" : 10.01
|
||||||
"Iron" : 5
|
"Iron" : 5
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid2" style="width: 50%;">
|
<div class="mermaid" style="width: 50%;">
|
||||||
gitGraph
|
gitGraph TB
|
||||||
commit
|
commit
|
||||||
commit
|
commit
|
||||||
branch develop
|
branch develop
|
||||||
@@ -217,7 +227,7 @@ class Class10 {
|
|||||||
size()
|
size()
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="mermaid" style="width: 100%;">
|
<div class="mermaid2" style="width: 100%;">
|
||||||
%%{init: {'config': {'wrap': true }}}%%
|
%%{init: {'config': {'wrap': true }}}%%
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant A as Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
participant A as Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
@@ -332,7 +342,7 @@ flowchart TD
|
|||||||
rankSpacing: 50,
|
rankSpacing: 50,
|
||||||
defaultRenderer: 'dagre-d3',
|
defaultRenderer: 'dagre-d3',
|
||||||
},
|
},
|
||||||
logLevel: 0,
|
logLevel: 1,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
curve: 'cardinal',
|
curve: 'cardinal',
|
||||||
// securityLevel: 'sandbox',
|
// securityLevel: 'sandbox',
|
||||||
|
@@ -1818,6 +1818,11 @@ const config = {
|
|||||||
external_component_queue_bg_color: '#CCCCCC',
|
external_component_queue_bg_color: '#CCCCCC',
|
||||||
external_component_queue_border_color: '#BFBFBF',
|
external_component_queue_border_color: '#BFBFBF',
|
||||||
},
|
},
|
||||||
|
mindmap: {
|
||||||
|
useMaxWidth: true,
|
||||||
|
diagramPadding: 10,
|
||||||
|
maxNodeWidth: 200,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||||
|
@@ -83,8 +83,10 @@ const detectType = function (text, cnf) {
|
|||||||
if (cnf && cnf.flowchart && cnf.flowchart.defaultRenderer === 'dagre-wrapper')
|
if (cnf && cnf.flowchart && cnf.flowchart.defaultRenderer === 'dagre-wrapper')
|
||||||
return 'flowchart-v2';
|
return 'flowchart-v2';
|
||||||
const k = Object.keys(detectors);
|
const k = Object.keys(detectors);
|
||||||
|
console.log('here', k);
|
||||||
for (let i = 0; i < k.length; i++) {
|
for (let i = 0; i < k.length; i++) {
|
||||||
const key = k[i];
|
const key = k[i];
|
||||||
|
console.log('Detecting type for', key);
|
||||||
const dia = detectors[key];
|
const dia = detectors[key];
|
||||||
if (dia && dia.detector(text)) {
|
if (dia && dia.detector(text)) {
|
||||||
return key;
|
return key;
|
||||||
|
@@ -1,24 +1,17 @@
|
|||||||
import { registerDiagram } from './diagramAPI.js';
|
import { registerDiagram } from './diagramAPI.js';
|
||||||
// import mindmapDb from '../diagrams/mindmap/mindmapDb';
|
import mindmapDb from '../diagrams/mindmap/mindmapDb';
|
||||||
// import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
|
import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
|
||||||
// import mindmapParser from '../diagrams/mindmap/parser/mindmapDiagram';
|
import mindmapParser from '../diagrams/mindmap/parser/mindmap';
|
||||||
// import mindmapDetector from '../diagrams/mindmap/mindmapDetector';
|
import mindmapDetector from '../diagrams/mindmap/mindmapDetector';
|
||||||
|
import mindmapStyles from '../diagrams/mindmap/styles';
|
||||||
|
|
||||||
import gitGraphDb from '../diagrams/git/gitGraphAst';
|
import gitGraphDb from '../diagrams/git/gitGraphAst';
|
||||||
import gitGraphRenderer from '../diagrams/git/gitGraphRenderer';
|
import gitGraphRenderer from '../diagrams/git/gitGraphRenderer';
|
||||||
import gitGraphParser from '../diagrams/git/parser/gitGraph';
|
import gitGraphParser from '../diagrams/git/parser/gitGraph';
|
||||||
import gitGraphDetector from '../diagrams/git/gitGraphDetector';
|
import gitGraphDetector from '../diagrams/git/gitGraphDetector';
|
||||||
|
import gitGraphStyles from '../diagrams/git/styles';
|
||||||
|
|
||||||
// Register mindmap and other built-in diagrams
|
// Register mindmap and other built-in diagrams
|
||||||
// registerDiagram(
|
|
||||||
// 'mindmap',
|
|
||||||
// mindmapParser,
|
|
||||||
// mindmapDb,
|
|
||||||
// mindmapRenderer,
|
|
||||||
// undefined,
|
|
||||||
// mindmapRenderer,
|
|
||||||
// mindmapDetector
|
|
||||||
// );
|
|
||||||
const addDiagrams = () => {
|
const addDiagrams = () => {
|
||||||
registerDiagram(
|
registerDiagram(
|
||||||
'gitGraph',
|
'gitGraph',
|
||||||
@@ -26,7 +19,17 @@ const addDiagrams = () => {
|
|||||||
gitGraphDb,
|
gitGraphDb,
|
||||||
gitGraphRenderer,
|
gitGraphRenderer,
|
||||||
undefined,
|
undefined,
|
||||||
gitGraphDetector
|
gitGraphDetector,
|
||||||
|
gitGraphStyles
|
||||||
|
);
|
||||||
|
registerDiagram(
|
||||||
|
'mindmap',
|
||||||
|
mindmapParser,
|
||||||
|
mindmapDb,
|
||||||
|
mindmapRenderer,
|
||||||
|
undefined,
|
||||||
|
mindmapDetector,
|
||||||
|
mindmapStyles
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default addDiagrams;
|
export default addDiagrams;
|
||||||
|
@@ -35,15 +35,19 @@ import journeyDb from '../diagrams/user-journey/journeyDb';
|
|||||||
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
|
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
|
||||||
import journeyParser from '../diagrams/user-journey/parser/journey';
|
import journeyParser from '../diagrams/user-journey/parser/journey';
|
||||||
import { addDetector } from './detectType';
|
import { addDetector } from './detectType';
|
||||||
|
import { addStylesForDiagram } from '../styles';
|
||||||
import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
|
import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
|
||||||
import { getConfig as _getConfig } from '../config';
|
import { getConfig as _getConfig } from '../config';
|
||||||
|
import { log as _log } from '../logger';
|
||||||
|
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let diagramTitle = '';
|
let diagramTitle = '';
|
||||||
let description = '';
|
let description = '';
|
||||||
|
|
||||||
export const getConfig = _getConfig;
|
export const getConfig = _getConfig;
|
||||||
export const sanitizeText = (txt) => _sanitizeText(txt, getConfig());
|
export const sanitizeText = (txt) => _sanitizeText(txt, getConfig());
|
||||||
|
export const log = _log;
|
||||||
const diagrams = {
|
const diagrams = {
|
||||||
c4: {
|
c4: {
|
||||||
db: c4Db,
|
db: c4Db,
|
||||||
@@ -171,13 +175,16 @@ const diagrams = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// console.log(sequenceDb);
|
|
||||||
export const registerDiagram = (id, parser, db, renderer, init, detector) => {
|
export const registerDiagram = (id, parser, db, renderer, init, detector, styles) => {
|
||||||
diagrams[id] = { parser, db, renderer, init };
|
diagrams[id] = { parser, db, renderer, init };
|
||||||
addDetector(id, detector);
|
addDetector(id, detector);
|
||||||
|
addStylesForDiagram(id, styles);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDiagrams = () => {
|
export const getDiagrams = () => {
|
||||||
// console.log('diagrams', diagrams);
|
// console.log('diagrams', diagrams);
|
||||||
return diagrams;
|
return diagrams;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setupGraphViewbox = _setupGraphViewbox;
|
||||||
|
227
src/diagram-api/text-wrap
Normal file
227
src/diagram-api/text-wrap
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches results of functions based on input
|
||||||
|
*
|
||||||
|
* @param {Function} fn Function to run
|
||||||
|
* @param {Function} resolver Function that resolves to an ID given arguments the `fn` takes
|
||||||
|
* @returns {Function} An optimized caching function
|
||||||
|
*/
|
||||||
|
const memoize = (fn, resolver) => {
|
||||||
|
let cache = {};
|
||||||
|
return (...args) => {
|
||||||
|
let n = resolver ? resolver.apply(this, args) : args[0];
|
||||||
|
if (n in cache) {
|
||||||
|
return cache[n];
|
||||||
|
} else {
|
||||||
|
let result = fn(...args);
|
||||||
|
cache[n] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* This calculates the width of the given text, font size and family.
|
||||||
|
*
|
||||||
|
* @param {any} text - The text to calculate the width of
|
||||||
|
* @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the resulting size
|
||||||
|
* @returns {any} - The width for the given text
|
||||||
|
*/
|
||||||
|
export const calculateTextWidth = function (text, config) {
|
||||||
|
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
|
||||||
|
return calculateTextDimensions(text, config).width;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTextObj = function () {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
fill: undefined,
|
||||||
|
anchor: 'start',
|
||||||
|
style: '#666',
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
textMargin: 0,
|
||||||
|
rx: 0,
|
||||||
|
ry: 0,
|
||||||
|
valign: undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds text to an element
|
||||||
|
*
|
||||||
|
* @param {SVGElement} elem Element to add text to
|
||||||
|
* @param {{
|
||||||
|
* text: string;
|
||||||
|
* x: number;
|
||||||
|
* y: number;
|
||||||
|
* anchor: 'start' | 'middle' | 'end';
|
||||||
|
* fontFamily: string;
|
||||||
|
* fontSize: string | number;
|
||||||
|
* fontWeight: string | number;
|
||||||
|
* fill: string;
|
||||||
|
* class: string | undefined;
|
||||||
|
* textMargin: number;
|
||||||
|
* }} textData
|
||||||
|
* @returns {SVGTextElement} Text element with given styling and content
|
||||||
|
*/
|
||||||
|
export const drawSimpleText = function (elem, textData) {
|
||||||
|
// Remove and ignore br:s
|
||||||
|
const nText = textData.text.replace(lineBreakRegex, ' ');
|
||||||
|
|
||||||
|
const textElem = elem.append('text');
|
||||||
|
textElem.attr('x', textData.x);
|
||||||
|
textElem.attr('y', textData.y);
|
||||||
|
textElem.style('text-anchor', textData.anchor);
|
||||||
|
textElem.style('font-family', textData.fontFamily);
|
||||||
|
textElem.style('font-size', textData.fontSize);
|
||||||
|
textElem.style('font-weight', textData.fontWeight);
|
||||||
|
textElem.attr('fill', textData.fill);
|
||||||
|
if (typeof textData.class !== 'undefined') {
|
||||||
|
textElem.attr('class', textData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = textElem.append('tspan');
|
||||||
|
span.attr('x', textData.x + textData.textMargin * 2);
|
||||||
|
span.attr('fill', textData.fill);
|
||||||
|
span.text(nText);
|
||||||
|
|
||||||
|
return textElem;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This calculates the dimensions of the given text, font size, font family, font weight, and margins.
|
||||||
|
*
|
||||||
|
* @param {any} text - The text to calculate the width of
|
||||||
|
* @param {any} config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
|
||||||
|
* the resulting size
|
||||||
|
* @returns - The width for the given text
|
||||||
|
*/
|
||||||
|
export const calculateTextDimensions = memoize(
|
||||||
|
function (text, config) {
|
||||||
|
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
|
||||||
|
const { fontSize, fontFamily, fontWeight } = config;
|
||||||
|
if (!text) {
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't really know if the user supplied font family will render on the user agent;
|
||||||
|
// thus, we'll take the max width between the user supplied font family, and a default
|
||||||
|
// of sans-serif.
|
||||||
|
const fontFamilies = ['sans-serif', fontFamily];
|
||||||
|
const lines = text.split(common.lineBreakRegex);
|
||||||
|
let dims = [];
|
||||||
|
|
||||||
|
const body = select('body');
|
||||||
|
// We don't want to leak DOM elements - if a removal operation isn't available
|
||||||
|
// for any reason, do not continue.
|
||||||
|
if (!body.remove) {
|
||||||
|
return { width: 0, height: 0, lineHeight: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const g = body.append('svg');
|
||||||
|
|
||||||
|
for (let fontFamily of fontFamilies) {
|
||||||
|
let cheight = 0;
|
||||||
|
let dim = { width: 0, height: 0, lineHeight: 0 };
|
||||||
|
for (let line of lines) {
|
||||||
|
const textObj = getTextObj();
|
||||||
|
textObj.text = line;
|
||||||
|
const textElem = drawSimpleText(g, textObj)
|
||||||
|
.style('font-size', fontSize)
|
||||||
|
.style('font-weight', fontWeight)
|
||||||
|
.style('font-family', fontFamily);
|
||||||
|
|
||||||
|
let bBox = (textElem._groups || textElem)[0][0].getBBox();
|
||||||
|
dim.width = Math.round(Math.max(dim.width, bBox.width));
|
||||||
|
cheight = Math.round(bBox.height);
|
||||||
|
dim.height += cheight;
|
||||||
|
dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
|
||||||
|
}
|
||||||
|
dims.push(dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.remove();
|
||||||
|
|
||||||
|
let index =
|
||||||
|
isNaN(dims[1].height) ||
|
||||||
|
isNaN(dims[1].width) ||
|
||||||
|
isNaN(dims[1].lineHeight) ||
|
||||||
|
(dims[0].height > dims[1].height &&
|
||||||
|
dims[0].width > dims[1].width &&
|
||||||
|
dims[0].lineHeight > dims[1].lineHeight)
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
return dims[index];
|
||||||
|
},
|
||||||
|
(text, config) => `${text}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const breakString = memoize(
|
||||||
|
(word, maxWidth, hyphenCharacter = '-', config) => {
|
||||||
|
config = Object.assign(
|
||||||
|
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 },
|
||||||
|
config
|
||||||
|
);
|
||||||
|
const characters = word.split('');
|
||||||
|
const lines = [];
|
||||||
|
let currentLine = '';
|
||||||
|
characters.forEach((character, index) => {
|
||||||
|
const nextLine = `${currentLine}${character}`;
|
||||||
|
const lineWidth = calculateTextWidth(nextLine, config);
|
||||||
|
if (lineWidth >= maxWidth) {
|
||||||
|
const currentCharacter = index + 1;
|
||||||
|
const isLastLine = characters.length === currentCharacter;
|
||||||
|
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
|
||||||
|
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
|
||||||
|
currentLine = '';
|
||||||
|
} else {
|
||||||
|
currentLine = nextLine;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { hyphenatedStrings: lines, remainingWord: currentLine };
|
||||||
|
},
|
||||||
|
(word, maxWidth, hyphenCharacter = '-', config) =>
|
||||||
|
`${word}-${maxWidth}-${hyphenCharacter}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
|
||||||
|
);
|
||||||
|
|
||||||
|
export const wrapLabel = memoize(
|
||||||
|
(label, maxWidth, config) => {
|
||||||
|
if (!label) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
config = Object.assign(
|
||||||
|
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '<br/>' },
|
||||||
|
config
|
||||||
|
);
|
||||||
|
if (lineBreakRegex.test(label)) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
const words = label.split(' ');
|
||||||
|
const completedLines = [];
|
||||||
|
let nextLine = '';
|
||||||
|
words.forEach((word, index) => {
|
||||||
|
const wordLength = calculateTextWidth(`${word} `, config);
|
||||||
|
const nextLineLength = calculateTextWidth(nextLine, config);
|
||||||
|
if (wordLength > maxWidth) {
|
||||||
|
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
|
||||||
|
completedLines.push(nextLine, ...hyphenatedStrings);
|
||||||
|
nextLine = remainingWord;
|
||||||
|
} else if (nextLineLength + wordLength >= maxWidth) {
|
||||||
|
completedLines.push(nextLine);
|
||||||
|
nextLine = word;
|
||||||
|
} else {
|
||||||
|
nextLine = [nextLine, word].filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
const currentWord = index + 1;
|
||||||
|
const isLastWord = currentWord === words.length;
|
||||||
|
if (isLastWord) {
|
||||||
|
completedLines.push(nextLine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return completedLines.filter((line) => line !== '').join(config.joinWith);
|
||||||
|
},
|
||||||
|
(label, maxWidth, config) =>
|
||||||
|
`${label}-${maxWidth}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}-${config.joinWith}`
|
||||||
|
);
|
@@ -1,6 +1,5 @@
|
|||||||
/** Created by knut on 15-01-14. */
|
/** Created by knut on 15-01-14. */
|
||||||
import { log } from '../../logger';
|
import { log, sanitizeText, getConfig } from '../../diagram-api/diagramAPI';
|
||||||
import { sanitizeText } from '../../diagram-api/diagramAPI';
|
|
||||||
|
|
||||||
var message = '';
|
var message = '';
|
||||||
var info = false;
|
var info = false;
|
||||||
@@ -26,7 +25,14 @@ export const getMindmap = () => {
|
|||||||
return nodes.length > 0 ? nodes[0] : null;
|
return nodes.length > 0 ? nodes[0] : null;
|
||||||
};
|
};
|
||||||
export const addNode = (level, id, descr, type) => {
|
export const addNode = (level, id, descr, type) => {
|
||||||
const node = { id: sanitizeText(id), level, descr: sanitizeText(descr), type, children: [] };
|
const node = {
|
||||||
|
id: sanitizeText(id),
|
||||||
|
level,
|
||||||
|
descr: sanitizeText(descr),
|
||||||
|
type,
|
||||||
|
children: [],
|
||||||
|
width: getConfig().mindmap.maxNodeWidth,
|
||||||
|
};
|
||||||
const parent = getParent(level);
|
const parent = getParent(level);
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.children.push(node);
|
parent.children.push(node);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const detector = (txt) => {
|
const detector = function detect(txt) {
|
||||||
if (txt.match(/^\s*mindmap/)) {
|
if (txt.match(/^\s*mindmap/)) {
|
||||||
return 'mindmap';
|
return 'mindmap';
|
||||||
}
|
}
|
@@ -1,7 +1,33 @@
|
|||||||
/** Created by knut on 14-12-11. */
|
/** Created by knut on 14-12-11. */
|
||||||
import { select } from 'd3';
|
import { select } from 'd3';
|
||||||
import { log } from '../../logger';
|
import { log, getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
|
||||||
import { getConfig } from '../../config';
|
import svgDraw from './svgDraw';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} svg The svg element to draw the diagram onto
|
||||||
|
* @param {object} mindmap The maindmap data and hierarchy
|
||||||
|
* @param {object} conf The configuration object
|
||||||
|
*/
|
||||||
|
function drawNodes(svg, mindmap, conf) {
|
||||||
|
svgDraw.drawNode(svg, mindmap, conf);
|
||||||
|
if (mindmap.children) {
|
||||||
|
mindmap.children.forEach((child) => {
|
||||||
|
drawNodes(svg, child, conf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawEdges() {}
|
||||||
|
/**
|
||||||
|
* @param node
|
||||||
|
* @param isRoot
|
||||||
|
*/
|
||||||
|
function layoutMindmap(node, isRoot) {}
|
||||||
|
/**
|
||||||
|
* @param node
|
||||||
|
* @param isRoot
|
||||||
|
*/
|
||||||
|
function positionNodes(node, isRoot) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a an info picture in the tag with id: id based on the graph definition in text.
|
* Draws a an info picture in the tag with id: id based on the graph definition in text.
|
||||||
@@ -12,6 +38,7 @@ import { getConfig } from '../../config';
|
|||||||
* @param diagObj
|
* @param diagObj
|
||||||
*/
|
*/
|
||||||
export const draw = (text, id, version, diagObj) => {
|
export const draw = (text, id, version, diagObj) => {
|
||||||
|
const conf = getConfig();
|
||||||
try {
|
try {
|
||||||
// const parser = infoParser.parser;
|
// const parser = infoParser.parser;
|
||||||
// parser.yy = db;
|
// parser.yy = db;
|
||||||
@@ -30,24 +57,40 @@ export const draw = (text, id, version, diagObj) => {
|
|||||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||||
|
|
||||||
// Parse the graph definition
|
// Parse the graph definition
|
||||||
// parser.parse(text);
|
|
||||||
// log.debug('Parsed info diagram');
|
|
||||||
// Fetch the default direction, use TD if none was found
|
|
||||||
const svg = root.select('#' + id);
|
const svg = root.select('#' + id);
|
||||||
|
|
||||||
const g = svg.append('g');
|
const g = svg.append('g');
|
||||||
|
const mm = diagObj.db.getMindmap();
|
||||||
|
|
||||||
g.append('text') // text label for the x axis
|
// mm.x = 0;
|
||||||
.attr('x', 100)
|
// mm.y = 0;
|
||||||
.attr('y', 40)
|
// svgDraw.drawNode(g, mm, getConfig());
|
||||||
.attr('class', 'version')
|
// mm.children.forEach((child) => {
|
||||||
.attr('font-size', '32px')
|
// child.x = 200;
|
||||||
.style('text-anchor', 'middle')
|
// child.y = 200;
|
||||||
.text('v ' + version);
|
// child.width = 200;
|
||||||
|
// svgDraw.drawNode(g, child, getConfig());
|
||||||
|
// });
|
||||||
|
|
||||||
svg.attr('height', 100);
|
// Draw the graph and start with drawing the nodes without proper position
|
||||||
svg.attr('width', 400);
|
// this gives us the size of the nodes and we can set the positions later
|
||||||
// svg.attr('viewBox', '0 0 300 150');
|
|
||||||
|
const nodesElem = svg.append('g');
|
||||||
|
nodesElem.attr('class', 'mindmap-nodes');
|
||||||
|
drawNodes(nodesElem, mm, conf);
|
||||||
|
|
||||||
|
// Next step is to layout the mindmap, giving each node a position
|
||||||
|
|
||||||
|
// layoutMindmap(mm, conf);
|
||||||
|
|
||||||
|
// After this we can draw, first the edges and the then nodes with the correct position
|
||||||
|
// drawEdges(svg, mm, conf);
|
||||||
|
|
||||||
|
// positionNodes(svg, mm, conf);
|
||||||
|
|
||||||
|
// Setup the view box and size of the svg element
|
||||||
|
setupGraphViewbox(undefined, svg, conf.mindmap.diagramPadding, conf.mindmap.useMaxWidth);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Error while rendering info diagram');
|
log.error('Error while rendering info diagram');
|
||||||
log.error(e.message);
|
log.error(e.message);
|
||||||
|
@@ -1,3 +1,9 @@
|
|||||||
const getStyles = () => ``;
|
const getStyles = (options) =>
|
||||||
|
`
|
||||||
|
.node{
|
||||||
|
stroke: ${options.pieStrokeColor};
|
||||||
|
stroke-width : ${options.pieStrokeWidth};
|
||||||
|
opacity : ${options.pieOpacity};
|
||||||
|
}
|
||||||
|
`;
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
100
src/diagrams/mindmap/svgDraw.js
Normal file
100
src/diagrams/mindmap/svgDraw.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
const lineBreakRegex = /<br\s*\/?>/gi;
|
||||||
|
import { select } from 'd3';
|
||||||
|
/**
|
||||||
|
* @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 = [],
|
||||||
|
lineNumber = 0,
|
||||||
|
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 {object} elem The D3 dom element in which the node is to be added
|
||||||
|
* @param {object} node The node to be added
|
||||||
|
* @param {object} conf The configuration object
|
||||||
|
* @returns {number} The height nodes dom element
|
||||||
|
*/
|
||||||
|
export const drawNode = function (elem, node, conf) {
|
||||||
|
const nodeElem = elem.append('g');
|
||||||
|
nodeElem.attr('class', 'mindmap-node');
|
||||||
|
|
||||||
|
const rect = {
|
||||||
|
fill: '#EDF2AE',
|
||||||
|
stroke: '#666',
|
||||||
|
width: node.width,
|
||||||
|
anchor: 'start',
|
||||||
|
height: 100,
|
||||||
|
rx: 3,
|
||||||
|
ry: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const r = nodeElem
|
||||||
|
.append('rect')
|
||||||
|
.attr('x', (-1 * node.width) / 2)
|
||||||
|
.attr('width', node.width)
|
||||||
|
.attr('fill', rect.fill)
|
||||||
|
.attr('stroke', rect.stroke)
|
||||||
|
.attr('rx', rect.rx)
|
||||||
|
.attr('ry', rect.ry);
|
||||||
|
|
||||||
|
const textElem = nodeElem.append('g');
|
||||||
|
// .attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||||
|
|
||||||
|
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();
|
||||||
|
node.height = bbox.height + conf.fontSize * 1.1 * 0.5;
|
||||||
|
r.attr('height', node.height).attr('y', (-1 * node.height) / 2);
|
||||||
|
|
||||||
|
txt.attr('transform', 'translate( 0,' + (-1 * node.height) / 2 + ')');
|
||||||
|
// Position the node to its coordinate
|
||||||
|
if (node.x || node.y) {
|
||||||
|
nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
|
||||||
|
}
|
||||||
|
return node.height;
|
||||||
|
};
|
||||||
|
export default { drawNode };
|
102
src/setupGraphViewbox.js
Normal file
102
src/setupGraphViewbox.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { log } from './logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applys d3 attributes
|
||||||
|
*
|
||||||
|
* @param {any} d3Elem D3 Element to apply the attributes onto
|
||||||
|
* @param {[string, string][]} attrs Object.keys equivalent format of key to value mapping of attributes
|
||||||
|
*/
|
||||||
|
const d3Attrs = function (d3Elem, attrs) {
|
||||||
|
for (let attr of attrs) {
|
||||||
|
d3Elem.attr(attr[0], attr[1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives attributes for an SVG's size given arguments
|
||||||
|
*
|
||||||
|
* @param {number} height The height of the SVG
|
||||||
|
* @param {number} width The width of the SVG
|
||||||
|
* @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
|
||||||
|
* @returns {Map<'height' | 'width' | 'style', string>} Attributes for the SVG
|
||||||
|
*/
|
||||||
|
export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
|
||||||
|
let attrs = new Map();
|
||||||
|
attrs.set('height', height);
|
||||||
|
if (useMaxWidth) {
|
||||||
|
attrs.set('width', '100%');
|
||||||
|
attrs.set('style', `max-width: ${width}px;`);
|
||||||
|
} else {
|
||||||
|
attrs.set('width', width);
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies attributes from `calculateSvgSizeAttrs`
|
||||||
|
*
|
||||||
|
* @param {SVGSVGElement} svgElem The SVG Element to configure
|
||||||
|
* @param {number} height The height of the SVG
|
||||||
|
* @param {number} width The width of the SVG
|
||||||
|
* @param tx
|
||||||
|
* @param ty
|
||||||
|
* @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
|
||||||
|
*/
|
||||||
|
export const configureSvgSize = function (svgElem, height, width, tx, ty, useMaxWidth) {
|
||||||
|
const attrs = calculateSvgSizeAttrs(height, width, useMaxWidth);
|
||||||
|
d3Attrs(svgElem, attrs);
|
||||||
|
};
|
||||||
|
export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth) {
|
||||||
|
const svgBounds = svgElem.node().getBBox();
|
||||||
|
const sWidth = svgBounds.width;
|
||||||
|
const sHeight = svgBounds.height;
|
||||||
|
|
||||||
|
let width;
|
||||||
|
let height;
|
||||||
|
let tx = 0;
|
||||||
|
let ty = 0;
|
||||||
|
if (graph) {
|
||||||
|
width = graph._label.width;
|
||||||
|
height = graph._label.height;
|
||||||
|
if (sWidth > width) {
|
||||||
|
tx = (sWidth - width) / 2 + padding;
|
||||||
|
width = sWidth + padding * 2;
|
||||||
|
} else {
|
||||||
|
if (Math.abs(sWidth - width) >= 2 * padding + 1) {
|
||||||
|
width = width - padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sHeight > height) {
|
||||||
|
ty = (sHeight - height) / 2 + padding;
|
||||||
|
height = sHeight + padding * 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
width = sWidth + padding * 2;
|
||||||
|
height = sHeight + padding * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the viewBox includes the whole svgBounds area with extra space for padding
|
||||||
|
const vBox = graph
|
||||||
|
? `0 0 ${width} ${height}`
|
||||||
|
: `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`;
|
||||||
|
console.log(
|
||||||
|
'Graph.label',
|
||||||
|
graph ? graph._label : null,
|
||||||
|
'swidth',
|
||||||
|
sWidth,
|
||||||
|
'sheight',
|
||||||
|
sHeight,
|
||||||
|
'width',
|
||||||
|
width,
|
||||||
|
'height',
|
||||||
|
height,
|
||||||
|
'tx',
|
||||||
|
tx,
|
||||||
|
'ty',
|
||||||
|
ty,
|
||||||
|
'vBox',
|
||||||
|
vBox
|
||||||
|
);
|
||||||
|
svgElem.attr('viewBox', vBox);
|
||||||
|
svgElem.select('g').attr('transform', `translate(${tx}, ${ty})`);
|
||||||
|
};
|
@@ -2,7 +2,7 @@ import classDiagram from './diagrams/class/styles';
|
|||||||
import er from './diagrams/er/styles';
|
import er from './diagrams/er/styles';
|
||||||
import flowchart from './diagrams/flowchart/styles';
|
import flowchart from './diagrams/flowchart/styles';
|
||||||
import gantt from './diagrams/gantt/styles';
|
import gantt from './diagrams/gantt/styles';
|
||||||
import gitGraph from './diagrams/git/styles';
|
// import gitGraph from './diagrams/git/styles';
|
||||||
import info from './diagrams/info/styles';
|
import info from './diagrams/info/styles';
|
||||||
import pie from './diagrams/pie/styles';
|
import pie from './diagrams/pie/styles';
|
||||||
import requirement from './diagrams/requirement/styles';
|
import requirement from './diagrams/requirement/styles';
|
||||||
@@ -22,7 +22,7 @@ const themes = {
|
|||||||
class: classDiagram,
|
class: classDiagram,
|
||||||
stateDiagram,
|
stateDiagram,
|
||||||
state: stateDiagram,
|
state: stateDiagram,
|
||||||
gitGraph,
|
// gitGraph,
|
||||||
info,
|
info,
|
||||||
pie,
|
pie,
|
||||||
er,
|
er,
|
||||||
@@ -89,4 +89,8 @@ const getStyles = (type, userStyles, options) => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addStylesForDiagram = (type, diagramTheme, options) => {
|
||||||
|
themes[type] = diagramTheme;
|
||||||
|
};
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import assignWithDepth from './assignWithDepth';
|
import assignWithDepth from './assignWithDepth';
|
||||||
import detectType from './diagram-api/detectType';
|
import detectType from './diagram-api/detectType';
|
||||||
import './diagram-api/diagram-orchestration';
|
import addDiagrams from './diagram-api/diagram-orchestration';
|
||||||
|
|
||||||
|
// Orchestrating diagrams and adding the dynamic ones to the list of diagrams
|
||||||
|
addDiagrams();
|
||||||
|
|
||||||
describe('when assignWithDepth: should merge objects within objects', function () {
|
describe('when assignWithDepth: should merge objects within objects', function () {
|
||||||
it('should handle simple, depth:1 types (identity)', function () {
|
it('should handle simple, depth:1 types (identity)', function () {
|
||||||
@@ -214,7 +217,7 @@ Alice->Bob: hi`;
|
|||||||
const type = detectType(str);
|
const type = detectType(str);
|
||||||
expect(type).toBe('flowchart');
|
expect(type).toBe('flowchart');
|
||||||
});
|
});
|
||||||
it('should handle a graph definition for gitGraph', function () {
|
fit('should handle a graph definition for gitGraph', function () {
|
||||||
const str = ' \n gitGraph TB:\nbfs1:queue';
|
const str = ' \n gitGraph TB:\nbfs1:queue';
|
||||||
const type = detectType(str);
|
const type = detectType(str);
|
||||||
expect(type).toBe('gitGraph');
|
expect(type).toBe('gitGraph');
|
||||||
|
Reference in New Issue
Block a user