Merge branch 'develop' into feature/add-point-styling-quadrant-to-charts

This commit is contained in:
ilyes-ced 2024-02-14 18:21:51 +00:00 committed by GitHub
commit af7364bdf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 719 additions and 207 deletions

View File

@ -68,6 +68,7 @@
"jgreywolf",
"jison",
"jiti",
"katex",
"kaufmann",
"khroma",
"klemm",
@ -81,6 +82,7 @@
"logmsg",
"lucida",
"markdownish",
"mathml",
"matthieu",
"matthieumorel",
"mdast",

View File

@ -0,0 +1,36 @@
import { imgSnapshotTest } from '../../helpers/util';
describe('Katex', () => {
it('1: should render a complex Katex flowchart no htmlLabels', () => {
imgSnapshotTest(
`graph LR
A["$$f(\\relax{x}) = \\int_{-\\infty}^\\infty \\hat{f}(\\xi)\\,e^{2 \\pi i \\xi x}\\,d\\xi$$"] -->|"$$\\Bigg(\\bigg(\\Big(\\big((\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a})\\big)\\Big)\\bigg)\\Bigg)$$"| B("$$1+\\frac{e^{-2\\pi}} {1+\\frac{e^{-4\\pi}} {1+\\frac{e^{-6\\pi}} {1+\\frac{e^{-8\\pi}} {1+\\cdots}}}}$$")
A -->|"$$\\overbrace{a+b+c}^{\\text{note}}$$"| C("$$\\phase{-78^\\circ}$$")
B --> D("$$x = \\begin{cases} a &\\text{if } b \\\\ c &\\text{if } d \\end{cases}$$")
C --> E("$$x(t)=c_1\\begin{bmatrix}-\\cos{t}+\\sin{t}\\\\ 2\\cos{t} \\end{bmatrix}e^{2t}$$")`,
{ fontFamily: 'courier' }
);
});
it('2: should render a Katex flowchart containing the Greek alphabet', () => {
imgSnapshotTest(
`graph LR
A["$$\\alpha\\beta\\gamma\\delta\\epsilon\\zeta\\eta\\theta\\iota\\kappa\\lambda\\mu\\nu\\xi\\omicron\\pi\\rho\\sigma\\tau\\upsilon\\phi\\chi\\psi\\omega$$"] --> B["$$\\Alpha\\Beta\\Gamma\\Delta\\Epsilon\\Zeta\\Eta\\Theta\\Iota\\Kappa\\Lambda\\Mu\\Nu\\Xi\\Omicron\\Pi\\Rho\\Sigma\\Tau\\Upsilon\\Phi\\Chi\\Psi\\Omega$$"]`,
{ fontFamily: 'courier' }
);
});
it('3: should render a Katex flowchart containing set theory symbols', () => {
imgSnapshotTest(
`graph LR
A["$$\\forall\\complement\\therefore\\emptyset\\exists\\subset\\because\\empty\\exist\\supset\\mapsto\\varnothing\\nexists\\mid\\to\\implies\\in\\land\\gets\\impliedby\\isin\\lor\\leftrightarrow\\iff\\notin\\ni\\notni\\lnot$$"] --> B["$$\\nabla\\Im\\Reals\\jmath\\partial\\image\\wp\\aleph\\Game\\weierp\\alef\\Finv\\N\\Z\\alefsym\\cnums\\natnums\\beth\\Complex\\R\\gimel\\ell\\Re\\daleth\\hbar\\real\\eth\\hslash\\reals$$"]`,
{ fontFamily: 'courier' }
);
});
// TODO: changes made to develop between Feb 13 - Feb 23 cause this test to no longer function
// it.skip('4: should render an error box originating from Katex', () => {
// imgSnapshotTest(
// `graph LR
// A["$$\\shouldBeError$$"]`,
// { fontFamily: 'courier' }
// );
// });
});

View File

@ -1102,6 +1102,57 @@
</pre>
<hr />
<h2>Sample 20</h2>
<h3>graph</h3>
<pre class="mermaid">
graph LR
A["$$f(\relax{x}) = \int_{-\infty}^\infty \hat{f}(\xi)\,e^{2 \pi i \xi x}\,d\xi$$"] -->|"$$\Bigg(\bigg(\Big(\big((\frac{-b\pm\sqrt{b^2-4ac}}{2a})\big)\Big)\bigg)\Bigg)$$"| B("$$1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots}}}}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\phase{-78^\circ}$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
</pre>
<hr />
<h3>flowchart</h3>
<pre class="mermaid">
flowchart LR
A["$$f(\relax{x}) = \int_{-\infty}^\infty \hat{f}(\xi)\,e^{2 \pi i \xi x}\,d\xi$$"] -->|"$$\Bigg(\bigg(\Big(\big((\frac{-b\pm\sqrt{b^2-4ac}}{2a})\big)\Big)\bigg)\Bigg)$$"| B("$$1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots}}}}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\phase{-78^\circ}$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
</pre>
<hr />
<h2>Sample 21</h2>
<h3>graph</h3>
<pre class="mermaid">
graph LR
A["$$\alpha\beta\gamma\delta\epsilon\zeta\eta\theta\iota\kappa\lambda\mu\nu\xi\omicron\pi\rho\sigma\tau\upsilon\phi\chi\psi\omega$$"] --> B["$$\Alpha\Beta\Gamma\Delta\Epsilon\Zeta\Eta\Theta\Iota\Kappa\Lambda\Mu\Nu\Xi\Omicron\Pi\Rho\Sigma\Tau\Upsilon\Phi\Chi\Psi\Omega$$"]
</pre>
<hr />
<h3>flowchart</h3>
<pre class="mermaid">
graph LR
A["$$\alpha\beta\gamma\delta\epsilon\zeta\eta\theta\iota\kappa\lambda\mu\nu\xi\omicron\pi\rho\sigma\tau\upsilon\phi\chi\psi\omega$$"] --> B["$$\Alpha\Beta\Gamma\Delta\Epsilon\Zeta\Eta\Theta\Iota\Kappa\Lambda\Mu\Nu\Xi\Omicron\Pi\Rho\Sigma\Tau\Upsilon\Phi\Chi\Psi\Omega$$"]
</pre>
<hr />
<h2>Sample 22</h2>
<h3>graph</h3>
<pre class="mermaid">
graph LR
A["$$\forall\complement\therefore\emptyset\exists\subset\because\empty\exist\supset\mapsto\varnothing\nexists\mid\to\implies\in\land\gets\impliedby\isin\lor\leftrightarrow\iff\notin\ni\notni\lnot$$"] --> B["$$\nabla\Im\Reals\jmath\partial\image\wp\aleph\Game\weierp\alef\Finv\N\Z\alefsym\cnums\natnums\beth\Complex\R\gimel\ell\Re\daleth\hbar\real\eth\hslash\reals$$"]
</pre>
<hr />
<h3>flowchart</h3>
<pre class="mermaid">
graph LR
A["$$\forall\complement\therefore\emptyset\exists\subset\because\empty\exist\supset\mapsto\varnothing\nexists\mid\to\implies\in\land\gets\impliedby\isin\lor\leftrightarrow\iff\notin\ni\notni\lnot$$"] --> B["$$\nabla\Im\Reals\jmath\partial\image\wp\aleph\Game\weierp\alef\Finv\N\Z\alefsym\cnums\natnums\beth\Complex\R\gimel\ell\Re\daleth\hbar\real\eth\hslash\reals$$"]
</pre>
<hr />
<hr />
<pre class="mermaid">
@ -1524,11 +1575,11 @@
F{Flow 2} == Choice 2.1 ==> H[Feedback node]
H[Feedback node] ==> B[Step 1]
F{Flow 2} == Choice 2.2 ==> G((Finish))
linkStyle 0,1,4,6,7,8,9 stroke:gold, stroke-width:4px
classDef active_node fill:#0CF,stroke:#09F,stroke-width:6px
classDef unactive_node fill:#e0e0e0,stroke:#bdbdbd,stroke-width:3px
classDef unactive_node fill:#e0e0e0,stroke:#bdbdbd,stroke-width:3px
classDef bugged_node fill:#F88,stroke:#F22,stroke-width:3px
classDef start_node,finish_node fill:#3B1,stroke:#391,stroke-width:8px

View File

@ -16,9 +16,9 @@
<body>
<h1>Sequence diagram demos</h1>
<pre class="mermaid">
sequenceDiagram
accTitle: test the accTitle
accDescr: Test a description
sequenceDiagram
accTitle: test the accTitle
accDescr: Test a description
participant Alice
participant Bob
@ -31,39 +31,39 @@
rect rgb(200, 220, 100)
rect rgb(200, 255, 200)
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
end
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
end
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: John thinks a long<br />long time, so long<br />that the text does<br />not fit on a row.
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: John thinks a long<br />long time, so long<br />that the text does<br />not fit on a row.
Bob-->Alice: Checking with John...
Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit.
Bob-x John: Hey John - we're still waiting to know<br />how you're doing
Note over John:nowrap: John's trying hard not to break his train of thought.
Bob-x John:wrap: John! Are you still debating about how you're doing? How long does it take??
Note over John: After a few more moments, John<br />finally snaps out of it.
end
Bob-->Alice: Checking with John...
Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit.
Bob-x John: Hey John - we're still waiting to know<br />how you're doing
Note over John:nowrap: John's trying hard not to break his train of thought.
Bob-x John:wrap: John! Are you still debating about how you're doing? How long does it take??
Note over John: After a few more moments, John<br />finally snaps out of it.
end
autonumber off
alt either this
Alice->>+John: Yes
John-->>-Alice: OK
else or this
autonumber
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
autonumber 200
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
</pre>
autonumber off
alt either this
Alice->>+John: Yes
John-->>-Alice: OK
else or this
autonumber
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
autonumber 200
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
</pre>
<hr />
<pre class="mermaid">
---
@ -153,18 +153,18 @@
<hr />
<pre class="mermaid">
sequenceDiagram
box lightgreen Alice & John
participant A
participant J
end
box Another Group very very long description not wrapped
participant B
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how are you ?
</pre
sequenceDiagram
box lightgreen Alice & John
participant A
participant J
end
box Another Group very very long description not wrapped
participant B
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how are you ?
</pre
>
<hr />
@ -188,6 +188,49 @@
end
</pre>
<hr />
<pre class="mermaid">
sequenceDiagram
participant 1 as $$\frac{\lim_{x\rightarrow0}{\frac{1}{x}}}{\frac{-b\pm\sqrt{b^2-4ac}}{2a}}$$
participant 2 as $$\beta$$
participant 3 as $$\delta$$
participant 4 as $$\frac{\frac{\lim_{x\rightarrow0}{\frac{1}{x}}}{\frac{-b\pm\sqrt{b^2-4ac}}{2a}}}{\frac{\text{d}}{\text{d}x}{x^2}}$$
1->>2: $$\sqrt{2}$$
note right of 2: $$\frac{1+\frac{1+\frac{1+\frac{1}{2}}{2}}{2}}{2}+\frac{-b\pm\sqrt{b^2-4ac}}{2a}$$
2->>3: $$\frac{\lim_{x\rightarrow0}{\frac{1}{x}}}{\frac{-b\pm\sqrt{b^2-4ac}}{2a}}$$
note right of 3: $$\frac{-b\pm\sqrt{b^2-4ac}}{2a}$$
3->>4: $$\lim_{x\rightarrow0}{\frac{1}{x}}$$;
note right of 4: multiline
4->>1: multiline<br />using #lt;br /#gt;
note right of 1: multiline<br />$$\frac{1}{2}$$<br />3rd line
</pre>
<hr />
<pre class="mermaid">
sequenceDiagram
autonumber
participant 1 as $$\alpha$$lex
participant 2 as $$\beta$$ob
participant 3 as $$\theta$$iffany
1->>2: Hello John, does&nbsp; $$\frac{1}{2}+1=2$$?
loop $$\frac{1}{2}+1=2$$
2->>2: $$\frac{1}{2}+1=\frac{3}{2}$$
end
Note right of 2: $$x = \begin{cases} 1 &\text{if } \frac{1}{2}+1=2 \\ 0 &\text{if } \frac{1}{2}+1\ne2 \end{cases}$$
2-->>1: $$\frac{1}{2}+1\ne2\implies 1$$
2->>3: $$\frac{\text{d}}{\text{d}x}{3x^2+2x+1}$$
3-->>2: $$6x+2$$
</pre>
<hr />
<pre class="mermaid">
sequenceDiagram
actor Alice
actor John
Alice-xJohn: Hello John, how are you?
John--xAlice: Great!
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({

86
docs/config/math.md Normal file
View File

@ -0,0 +1,86 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/math.md](../../packages/mermaid/src/docs/config/math.md).
# Math Configuration (v\<MERMAID_RELEASE_VERSION>+)
Mermaid supports rendering mathematical expressions through the [KaTeX](https://katex.org/) typesetter.
## Usage
To render math within a diagram, surround the mathematical expression with the `$$` delimiter.
Note that at the moment, the only supported diagrams are below:
### Flowcharts
```mermaid-example
graph LR
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
```mermaid
graph LR
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
### Sequence
```mermaid-example
sequenceDiagram
autonumber
participant 1 as $$\alpha$$
participant 2 as $$\beta$$
1->>2: Solve: $$\sqrt{2+2}$$
2-->>1: Answer: $$2$$
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
```
```mermaid
sequenceDiagram
autonumber
participant 1 as $$\alpha$$
participant 2 as $$\beta$$
1->>2: Solve: $$\sqrt{2+2}$$
2-->>1: Answer: $$2$$
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
```
## Legacy Support
By default, MathML is used for rendering mathematical expressions. If you have users on [unsupported browsers](https://caniuse.com/?search=mathml), `legacyMathML` can be set in the config to fall back to CSS rendering. Note that **you must provide KaTeX's stylesheets on your own** as they do not come bundled with Mermaid.
Example with legacy mode enabled (the latest version of KaTeX's stylesheet can be found on their [docs](https://katex.org/docs/browser.html)):
```html
<!DOCTYPE html>
<!-- KaTeX requires the use of the HTML5 doctype. Without it, KaTeX may not render properly -->
<html lang="en">
<head>
<!-- Please ensure the stylesheet's version matches with the KaTeX version in your package-lock -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@{version_number}/dist/katex.min.css"
integrity="sha384-{hash}"
crossorigin="anonymous"
/>
</head>
<body>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
legacyMathML: true,
});
</script>
</body>
</html>
```

View File

@ -70,6 +70,7 @@
"dayjs": "^1.11.7",
"dompurify": "^3.0.5",
"elkjs": "^0.9.0",
"katex": "^0.16.9",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"mdast-util-from-markdown": "^1.3.0",
@ -89,6 +90,7 @@
"@types/d3-shape": "^3.1.1",
"@types/dompurify": "^3.0.2",
"@types/jsdom": "^21.1.1",
"@types/katex": "^0.16.7",
"@types/lodash-es": "^4.17.7",
"@types/micromatch": "^4.0.2",
"@types/prettier": "^2.7.2",

View File

@ -127,6 +127,14 @@ export interface MermaidConfig {
*
*/
secure?: string[];
/**
* This option specifies if Mermaid can expect the dependent to include KaTeX stylesheets for browsers
* without their own MathML implementation. If this option is disabled and MathML is not supported, the math
* equations are replaced with a warning. If this option is enabled and MathML is not supported, Mermaid will
* fall back to legacy rendering for KaTeX.
*
*/
legacyMathML?: boolean;
/**
* This option controls if the generated ids of nodes in the SVG are
* generated randomly or based on a seed.

View File

@ -289,6 +289,83 @@ const processSet = (input: string): string => {
return chars.join('');
};
// TODO: find a better method for detecting support. This interface was added in the MathML 4 spec.
// Firefox versions between [4,71] (0.47%) and Safari versions between [5,13.4] (0.17%) don't have this interface implemented but MathML is supported
export const isMathMLSupported = () => window.MathMLElement !== undefined;
export const katexRegex = /\$\$(.*)\$\$/g;
/**
* Whether or not a text has KaTeX delimiters
*
* @param text - The text to test
* @returns Whether or not the text has KaTeX delimiters
*/
export const hasKatex = (text: string): boolean => (text.match(katexRegex)?.length ?? 0) > 0;
/**
* Computes the minimum dimensions needed to display a div containing MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns Object containing \{width, height\}
*/
export const calculateMathMLDimensions = async (text: string, config: MermaidConfig) => {
text = await renderKatex(text, config);
const divElem = document.createElement('div');
divElem.innerHTML = text;
divElem.id = 'katex-temp';
divElem.style.visibility = 'hidden';
divElem.style.position = 'absolute';
divElem.style.top = '0';
const body = document.querySelector('body');
body?.insertAdjacentElement('beforeend', divElem);
const dim = { width: divElem.clientWidth, height: divElem.clientHeight };
divElem.remove();
return dim;
};
/**
* Attempts to render and return the KaTeX portion of a string with MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing MathML if KaTeX is supported, or an error message if it is not and stylesheets aren't present
*/
export const renderKatex = async (text: string, config: MermaidConfig): Promise<string> => {
if (!hasKatex(text)) {
return text;
}
if (!isMathMLSupported() && !config.legacyMathML) {
return text.replace(katexRegex, 'MathML is unsupported in this environment.');
}
const { default: katex } = await import('katex');
return text
.split(lineBreakRegex)
.map((line) =>
hasKatex(line)
? `
<div style="display: flex; align-items: center; justify-content: center; white-space: nowrap;">
${line}
</div>
`
: `<div>${line}</div>`
)
.join('')
.replace(katexRegex, (_, c) =>
katex
.renderToString(c, {
throwOnError: true,
displayMode: true,
output: isMathMLSupported() ? 'mathml' : 'htmlAndMathml',
})
.replace(/\n/g, ' ')
.replace(/<annotation.*<\/annotation>/g, '')
);
};
export default {
getRows,
sanitizeText,

View File

@ -4,7 +4,7 @@ import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
* Draws an info picture in the tag with id: id based on the graph definition in text.
*
* @param _text - Mermaid graph definition.
* @param id - The text for the error
@ -12,12 +12,12 @@ import { configureSvgSize } from '../../setupGraphViewbox.js';
*/
export const draw = (_text: string, id: string, version: string) => {
log.debug('rendering svg for syntax error\n');
const svg: SVG = selectSvgElement(id);
const g: Group = svg.append('g');
svg.attr('viewBox', '0 0 2412 512');
configureSvgSize(svg, 100, 512, true);
const g: Group = svg.append('g');
g.append('path')
.attr('class', 'error-icon')
.attr(

View File

@ -5,7 +5,7 @@ import utils from '../../utils.js';
import { render } from '../../dagre-wrapper/index.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { log } from '../../logger.js';
import common, { evaluate } from '../common/common.js';
import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
@ -27,12 +27,12 @@ export const setConf = function (cnf) {
* @param doc
* @param diagObj
*/
export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
export const addVertices = async function (vert, g, svgId, root, doc, diagObj) {
const svg = root.select(`[id="${svgId}"]`);
const keys = Object.keys(vert);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
for (const id of keys) {
const vertex = vert[id];
/**
@ -59,10 +59,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
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>`
),
label: vertexText,
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
@ -143,11 +140,13 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
default:
_shape = 'rect';
}
const labelText = await renderKatex(vertexText, getConfig());
// Add the node
g.setNode(vertex.id, {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
labelText,
labelType: vertex.labelType,
rx: radious,
ry: radious,
@ -170,7 +169,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
labelStyle: styles.labelStyle,
labelType: vertex.labelType,
shape: _shape,
labelText: vertexText,
labelText,
rx: radious,
ry: radious,
class: classStr,
@ -183,7 +182,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
props: vertex.props,
padding: getConfig().flowchart.padding,
});
});
}
};
/**
@ -193,7 +192,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
* @param {object} g The graph object
* @param diagObj
*/
export const addEdges = function (edges, g, diagObj) {
export const addEdges = async function (edges, g, diagObj) {
log.info('abc78 edges = ', edges);
let cnt = 0;
let linkIdCnt = {};
@ -207,7 +206,7 @@ export const addEdges = function (edges, g, diagObj) {
defaultLabelStyle = defaultStyles.labelStyle;
}
edges.forEach(function (edge) {
for (const edge of edges) {
cnt++;
// Identify Link
@ -315,9 +314,8 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
}
edgeData.labelType = edge.labelType;
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
edgeData.label = await renderKatex(edge.text.replace(common.lineBreakRegex, '\n'), getConfig());
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
@ -330,7 +328,7 @@ export const addEdges = function (edges, g, diagObj) {
// Add the edge to the graph
g.setEdge(edge.start, edge.end, edgeData, cnt);
});
}
};
/**
@ -427,8 +425,8 @@ export const draw = async function (text, id, _version, diagObj) {
g.setParent(subG.nodes[j], subG.id);
}
}
addVertices(vert, g, id, root, doc, diagObj);
addEdges(edges, g, diagObj);
await addVertices(vert, g, id, root, doc, diagObj);
await addEdges(edges, g, diagObj);
// Add custom shapes
// flowChartShapes.addToRenderV2(addShape);

View File

@ -15,7 +15,7 @@ describe('when using mermaid and ', function () {
flowDb.clear();
flowDb.setGen('gen-2');
});
it('should handle edges with text', () => {
it('should handle edges with text', async () => {
parser.parse('graph TD;A-->|text ex|B;');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -29,7 +29,7 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges without text', async function () {
@ -45,10 +45,10 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle open-ended edges', () => {
it('should handle open-ended edges', async () => {
parser.parse('graph TD;A---B;');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -61,10 +61,10 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges with styles defined', () => {
it('should handle edges with styles defined', async () => {
parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -78,9 +78,9 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges with interpolation defined', () => {
it('should handle edges with interpolation defined', async () => {
parser.parse('graph TD;A---B; linkStyle 0 interpolate basis');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -94,9 +94,9 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should handle edges with text and styles defined', () => {
it('should handle edges with text and styles defined', async () => {
parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -111,10 +111,10 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should set fill to "none" by default when handling edges', () => {
it('should set fill to "none" by default when handling edges', async () => {
parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -128,10 +128,10 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
it('should not set fill to none if fill is set in linkStyle', () => {
it('should not set fill to none if fill is set in linkStyle', async () => {
parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;');
flowDb.getVertices();
const edges = flowDb.getEdges();
@ -144,7 +144,7 @@ describe('when using mermaid and ', function () {
},
};
flowRenderer.addEdges(edges, mockG, diag);
await flowRenderer.addEdges(edges, mockG, diag);
});
});
});

View File

@ -5,7 +5,7 @@ import { render as Render } from 'dagre-d3-es';
import { applyStyle } from 'dagre-d3-es/src/dagre-js/util.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { log } from '../../logger.js';
import common, { evaluate } from '../common/common.js';
import common, { evaluate, renderKatex } from '../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import flowChartShapes from './flowChartShapes.js';
@ -28,13 +28,13 @@ export const setConf = function (cnf) {
* @param _doc
* @param diagObj
*/
export const addVertices = function (vert, g, svgId, root, _doc, diagObj) {
export const addVertices = async function (vert, g, svgId, root, _doc, diagObj) {
const svg = !root ? select(`[id="${svgId}"]`) : root.select(`[id="${svgId}"]`);
const doc = !_doc ? document : _doc;
const keys = Object.keys(vert);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
for (const id of keys) {
const vertex = vert[id];
/**
@ -57,9 +57,12 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) {
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>`
label: await renderKatex(
vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
getConfig()
),
};
vertexNode = addHtmlLabel(svg, node).node();
@ -150,7 +153,7 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) {
style: styles.style,
id: diagObj.db.lookUpDomId(vertex.id),
});
});
}
};
/**
@ -160,7 +163,7 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) {
* @param {object} g The graph object
* @param diagObj
*/
export const addEdges = function (edges, g, diagObj) {
export const addEdges = async function (edges, g, diagObj) {
let cnt = 0;
let defaultStyle;
@ -172,7 +175,7 @@ export const addEdges = function (edges, g, diagObj) {
defaultLabelStyle = defaultStyles.labelStyle;
}
edges.forEach(function (edge) {
for (const edge of edges) {
cnt++;
// Identify Link
@ -239,9 +242,12 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.labelType = 'html';
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
edgeData.labelStyle
}">${edge.text.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
}">${await renderKatex(
edge.text.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
getConfig()
)}</span>`;
} else {
edgeData.labelType = 'text';
@ -261,7 +267,7 @@ export const addEdges = function (edges, g, diagObj) {
// Add the edge to the graph
g.setEdge(diagObj.db.lookUpDomId(edge.start), diagObj.db.lookUpDomId(edge.end), edgeData, cnt);
});
}
};
/**
@ -284,7 +290,7 @@ export const getClasses = function (text, diagObj) {
* @param _version
* @param diagObj
*/
export const draw = function (text, id, _version, diagObj) {
export const draw = async function (text, id, _version, diagObj) {
log.info('Drawing flowchart');
const { securityLevel, flowchart: conf } = getConfig();
let sandboxElement;
@ -350,8 +356,8 @@ export const draw = function (text, id, _version, diagObj) {
g.setParent(diagObj.db.lookUpDomId(subG.nodes[j]), diagObj.db.lookUpDomId(subG.id));
}
}
addVertices(vert, g, id, root, doc, diagObj);
addEdges(edges, g, diagObj);
await addVertices(vert, g, id, root, doc, diagObj);
await addEdges(edges, g, diagObj);
// Create the renderer
const render = new Render();

View File

@ -27,7 +27,7 @@ describe('the flowchart renderer', function () {
['cylinder', 'cylinder'],
['group', 'rect'],
].forEach(function ([type, expectedShape, expectedRadios = 0]) {
it(`should add the correct shaped node to the graph for vertex type ${type}`, function () {
it(`should add the correct shaped node to the graph for vertex type ${type}`, async function () {
const fakeDiag = {
db: {
lookUpDomId: () => {
@ -41,7 +41,7 @@ describe('the flowchart renderer', function () {
addedNodes.push([id, object]);
},
};
addVertices(
await addVertices(
{
v1: {
type,
@ -70,7 +70,7 @@ describe('the flowchart renderer', function () {
['Multi<br>Line', 'Multi<br/>Line', 'Multi<br />Line', 'Multi<br\t/>Line'].forEach(function (
labelText
) {
it('should handle multiline texts with different line breaks', function () {
it('should handle multiline texts with different line breaks', async function () {
const addedNodes = [];
const fakeDiag = {
db: {
@ -84,7 +84,7 @@ describe('the flowchart renderer', function () {
addedNodes.push([id, object]);
},
};
addVertices(
await addVertices(
{
v1: {
type: 'rect',
@ -121,7 +121,7 @@ describe('the flowchart renderer', function () {
'color:#ccc;text-align:center;',
],
].forEach(function ([style, expectedStyle, expectedLabelStyle]) {
it(`should add the styles to style and/or labelStyle for style ${style}`, function () {
it(`should add the styles to style and/or labelStyle for style ${style}`, async function () {
const addedNodes = [];
const fakeDiag = {
db: {
@ -135,7 +135,7 @@ describe('the flowchart renderer', function () {
addedNodes.push([id, object]);
},
};
addVertices(
await addVertices(
{
v1: {
type: 'rect',
@ -160,7 +160,7 @@ describe('the flowchart renderer', function () {
});
});
it(`should add default class to all nodes which do not have another class assigned`, function () {
it(`should add default class to all nodes which do not have another class assigned`, async function () {
const addedNodes = [];
const mockG = {
setNode: function (id, object) {
@ -174,7 +174,7 @@ describe('the flowchart renderer', function () {
},
},
};
addVertices(
await addVertices(
{
v1: {
type: 'rect',
@ -206,7 +206,7 @@ describe('the flowchart renderer', function () {
});
describe('when adding edges to a graph', function () {
it('should handle multiline texts and set centered label position', function () {
it('should handle multiline texts and set centered label position', async function () {
const addedEdges = [];
const fakeDiag = {
db: {
@ -220,7 +220,7 @@ describe('the flowchart renderer', function () {
addedEdges.push(data);
},
};
addEdges(
await addEdges(
[
{ text: 'Multi<br>Line' },
{ text: 'Multi<br/>Line' },
@ -251,7 +251,7 @@ describe('the flowchart renderer', function () {
'fill:red;',
],
].forEach(function ([style, expectedStyle, expectedLabelStyle]) {
it(`should add the styles to style and/or labelStyle for style ${style}`, function () {
it(`should add the styles to style and/or labelStyle for style ${style}`, async function () {
const addedEdges = [];
const fakeDiag = {
db: {
@ -265,7 +265,7 @@ describe('the flowchart renderer', function () {
addedEdges.push(data);
},
};
addEdges([{ style: style, text: 'styling' }], mockG, fakeDiag);
await addEdges([{ style: style, text: 'styling' }], mockG, fakeDiag);
expect(addedEdges).toHaveLength(1);
expect(addedEdges[0]).toHaveProperty('style', expectedStyle);

View File

@ -66,6 +66,12 @@ const getStyles = (options: FlowChartStyleOptions) =>
// text-anchor: start;
// }
.node .katex path {
fill: #000;
stroke: #000;
stroke-width: 1px;
}
.node .label {
text-align: center;
}

View File

@ -209,7 +209,7 @@ Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers
expect(diagram.db.showSequenceNumbers()).toBe(false);
});
it('should show sequence numbers when autonumber is enabled', async () => {
@ -221,7 +221,7 @@ Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers
expect(diagram.db.showSequenceNumbers()).toBe(true);
});
@ -1648,7 +1648,7 @@ participant Alice`;
// mermaidAPI.reinitialize({ sequence: { textPlacement: textPlacement } });
await mermaidAPI.parse(str);
// diagram.renderer.setConf(mermaidAPI.getConfig().sequence);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1679,7 +1679,7 @@ Note over Alice: Alice thinks
expect(mermaidAPI.getConfig().sequence.mirrorActors).toBeFalsy();
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1695,7 +1695,7 @@ participant Alice
Note left of Alice: Alice thinks`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
@ -1711,7 +1711,7 @@ participant Alice
Note right of Alice: Alice thinks`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1726,7 +1726,7 @@ sequenceDiagram
Alice->Bob: Hello Bob, how are you?`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1744,7 +1744,7 @@ end
Alice->Bob: Hello Bob, how are you?`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1759,7 +1759,7 @@ sequenceDiagram
Alice->Bob: Hello Bob, how are you?`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
const mermaid = mermaidAPI.getConfig();
@ -1779,7 +1779,7 @@ wrap
Alice->Bob: Hello Bob, how are you?`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const msgs = diagram.db.getMessages();
const { bounds, models } = diagram.renderer.bounds.getBounds();
@ -1800,7 +1800,7 @@ Note over Bob,Alice: Looks back
`;
// mermaidAPI.initialize({logLevel:0})
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1815,7 +1815,7 @@ Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1831,7 +1831,7 @@ Note right of Bob: Bob thinks
Bob->Alice: Fine!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1850,7 +1850,7 @@ Note left of Alice: Bob thinks
Bob->Alice: Fine!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
@ -1867,7 +1867,7 @@ Note left of Alice: Bob thinks
Bob->>Alice: Fine!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
const msgs = diagram.db.getMessages();
@ -1888,7 +1888,7 @@ Note left of Alice: Bob thinks
Bob->>Alice: Fine!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
const msgs = diagram.db.getMessages();
@ -1911,7 +1911,7 @@ Note left of Alice: Bob thinks
Bob->>Alice: Fine!`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
const msgs = diagram.db.getMessages();
@ -1933,7 +1933,7 @@ Note left of Alice: Bob thinks
Bob->>Alice: Fine!`;
// mermaidAPI.initialize({ logLevel: 0 });
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
const msgs = diagram.db.getMessages();
@ -1957,7 +1957,7 @@ loop Cheers
Bob->Alice: Fine!
end`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
@ -1975,7 +1975,7 @@ end`;
end
`;
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
@ -2022,7 +2022,7 @@ sequenceDiagram
participant Alice`;
diagram.renderer.bounds.init();
await mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);

View File

@ -1,8 +1,8 @@
// @ts-nocheck TODO: fix file
import { select } from 'd3';
import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import { log } from '../../logger.js';
import common from '../common/common.js';
import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import assignWithDepth from '../../assignWithDepth.js';
@ -237,7 +237,7 @@ interface NoteModel {
* @param elem - The diagram to draw to.
* @param noteModel - Note model options.
*/
const drawNote = function (elem: any, noteModel: NoteModel) {
const drawNote = async function (elem: any, noteModel: NoteModel) {
bounds.bumpVerticalPos(conf.boxMargin);
noteModel.height = conf.boxMargin;
noteModel.starty = bounds.getVerticalPos();
@ -263,7 +263,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
textObj.textMargin = conf.noteMargin;
textObj.valign = 'center';
const textElem = drawText(g, textObj);
const textElem = hasKatex(textObj.text) ? await drawKatex(g, textObj) : drawText(g, textObj);
const textHeight = Math.round(
textElem
@ -311,15 +311,20 @@ const actorFont = (cnf) => {
* @param msgModel - The model containing fields describing a message
* @returns `lineStartY` - The Y coordinate at which the message line starts
*/
function boundMessage(_diagram, msgModel): number {
async function boundMessage(_diagram, msgModel): Promise<number> {
bounds.bumpVerticalPos(10);
const { startx, stopx, message } = msgModel;
const lines = common.splitBreaks(message).length;
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
const lineHeight = textDims.height / lines;
msgModel.height += lineHeight;
const isKatexMsg = hasKatex(message);
const textDims = isKatexMsg
? await calculateMathMLDimensions(message, getConfig())
: utils.calculateTextDimensions(message, messageFont(conf));
bounds.bumpVerticalPos(lineHeight);
if (!isKatexMsg) {
const lineHeight = textDims.height / lines;
msgModel.height += lineHeight;
bounds.bumpVerticalPos(lineHeight);
}
let lineStartY;
let totalOffset = textDims.height - 10;
@ -360,7 +365,7 @@ function boundMessage(_diagram, msgModel): number {
* @param lineStartY - The Y coordinate at which the message line starts
* @param diagObj - The diagram object.
*/
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
const drawMessage = async function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
const textObj = svgDrawCommon.getTextObj();
@ -378,7 +383,9 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
textObj.textMargin = conf.wrapPadding;
textObj.tspan = false;
drawText(diagram, textObj);
hasKatex(textObj.text)
? await drawKatex(diagram, textObj, { startx, stopx, starty: lineStartY })
: drawText(diagram, textObj);
const textWidth = textDims.width;
@ -478,7 +485,7 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
}
};
const addActorRenderingData = function (
const addActorRenderingData = async function (
diagram,
actors,
createdActors,
@ -548,12 +555,12 @@ const addActorRenderingData = function (
bounds.bumpVerticalPos(maxHeight);
};
export const drawActors = function (diagram, actors, actorKeys, isFooter) {
export const drawActors = async function (diagram, actors, actorKeys, isFooter) {
if (!isFooter) {
for (const actorKey of actorKeys) {
const actor = actors[actorKey];
// Draw the box with the attached line
svgDraw.drawActor(diagram, actor, conf, false);
await svgDraw.drawActor(diagram, actor, conf, false);
}
} else {
let maxHeight = 0;
@ -563,7 +570,7 @@ export const drawActors = function (diagram, actors, actorKeys, isFooter) {
if (!actor.stopy) {
actor.stopy = bounds.getVerticalPos();
}
const height = svgDraw.drawActor(diagram, actor, conf, true);
const height = await svgDraw.drawActor(diagram, actor, conf, true);
maxHeight = common.getMax(maxHeight, height);
}
bounds.bumpVerticalPos(maxHeight + conf.boxMargin);
@ -746,7 +753,7 @@ function adjustCreatedDestroyedData(
* @param _version - Mermaid version from package.json
* @param diagObj - A standard diagram containing the db and the text and type etc of the diagram
*/
export const draw = function (_text: string, id: string, _version: string, diagObj: Diagram) {
export const draw = async function (_text: string, id: string, _version: string, diagObj: Diagram) {
const { securityLevel, sequence } = getConfig();
conf = sequence;
// Handle root and Document for when rendering in sandbox mode
@ -776,8 +783,8 @@ export const draw = function (_text: string, id: string, _version: string, diagO
const title = diagObj.db.getDiagramTitle();
const hasBoxes = diagObj.db.hasAtLeastOneBox();
const hasBoxTitles = diagObj.db.hasAtLeastOneBoxWithTitle();
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj);
conf.height = calculateActorMargins(actors, maxMessageWidthPerActor, boxes);
const maxMessageWidthPerActor = await getMaxMessageWidthPerActor(actors, messages, diagObj);
conf.height = await calculateActorMargins(actors, maxMessageWidthPerActor, boxes);
svgDraw.insertComputerIcon(diagram);
svgDraw.insertDatabaseIcon(diagram);
@ -799,8 +806,8 @@ export const draw = function (_text: string, id: string, _version: string, diagO
actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey));
}
addActorRenderingData(diagram, actors, createdActors, actorKeys, 0, messages, false);
const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj);
await addActorRenderingData(diagram, actors, createdActors, actorKeys, 0, messages, false);
const loopWidths = await calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj);
// The arrow head definition is attached to the svg once
svgDraw.insertArrowHead(diagram);
@ -834,14 +841,15 @@ export const draw = function (_text: string, id: string, _version: string, diagO
let sequenceIndexStep = 1;
const messagesToDraw = [];
const backgrounds = [];
messages.forEach(function (msg, index) {
let index = 0;
for (const msg of messages) {
let loopModel, noteModel, msgModel;
switch (msg.type) {
case diagObj.db.LINETYPE.NOTE:
bounds.resetVerticalPos();
noteModel = msg.noteModel;
drawNote(diagram, noteModel);
await drawNote(diagram, noteModel);
break;
case diagObj.db.LINETYPE.ACTIVE_START:
bounds.newActivation(msg, diagram, actors);
@ -860,7 +868,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.LOOP_END:
loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'loop', conf);
await svgDraw.drawLoop(diagram, loopModel, 'loop', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break;
@ -886,7 +894,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.OPT_END:
loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'opt', conf);
await svgDraw.drawLoop(diagram, loopModel, 'opt', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break;
@ -910,7 +918,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.ALT_END:
loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'alt', conf);
await svgDraw.drawLoop(diagram, loopModel, 'alt', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break;
@ -936,7 +944,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.PAR_END:
loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'par', conf);
await svgDraw.drawLoop(diagram, loopModel, 'par', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break;
@ -969,7 +977,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.CRITICAL_END:
loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'critical', conf);
await svgDraw.drawLoop(diagram, loopModel, 'critical', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break;
@ -984,7 +992,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.BREAK_END:
loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'break', conf);
await svgDraw.drawLoop(diagram, loopModel, 'break', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel);
break;
@ -994,7 +1002,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
msgModel.starty = bounds.getVerticalPos();
msgModel.sequenceIndex = sequenceIndex;
msgModel.sequenceVisible = diagObj.db.showSequenceNumbers();
const lineStartY = boundMessage(diagram, msgModel);
const lineStartY = await boundMessage(diagram, msgModel);
adjustCreatedDestroyedData(
msg,
msgModel,
@ -1026,20 +1034,23 @@ export const draw = function (_text: string, id: string, _version: string, diagO
) {
sequenceIndex = sequenceIndex + sequenceIndexStep;
}
});
index++;
}
log.debug('createdActors', createdActors);
log.debug('destroyedActors', destroyedActors);
await drawActors(diagram, actors, actorKeys, false);
drawActors(diagram, actors, actorKeys, false);
messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj));
for (const e of messagesToDraw) {
await drawMessage(diagram, e.messageModel, e.lineStartY, diagObj);
}
if (conf.mirrorActors) {
drawActors(diagram, actors, actorKeys, true);
await drawActors(diagram, actors, actorKeys, true);
}
backgrounds.forEach((e) => svgDraw.drawBackgroundRect(diagram, e));
fixLifeLineHeights(diagram, actors, actorKeys, conf);
bounds.models.boxes.forEach(function (box) {
for (const box of bounds.models.boxes) {
box.height = bounds.getVerticalPos() - box.y;
bounds.insert(box.x, box.y, box.x + box.width, box.height);
box.startx = box.x;
@ -1047,8 +1058,8 @@ export const draw = function (_text: string, id: string, _version: string, diagO
box.stopx = box.startx + box.width;
box.stopy = box.starty + box.height;
box.stroke = 'rgb(0,0,0, 0.5)';
svgDraw.drawBox(diagram, box, conf);
});
await svgDraw.drawBox(diagram, box, conf);
}
if (hasBoxes) {
bounds.bumpVerticalPos(conf.boxMargin);
@ -1114,25 +1125,25 @@ export const draw = function (_text: string, id: string, _version: string, diagO
* @param diagObj - The diagram object.
* @returns The max message width of each actor.
*/
function getMaxMessageWidthPerActor(
async function getMaxMessageWidthPerActor(
actors: { [id: string]: any },
messages: any[],
diagObj: Diagram
): { [id: string]: number } {
): Promise<{ [id: string]: number }> {
const maxMessageWidthPerActor = {};
messages.forEach(function (msg) {
for (const msg of messages) {
if (actors[msg.to] && actors[msg.from]) {
const actor = actors[msg.to];
// If this is the first actor, and the message is left of it, no need to calculate the margin
if (msg.placement === diagObj.db.PLACEMENT.LEFTOF && !actor.prevActor) {
return;
continue;
}
// If this is the last actor, and the message is right of it, no need to calculate the margin
if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF && !actor.nextActor) {
return;
continue;
}
const isNote = msg.placement !== undefined;
@ -1142,7 +1153,9 @@ function getMaxMessageWidthPerActor(
const wrappedMessage = msg.wrap
? utils.wrapLabel(msg.message, conf.width - 2 * conf.wrapPadding, textFont)
: msg.message;
const messageDimensions = utils.calculateTextDimensions(wrappedMessage, textFont);
const messageDimensions = hasKatex(wrappedMessage)
? await calculateMathMLDimensions(msg.message, getConfig())
: utils.calculateTextDimensions(wrappedMessage, textFont);
const messageWidth = messageDimensions.width + 2 * conf.wrapPadding;
/*
@ -1207,7 +1220,7 @@ function getMaxMessageWidthPerActor(
}
}
}
});
}
log.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor);
return maxMessageWidthPerActor;
@ -1238,13 +1251,13 @@ const getRequiredPopupWidth = function (actor) {
* @param actorToMessageWidth - A map of actor key max message width it holds
* @param boxes - The boxes around the actors if any
*/
function calculateActorMargins(
async function calculateActorMargins(
actors: { [id: string]: any },
actorToMessageWidth: ReturnType<typeof getMaxMessageWidthPerActor>,
actorToMessageWidth: Awaited<ReturnType<typeof getMaxMessageWidthPerActor>>,
boxes
) {
let maxHeight = 0;
Object.keys(actors).forEach((prop) => {
for (const prop of Object.keys(actors)) {
const actor = actors[prop];
if (actor.wrap) {
actor.description = utils.wrapLabel(
@ -1253,14 +1266,17 @@ function calculateActorMargins(
actorFont(conf)
);
}
const actDims = utils.calculateTextDimensions(actor.description, actorFont(conf));
const actDims = hasKatex(actor.description)
? await calculateMathMLDimensions(actor.description, getConfig())
: utils.calculateTextDimensions(actor.description, actorFont(conf));
actor.width = actor.wrap
? conf.width
: common.getMax(conf.width, actDims.width + 2 * conf.wrapPadding);
actor.height = actor.wrap ? common.getMax(actDims.height, conf.height) : conf.height;
maxHeight = common.getMax(maxHeight, actor.height);
});
}
for (const actorKey in actorToMessageWidth) {
const actor = actors[actorKey];
@ -1311,15 +1327,17 @@ function calculateActorMargins(
return common.getMax(maxHeight, conf.height);
}
const buildNoteModel = function (msg, actors, diagObj) {
const buildNoteModel = async function (msg, actors, diagObj) {
const startx = actors[msg.from].x;
const stopx = actors[msg.to].x;
const shouldWrap = msg.wrap && msg.message;
let textDimensions = utils.calculateTextDimensions(
shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message,
noteFont(conf)
);
let textDimensions: { width: number; height: number; lineHeight?: number } = hasKatex(msg.message)
? await calculateMathMLDimensions(msg.message, getConfig())
: utils.calculateTextDimensions(
shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message,
noteFont(conf)
);
const noteModel = {
width: shouldWrap
? conf.width
@ -1477,12 +1495,12 @@ const buildMessageModel = function (msg, actors, diagObj) {
};
};
const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagObj) {
const calculateLoopBounds = async function (messages, actors, _maxWidthPerActor, diagObj) {
const loops = {};
const stack = [];
let current, noteModel, msgModel;
messages.forEach(function (msg) {
for (const msg of messages) {
msg.id = utils.random({ length: 10 });
switch (msg.type) {
case diagObj.db.LINETYPE.LOOP_START:
@ -1545,7 +1563,7 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
}
const isNote = msg.placement !== undefined;
if (isNote) {
noteModel = buildNoteModel(msg, actors, diagObj);
noteModel = await buildNoteModel(msg, actors, diagObj);
msg.noteModel = noteModel;
stack.forEach((stk) => {
current = stk;
@ -1584,7 +1602,7 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
});
}
}
});
}
bounds.activations = [];
log.debug('Loop type widths:', loops);
return loops;

View File

@ -1,8 +1,9 @@
import common from '../common/common.js';
import common, { calculateMathMLDimensions, hasKatex, renderKatex } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { addFunction } from '../../interactionDb.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
import * as configApi from '../../config.js';
export const ACTOR_TYPE_WIDTH = 18 * 2;
const TOP_ACTOR_CLASS = 'actor-top';
@ -83,6 +84,47 @@ const popupMenuToggle = function (popid) {
);
};
export const drawKatex = async function (elem, textData, msgModel = null) {
let textElem = elem.append('foreignObject');
const lines = await renderKatex(textData.text, configApi.getConfig());
const divElem = textElem
.append('xhtml:div')
.attr('style', 'width: fit-content;')
.attr('xmlns', 'http://www.w3.org/1999/xhtml')
.html(lines);
const dim = divElem.node().getBoundingClientRect();
textElem.attr('height', Math.round(dim.height)).attr('width', Math.round(dim.width));
if (textData.class === 'noteText') {
const rectElem = elem.node().firstChild;
rectElem.setAttribute('height', dim.height + 2 * textData.textMargin);
const rectDim = rectElem.getBBox();
textElem
.attr('x', Math.round(rectDim.x + rectDim.width / 2 - dim.width / 2))
.attr('y', Math.round(rectDim.y + rectDim.height / 2 - dim.height / 2));
} else if (msgModel) {
let { startx, stopx, starty } = msgModel;
if (startx > stopx) {
const temp = startx;
startx = stopx;
stopx = temp;
}
textElem.attr('x', Math.round(startx + Math.abs(startx - stopx) / 2 - dim.width / 2));
if (textData.class === 'loopText') {
textElem.attr('y', Math.round(starty));
} else {
textElem.attr('y', Math.round(starty - dim.height));
}
}
return [textElem];
};
export const drawText = function (elem, textData) {
let prevTextHeight = 0;
let textHeight = 0;
@ -282,7 +324,7 @@ export const fixLifeLineHeights = (diagram, actors, actorKeys, conf) => {
* @param {any} conf - DrawText implementation discriminator object
* @param {boolean} isFooter - If the actor is the footer one
*/
const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
const drawActorTypeParticipant = async function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 5;
@ -345,7 +387,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
}
}
_drawTextCandidateFunc(conf)(
await _drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x,
@ -366,7 +408,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
return height;
};
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const drawActorTypeActor = async function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 80;
@ -446,7 +488,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const bounds = actElem.node().getBBox();
actor.height = bounds.height;
_drawTextCandidateFunc(conf)(
await _drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
@ -460,21 +502,21 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
return actor.height;
};
export const drawActor = function (elem, actor, conf, isFooter) {
export const drawActor = async function (elem, actor, conf, isFooter) {
switch (actor.type) {
case 'actor':
return drawActorTypeActor(elem, actor, conf, isFooter);
return await drawActorTypeActor(elem, actor, conf, isFooter);
case 'participant':
return drawActorTypeParticipant(elem, actor, conf, isFooter);
return await drawActorTypeParticipant(elem, actor, conf, isFooter);
}
};
export const drawBox = function (elem, box, conf) {
export const drawBox = async function (elem, box, conf) {
const boxplustextGroup = elem.append('g');
const g = boxplustextGroup;
drawBackgroundRect(g, box);
if (box.name) {
_drawTextCandidateFunc(conf)(
await _drawTextCandidateFunc(conf)(
box.name,
g,
box.x,
@ -521,7 +563,7 @@ export const drawActivation = function (elem, bounds, verticalPos, conf, actorAc
* @param {any} conf - Diagram configuration
* @returns {any}
*/
export const drawLoop = function (elem, loopModel, labelText, conf) {
export const drawLoop = async function (elem, loopModel, labelText, conf) {
const {
boxMargin,
boxTextMargin,
@ -583,10 +625,10 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
txt.fontWeight = fontWeight;
txt.wrap = true;
let textElem = drawText(g, txt);
let textElem = hasKatex(txt.text) ? await drawKatex(g, txt, loopModel) : drawText(g, txt);
if (loopModel.sectionTitles !== undefined) {
loopModel.sectionTitles.forEach(function (item, idx) {
for (const [idx, item] of Object.entries(loopModel.sectionTitles)) {
if (item.message) {
txt.text = item.message;
txt.x = loopModel.startx + (loopModel.stopx - loopModel.startx) / 2;
@ -599,7 +641,13 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
txt.fontSize = fontSize;
txt.fontWeight = fontWeight;
txt.wrap = loopModel.wrap;
textElem = drawText(g, txt);
if (hasKatex(txt.text)) {
loopModel.starty = loopModel.sections[idx].y;
await drawKatex(g, txt, loopModel);
} else {
drawText(g, txt);
}
let sectionHeight = Math.round(
textElem
.map((te) => (te._groups || te)[0][0].getBBox().height)
@ -607,7 +655,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
);
loopModel.sections[idx].height += sectionHeight - (boxMargin + boxTextMargin);
}
});
}
}
loopModel.height = Math.round(loopModel.stopy - loopModel.starty);
@ -884,6 +932,41 @@ const _drawTextCandidateFunc = (function () {
_setTextAttrs(text, textAttrs);
}
/**
*
* @param content
* @param g
* @param x
* @param y
* @param width
* @param height
* @param textAttrs
* @param conf
*/
async function byKatex(content, g, x, y, width, height, textAttrs, conf) {
// TODO duplicate render calls, optimize
const dim = await calculateMathMLDimensions(content, configApi.getConfig());
const s = g.append('switch');
const f = s
.append('foreignObject')
.attr('x', x + width / 2 - dim.width / 2)
.attr('y', y + height / 2 - dim.height / 2)
.attr('width', dim.width)
.attr('height', dim.height);
const text = f.append('xhtml:div').style('height', '100%').style('width', '100%');
text
.append('div')
.style('text-align', 'center')
.style('vertical-align', 'middle')
.html(await renderKatex(content, configApi.getConfig()));
byTspan(content, s, x, y, width, height, textAttrs, conf);
_setTextAttrs(text, textAttrs);
}
/**
* @param {any} toText
* @param {any} fromTextAttrsDict
@ -896,7 +979,10 @@ const _drawTextCandidateFunc = (function () {
}
}
return function (conf) {
return function (conf, hasKatex = false) {
if (hasKatex) {
return byKatex;
}
return conf.textPlacement === 'fo' ? byFo : conf.textPlacement === 'old' ? byText : byTspan;
};
})();

View File

@ -172,6 +172,7 @@ function sidebarConfig() {
{ text: 'Mermaid Configuration Options', link: '/config/schema-docs/config' },
{ text: 'Directives', link: '/config/directives' },
{ text: 'Theming', link: '/config/theming' },
{ text: 'Math', link: '/config/math' },
{ text: 'Accessibility', link: '/config/accessibility' },
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' },
{ text: 'FAQ', link: '/config/faq' },

View File

@ -0,0 +1,62 @@
# Math Configuration (v<MERMAID_RELEASE_VERSION>+)
Mermaid supports rendering mathematical expressions through the [KaTeX](https://katex.org/) typesetter.
## Usage
To render math within a diagram, surround the mathematical expression with the `$$` delimiter.
Note that at the moment, the only supported diagrams are below:
### Flowcharts
```mermaid
graph LR
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
### Sequence
```mermaid
sequenceDiagram
autonumber
participant 1 as $$\alpha$$
participant 2 as $$\beta$$
1->>2: Solve: $$\sqrt{2+2}$$
2-->>1: Answer: $$2$$
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
```
## Legacy Support
By default, MathML is used for rendering mathematical expressions. If you have users on [unsupported browsers](https://caniuse.com/?search=mathml), `legacyMathML` can be set in the config to fall back to CSS rendering. Note that **you must provide KaTeX's stylesheets on your own** as they do not come bundled with Mermaid.
Example with legacy mode enabled (the latest version of KaTeX's stylesheet can be found on their [docs](https://katex.org/docs/browser.html)):
```html
<!DOCTYPE html>
<!-- KaTeX requires the use of the HTML5 doctype. Without it, KaTeX may not render properly -->
<html lang="en">
<head>
<!-- Please ensure the stylesheet's version matches with the KaTeX version in your package-lock -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@{version_number}/dist/katex.min.css"
integrity="sha384-{hash}"
crossorigin="anonymous"
/>
</head>
<body>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
legacyMathML: true,
});
</script>
</body>
</html>
```

View File

@ -168,6 +168,14 @@ properties:
items:
type: string
uniqueItems: false # Should be enabled, but it may be a breaking change from the old config
legacyMathML:
description: |
This option specifies if Mermaid can expect the dependent to include KaTeX stylesheets for browsers
without their own MathML implementation. If this option is disabled and MathML is not supported, the math
equations are replaced with a warning. If this option is enabled and MathML is not supported, Mermaid will
fall back to legacy rendering for KaTeX.
type: boolean
default: false
deterministicIds:
description: |
This option controls if the generated ids of nodes in the SVG are

View File

@ -229,6 +229,9 @@ importers:
elkjs:
specifier: ^0.9.0
version: 0.9.1
katex:
specifier: ^0.16.9
version: 0.16.9
khroma:
specifier: ^2.0.0
version: 2.0.0
@ -252,7 +255,7 @@ importers:
version: 9.0.0
web-worker:
specifier: ^1.2.0
version: 1.2.0
version: 1.3.0
devDependencies:
'@adobe/jsonschema2md':
specifier: ^7.1.4
@ -278,6 +281,9 @@ importers:
'@types/jsdom':
specifier: ^21.1.1
version: 21.1.1
'@types/katex':
specifier: ^0.16.7
version: 0.16.7
'@types/lodash-es':
specifier: ^4.17.7
version: 4.17.7
@ -4897,6 +4903,10 @@ packages:
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
dev: true
/@types/katex@0.16.7:
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
dev: true
/@types/keyv@3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies:
@ -7436,6 +7446,11 @@ packages:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
/commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
dev: false
/comment-json@4.2.3:
resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==}
engines: {node: '>= 6'}
@ -11843,6 +11858,13 @@ packages:
engines: {node: '>=12.20'}
dev: true
/katex@0.16.9:
resolution: {integrity: sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==}
hasBin: true
dependencies:
commander: 8.3.0
dev: false
/keyv@4.5.3:
resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==}
dependencies:
@ -16522,8 +16544,8 @@ packages:
engines: {node: '>= 8'}
dev: true
/web-worker@1.2.0:
resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==}
/web-worker@1.3.0:
resolution: {integrity: sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==}
dev: false
/webdriver@7.31.1(typescript@5.1.6):