mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge pull request #6136 from mermaid-js/5813-edge-bugfix
Adding animations to flowchart edges also fixing bug with invisible edges
This commit is contained in:
commit
bc2cc61240
5
.changeset/many-brooms-promise.md
Normal file
5
.changeset/many-brooms-promise.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
---
|
||||
|
||||
Adding support for animation of flowchart edges
|
@ -84,29 +84,66 @@
|
||||
/* tspan {
|
||||
font-size: 6px !important;
|
||||
} */
|
||||
/* .flowchart-link {
|
||||
stroke-dasharray: 4, 4 !important;
|
||||
animation: flow 1s linear infinite;
|
||||
animation: dashdraw 4.93282s linear infinite;
|
||||
stroke-width: 2px !important;
|
||||
} */
|
||||
|
||||
@keyframes dashdraw {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: 4.932820s linear infinite;*/
|
||||
/* stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: dashdraw 4.932820s linear infinite;*/
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart LR
|
||||
subgraph S2
|
||||
subgraph s1["APA"]
|
||||
D{"Use the editor"}
|
||||
end
|
||||
|
||||
|
||||
D -- Mermaid js --> I{"fa:fa-code Text"}
|
||||
D --> I
|
||||
D --> I
|
||||
|
||||
end
|
||||
A --> B
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart LR
|
||||
A e1@==> B
|
||||
e1@{ animate: true}
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart LR
|
||||
A e1@--> B
|
||||
classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
|
||||
class e1 animate
|
||||
</pre>
|
||||
<h2>infinite</h2>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart LR
|
||||
A e1@--> B
|
||||
classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
|
||||
class e1 animate
|
||||
</pre>
|
||||
<h2>Mermaid - edge-animation-slow</h2>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart LR
|
||||
A e1@--> B
|
||||
e1@{ animation: fast}
|
||||
</pre>
|
||||
<h2>Mermaid - edge-animation-fast</h2>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart LR
|
||||
A e1@--> B
|
||||
classDef animate stroke-dasharray: 1000,stroke-dashoffset: 1000,animation: dash 10s linear;
|
||||
class e1 edge-animation-fast
|
||||
</pre>
|
||||
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
|
||||
info </pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
@ -131,7 +168,7 @@ config:
|
||||
end
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
@ -144,7 +181,7 @@ config:
|
||||
D-->I
|
||||
D-->I
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
@ -183,7 +220,7 @@ flowchart LR
|
||||
n8@{ shape: rect}
|
||||
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
@ -199,7 +236,7 @@ flowchart LR
|
||||
|
||||
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
@ -208,7 +245,7 @@ flowchart LR
|
||||
A{A} --> B & C
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
@ -220,7 +257,7 @@ flowchart LR
|
||||
end
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/types.ts:144](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L144)
|
||||
[packages/mermaid/src/rendering-util/types.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L147)
|
||||
|
||||
---
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/types.ts:143](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L143)
|
||||
[packages/mermaid/src/rendering-util/types.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L146)
|
||||
|
||||
---
|
||||
|
||||
@ -40,4 +40,4 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/types.ts:142](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L142)
|
||||
[packages/mermaid/src/rendering-util/types.ts:145](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L145)
|
||||
|
@ -19,4 +19,4 @@ The `parseError` function will not be called.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L59)
|
||||
[packages/mermaid/src/types.ts:64](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L64)
|
||||
|
@ -18,7 +18,7 @@ The config passed as YAML frontmatter or directives
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L70)
|
||||
[packages/mermaid/src/types.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L75)
|
||||
|
||||
---
|
||||
|
||||
@ -30,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:66](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L66)
|
||||
[packages/mermaid/src/types.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L71)
|
||||
|
@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
|
||||
[packages/mermaid/src/types.ts:103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L103)
|
||||
|
||||
---
|
||||
|
||||
@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
|
||||
[packages/mermaid/src/types.ts:93](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L93)
|
||||
|
||||
---
|
||||
|
||||
@ -63,4 +63,4 @@ The svg code for the rendered graph.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
|
||||
[packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89)
|
||||
|
@ -1183,6 +1183,91 @@ flowchart TB
|
||||
B --> D
|
||||
```
|
||||
|
||||
### Attaching an ID to Edges
|
||||
|
||||
Mermaid now supports assigning IDs to edges, similar to how IDs and metadata can be attached to nodes. This feature lays the groundwork for more advanced styling, classes, and animation capabilities on edges.
|
||||
|
||||
**Syntax:**
|
||||
|
||||
To give an edge an ID, prepend the edge syntax with the ID followed by an `@` character. For example:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
```
|
||||
|
||||
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
|
||||
|
||||
### Turning an Animation On
|
||||
|
||||
Once you have assigned an ID to an edge, you can turn on animations for that edge by defining the edge’s properties:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
A e1@==> B
|
||||
e1@{ animate: true }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@==> B
|
||||
e1@{ animate: true }
|
||||
```
|
||||
|
||||
This tells Mermaid that the edge `e1` should be animated.
|
||||
|
||||
### Selecting Type of Animation
|
||||
|
||||
In the initial version, two animation speeds are supported: `fast` and `slow`. Selecting a specific animation type is a shorthand for enabling animation and setting the animation speed in one go.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
e1@{ animation: fast }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
e1@{ animation: fast }
|
||||
```
|
||||
|
||||
This is equivalent to `{ animate: true, animation: fast }`.
|
||||
|
||||
### Using classDef Statements for Animations
|
||||
|
||||
You can also animate edges by assigning a class to them and then defining animation properties in a `classDef` statement. For example:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
|
||||
class e1 animate
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
|
||||
class e1 animate
|
||||
```
|
||||
|
||||
In this snippet:
|
||||
|
||||
- `e1@-->` creates an edge with ID `e1`.
|
||||
- `classDef animate` defines a class named `animate` with styling and animation properties.
|
||||
- `class e1 animate` applies the `animate` class to the edge `e1`.
|
||||
|
||||
**Note on Escaping Commas:**
|
||||
When setting the `stroke-dasharray` property, remember to escape commas as `\,` since commas are used as delimiters in Mermaid’s style definitions.
|
||||
|
||||
## New arrow types
|
||||
|
||||
There are new types of arrows supported:
|
||||
|
@ -24,7 +24,7 @@ import type {
|
||||
FlowLink,
|
||||
FlowVertexTypeParam,
|
||||
} from './types.js';
|
||||
import type { NodeMetaData } from '../../types.js';
|
||||
import type { NodeMetaData, EdgeMetaData } from '../../types.js';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'flowchart-';
|
||||
let vertexCounter = 0;
|
||||
@ -71,12 +71,38 @@ export const addVertex = function (
|
||||
classes: string[],
|
||||
dir: string,
|
||||
props = {},
|
||||
shapeData: any
|
||||
metadata: any
|
||||
) {
|
||||
// console.log('addVertex', id, shapeData);
|
||||
if (!id || id.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
// Extract the metadata from the shapeData, the syntax for adding metadata for nodes and edges is the same
|
||||
// so at this point we don't know if it's a node or an edge, but we can still extract the metadata
|
||||
let doc;
|
||||
if (metadata !== undefined) {
|
||||
let yamlData;
|
||||
// detect if shapeData contains a newline character
|
||||
if (!metadata.includes('\n')) {
|
||||
yamlData = '{\n' + metadata + '\n}';
|
||||
} else {
|
||||
yamlData = metadata + '\n';
|
||||
}
|
||||
doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
|
||||
}
|
||||
|
||||
// Check if this is an edge
|
||||
const edge = edges.find((e) => e.id === id);
|
||||
if (edge) {
|
||||
const edgeDoc = doc as EdgeMetaData;
|
||||
if (edgeDoc?.animate) {
|
||||
edge.animate = edgeDoc.animate;
|
||||
}
|
||||
if (edgeDoc?.animation) {
|
||||
edge.animation = edgeDoc.animation;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let txt;
|
||||
|
||||
let vertex = vertices.get(id);
|
||||
@ -128,19 +154,7 @@ export const addVertex = function (
|
||||
Object.assign(vertex.props, props);
|
||||
}
|
||||
|
||||
if (shapeData !== undefined) {
|
||||
let yamlData;
|
||||
// detect if shapeData contains a newline character
|
||||
// console.log('shapeData', shapeData);
|
||||
if (!shapeData.includes('\n')) {
|
||||
// console.log('yamlData shapeData has no new lines', shapeData);
|
||||
yamlData = '{\n' + shapeData + '\n}';
|
||||
} else {
|
||||
// console.log('yamlData shapeData has new lines', shapeData);
|
||||
yamlData = shapeData + '\n';
|
||||
}
|
||||
// console.log('yamlData', yamlData);
|
||||
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
|
||||
if (doc !== undefined) {
|
||||
if (doc.shape) {
|
||||
if (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_')) {
|
||||
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
|
||||
@ -187,11 +201,18 @@ export const addVertex = function (
|
||||
* Function called by parser when a link/edge definition has been found
|
||||
*
|
||||
*/
|
||||
export const addSingleLink = function (_start: string, _end: string, type: any) {
|
||||
export const addSingleLink = function (_start: string, _end: string, type: any, id?: string) {
|
||||
const start = _start;
|
||||
const end = _end;
|
||||
|
||||
const edge: FlowEdge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
|
||||
const edge: FlowEdge = {
|
||||
start: start,
|
||||
end: end,
|
||||
type: undefined,
|
||||
text: '',
|
||||
labelType: 'text',
|
||||
classes: [],
|
||||
};
|
||||
log.info('abc78 Got edge...', edge);
|
||||
const linkTextObj = type.text;
|
||||
|
||||
@ -210,6 +231,9 @@ export const addSingleLink = function (_start: string, _end: string, type: any)
|
||||
edge.stroke = type.stroke;
|
||||
edge.length = type.length > 10 ? 10 : type.length;
|
||||
}
|
||||
if (id) {
|
||||
edge.id = id;
|
||||
}
|
||||
|
||||
if (edges.length < (config.maxEdges ?? 500)) {
|
||||
log.info('Pushing edge...');
|
||||
@ -225,11 +249,27 @@ You have to call mermaid.initialize.`
|
||||
}
|
||||
};
|
||||
|
||||
export const addLink = function (_start: string[], _end: string[], type: unknown) {
|
||||
log.info('addLink', _start, _end, type);
|
||||
interface LinkData {
|
||||
id: string;
|
||||
}
|
||||
|
||||
function isLinkData(value: unknown): value is LinkData {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === 'object' &&
|
||||
'id' in value &&
|
||||
typeof (value as LinkData).id === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export const addLink = function (_start: string[], _end: string[], linkData: unknown) {
|
||||
const id = isLinkData(linkData) ? linkData.id.replace('@', '') : undefined;
|
||||
|
||||
log.info('addLink', _start, _end, id);
|
||||
|
||||
for (const start of _start) {
|
||||
for (const end of _end) {
|
||||
addSingleLink(start, end, type);
|
||||
addSingleLink(start, end, linkData, id);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -267,9 +307,6 @@ export const updateLink = function (positions: ('default' | number)[], style: st
|
||||
if (pos === 'default') {
|
||||
edges.defaultStyle = style;
|
||||
} else {
|
||||
// if (utils.isSubstringInArray('fill', style) === -1) {
|
||||
// style.push('fill:none');
|
||||
// }
|
||||
edges[pos].style = style;
|
||||
// if edges[pos].style does have fill not set, set it to none
|
||||
if (
|
||||
@ -282,7 +319,13 @@ export const updateLink = function (positions: ('default' | number)[], style: st
|
||||
});
|
||||
};
|
||||
|
||||
export const addClass = function (ids: string, style: string[]) {
|
||||
export const addClass = function (ids: string, _style: string[]) {
|
||||
const style = _style
|
||||
.join()
|
||||
.replace(/\\,/g, '§§§')
|
||||
.replace(/,/g, ';')
|
||||
.replace(/§§§/g, ',')
|
||||
.split(';');
|
||||
ids.split(',').forEach(function (id) {
|
||||
let classNode = classes.get(id);
|
||||
if (classNode === undefined) {
|
||||
@ -337,6 +380,10 @@ export const setClass = function (ids: string, className: string) {
|
||||
if (vertex) {
|
||||
vertex.classes.push(className);
|
||||
}
|
||||
const edge = edges.find((e) => e.id === id);
|
||||
if (edge) {
|
||||
edge.classes.push(className);
|
||||
}
|
||||
const subGraph = subGraphLookup.get(id);
|
||||
if (subGraph) {
|
||||
subGraph.classes.push(className);
|
||||
@ -997,7 +1044,7 @@ export const getData = () => {
|
||||
styles.push(...rawEdge.style);
|
||||
}
|
||||
const edge: Edge = {
|
||||
id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }),
|
||||
id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }, rawEdge.id),
|
||||
start: rawEdge.start,
|
||||
end: rawEdge.end,
|
||||
type: rawEdge.type ?? 'normal',
|
||||
@ -1009,14 +1056,20 @@ export const getData = () => {
|
||||
rawEdge?.stroke === 'invisible'
|
||||
? ''
|
||||
: 'edge-thickness-normal edge-pattern-solid flowchart-link',
|
||||
arrowTypeStart: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeStart,
|
||||
arrowTypeEnd: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeEnd,
|
||||
arrowTypeStart:
|
||||
rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeStart,
|
||||
arrowTypeEnd:
|
||||
rawEdge?.stroke === 'invisible' || rawEdge?.type === 'arrow_open' ? 'none' : arrowTypeEnd,
|
||||
arrowheadStyle: 'fill: #333',
|
||||
cssCompiledStyles: getCompiledStyles(rawEdge.classes),
|
||||
labelStyle: styles,
|
||||
style: styles,
|
||||
pattern: rawEdge.stroke,
|
||||
look: config.look,
|
||||
animate: rawEdge.animate,
|
||||
animation: rawEdge.animation,
|
||||
};
|
||||
|
||||
edges.push(edge);
|
||||
});
|
||||
|
||||
|
@ -39,6 +39,27 @@ const doubleEndedEdges = [
|
||||
{ edgeStart: '<==', edgeEnd: '==>', stroke: 'thick', type: 'double_arrow_point' },
|
||||
{ edgeStart: '<-.', edgeEnd: '.->', stroke: 'dotted', type: 'double_arrow_point' },
|
||||
];
|
||||
const regularEdges = [
|
||||
{ edgeStart: '--', edgeEnd: '--x', stroke: 'normal', type: 'arrow_cross' },
|
||||
{ edgeStart: '==', edgeEnd: '==x', stroke: 'thick', type: 'arrow_cross' },
|
||||
{ edgeStart: '-.', edgeEnd: '.-x', stroke: 'dotted', type: 'arrow_cross' },
|
||||
{ edgeStart: '--', edgeEnd: '--o', stroke: 'normal', type: 'arrow_circle' },
|
||||
{ edgeStart: '==', edgeEnd: '==o', stroke: 'thick', type: 'arrow_circle' },
|
||||
{ edgeStart: '-.', edgeEnd: '.-o', stroke: 'dotted', type: 'arrow_circle' },
|
||||
{ edgeStart: '--', edgeEnd: '-->', stroke: 'normal', type: 'arrow_point' },
|
||||
{ edgeStart: '==', edgeEnd: '==>', stroke: 'thick', type: 'arrow_point' },
|
||||
{ edgeStart: '-.', edgeEnd: '.->', stroke: 'dotted', type: 'arrow_point' },
|
||||
|
||||
{ edgeStart: '--', edgeEnd: '----x', stroke: 'normal', type: 'arrow_cross' },
|
||||
{ edgeStart: '==', edgeEnd: '====x', stroke: 'thick', type: 'arrow_cross' },
|
||||
{ edgeStart: '-.', edgeEnd: '...-x', stroke: 'dotted', type: 'arrow_cross' },
|
||||
{ edgeStart: '--', edgeEnd: '----o', stroke: 'normal', type: 'arrow_circle' },
|
||||
{ edgeStart: '==', edgeEnd: '====o', stroke: 'thick', type: 'arrow_circle' },
|
||||
{ edgeStart: '-.', edgeEnd: '...-o', stroke: 'dotted', type: 'arrow_circle' },
|
||||
{ edgeStart: '--', edgeEnd: '---->', stroke: 'normal', type: 'arrow_point' },
|
||||
{ edgeStart: '==', edgeEnd: '====>', stroke: 'thick', type: 'arrow_point' },
|
||||
{ edgeStart: '-.', edgeEnd: '...->', stroke: 'dotted', type: 'arrow_point' },
|
||||
];
|
||||
|
||||
describe('[Edges] when parsing', () => {
|
||||
beforeEach(function () {
|
||||
@ -67,6 +88,74 @@ describe('[Edges] when parsing', () => {
|
||||
expect(edges[0].type).toBe('arrow_circle');
|
||||
});
|
||||
|
||||
describe('edges with ids', function () {
|
||||
describe('open ended edges with ids and labels', function () {
|
||||
regularEdges.forEach((edgeType) => {
|
||||
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
|
||||
const res = flow.parser.parse(
|
||||
`flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
|
||||
);
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
expect(vert.get('A').id).toBe('A');
|
||||
expect(vert.get('B').id).toBe('B');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].id).toBe('e1');
|
||||
expect(edges[0].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe(`${edgeType.type}`);
|
||||
expect(edges[0].text).toBe('');
|
||||
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
|
||||
});
|
||||
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
|
||||
const res = flow.parser.parse(
|
||||
`flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
|
||||
);
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
expect(vert.get('A').id).toBe('A');
|
||||
expect(vert.get('B').id).toBe('B');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].id).toBe('e1');
|
||||
expect(edges[0].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe(`${edgeType.type}`);
|
||||
expect(edges[0].text).toBe('');
|
||||
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
|
||||
});
|
||||
});
|
||||
it('should handle normal edges where you also have a node with metadata', function () {
|
||||
const res = flow.parser.parse(`flowchart LR
|
||||
A id1@-->B
|
||||
A@{ shape: 'rect' }
|
||||
`);
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(edges[0].id).toBe('id1');
|
||||
});
|
||||
});
|
||||
describe('double ended edges with ids and labels', function () {
|
||||
doubleEndedEdges.forEach((edgeType) => {
|
||||
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
|
||||
const res = flow.parser.parse(
|
||||
`flowchart TD;\nA e1@${edgeType.edgeStart} label ${edgeType.edgeEnd} B;`
|
||||
);
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
expect(vert.get('A').id).toBe('A');
|
||||
expect(vert.get('B').id).toBe('B');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].id).toBe('e1');
|
||||
expect(edges[0].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe(`${edgeType.type}`);
|
||||
expect(edges[0].text).toBe('label');
|
||||
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edges', function () {
|
||||
doubleEndedEdges.forEach((edgeType) => {
|
||||
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
|
||||
|
@ -141,6 +141,7 @@ that id.
|
||||
.*direction\s+RL[^\n]* return 'direction_rl';
|
||||
.*direction\s+LR[^\n]* return 'direction_lr';
|
||||
|
||||
[^\s]+\@(?=[^\{]) { return 'LINK_ID'; }
|
||||
[0-9]+ return 'NUM';
|
||||
\# return 'BRKT';
|
||||
":::" return 'STYLE_SEPARATOR';
|
||||
@ -201,7 +202,9 @@ that id.
|
||||
"*" return 'MULT';
|
||||
"#" return 'BRKT';
|
||||
"&" return 'AMP';
|
||||
([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+ return 'NODE_STRING';
|
||||
([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+ {
|
||||
return 'NODE_STRING';
|
||||
}
|
||||
"-" return 'MINUS'
|
||||
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
|
||||
@ -361,7 +364,7 @@ spaceList
|
||||
|
||||
statement
|
||||
: vertexStatement separator
|
||||
{ /* console.warn('finat vs', $vertexStatement.nodes); */ $$=$vertexStatement.nodes}
|
||||
{ $$=$vertexStatement.nodes}
|
||||
| styleStatement separator
|
||||
{$$=[];}
|
||||
| linkStyleStatement separator
|
||||
@ -472,6 +475,8 @@ link: linkStatement arrowText
|
||||
{$$ = $linkStatement;}
|
||||
| START_LINK edgeText LINK
|
||||
{var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText};}
|
||||
| LINK_ID START_LINK edgeText LINK
|
||||
{var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText, "id": $LINK_ID};}
|
||||
;
|
||||
|
||||
edgeText: edgeTextToken
|
||||
@ -487,6 +492,8 @@ edgeText: edgeTextToken
|
||||
|
||||
linkStatement: LINK
|
||||
{var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length};}
|
||||
| LINK_ID LINK
|
||||
{var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length, "id": $LINK_ID};}
|
||||
;
|
||||
|
||||
arrowText:
|
||||
|
@ -62,6 +62,10 @@ export interface FlowEdge {
|
||||
length?: number;
|
||||
text: string;
|
||||
labelType: 'text';
|
||||
classes: string[];
|
||||
id?: string;
|
||||
animation?: 'fast' | 'slow';
|
||||
animate?: boolean;
|
||||
}
|
||||
|
||||
export interface FlowClass {
|
||||
|
@ -711,6 +711,67 @@ flowchart TB
|
||||
B --> D
|
||||
```
|
||||
|
||||
### Attaching an ID to Edges
|
||||
|
||||
Mermaid now supports assigning IDs to edges, similar to how IDs and metadata can be attached to nodes. This feature lays the groundwork for more advanced styling, classes, and animation capabilities on edges.
|
||||
|
||||
**Syntax:**
|
||||
|
||||
To give an edge an ID, prepend the edge syntax with the ID followed by an `@` character. For example:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
```
|
||||
|
||||
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
|
||||
|
||||
### Turning an Animation On
|
||||
|
||||
Once you have assigned an ID to an edge, you can turn on animations for that edge by defining the edge’s properties:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@==> B
|
||||
e1@{ animate: true }
|
||||
```
|
||||
|
||||
This tells Mermaid that the edge `e1` should be animated.
|
||||
|
||||
### Selecting Type of Animation
|
||||
|
||||
In the initial version, two animation speeds are supported: `fast` and `slow`. Selecting a specific animation type is a shorthand for enabling animation and setting the animation speed in one go.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
e1@{ animation: fast }
|
||||
```
|
||||
|
||||
This is equivalent to `{ animate: true, animation: fast }`.
|
||||
|
||||
### Using classDef Statements for Animations
|
||||
|
||||
You can also animate edges by assigning a class to them and then defining animation properties in a `classDef` statement. For example:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
|
||||
class e1 animate
|
||||
```
|
||||
|
||||
In this snippet:
|
||||
|
||||
- `e1@-->` creates an edge with ID `e1`.
|
||||
- `classDef animate` defines a class named `animate` with styling and animation properties.
|
||||
- `class e1 animate` applies the `animate` class to the edge `e1`.
|
||||
|
||||
**Note on Escaping Commas:**
|
||||
When setting the `stroke-dasharray` property, remember to escape commas as `\,` since commas are used as delimiters in Mermaid’s style definitions.
|
||||
|
||||
## New arrow types
|
||||
|
||||
There are new types of arrows supported:
|
||||
|
@ -9,6 +9,7 @@ import { curveBasis, line, select } from 'd3';
|
||||
import rough from 'roughjs';
|
||||
import createLabel from './createLabel.js';
|
||||
import { addEdgeMarkers } from './edgeMarker.ts';
|
||||
import { isLabelStyle } from './shapes/handDrawnShapeStyles.js';
|
||||
|
||||
const edgeLabels = new Map();
|
||||
const terminalLabels = new Map();
|
||||
@ -428,6 +429,13 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
|
||||
let pointsHasChanged = false;
|
||||
const tail = startNode;
|
||||
var head = endNode;
|
||||
const edgeClassStyles = [];
|
||||
for (const key in edge.cssCompiledStyles) {
|
||||
if (isLabelStyle(key)) {
|
||||
continue;
|
||||
}
|
||||
edgeClassStyles.push(edge.cssCompiledStyles[key]);
|
||||
}
|
||||
|
||||
if (head.intersect && tail.intersect) {
|
||||
points = points.slice(1, edge.points.length - 1);
|
||||
@ -501,6 +509,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
|
||||
let svgPath;
|
||||
let linePath = lineFunction(lineData);
|
||||
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
|
||||
|
||||
if (edge.look === 'handDrawn') {
|
||||
const rc = rough.svg(elem);
|
||||
Object.assign([], lineData);
|
||||
@ -521,12 +530,27 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
|
||||
svgPath.attr('d', d);
|
||||
elem.node().appendChild(svgPath.node());
|
||||
} else {
|
||||
const stylesFromClasses = edgeClassStyles.join(';');
|
||||
const styles = edgeStyles ? edgeStyles.reduce((acc, style) => acc + style + ';', '') : '';
|
||||
let animationClass = '';
|
||||
if (edge.animate) {
|
||||
animationClass = ' edge-animation-fast';
|
||||
}
|
||||
if (edge.animation) {
|
||||
animationClass = ' edge-animation-' + edge.animation;
|
||||
}
|
||||
svgPath = elem
|
||||
.append('path')
|
||||
.attr('d', linePath)
|
||||
.attr('id', edge.id)
|
||||
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
|
||||
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
|
||||
.attr(
|
||||
'class',
|
||||
' ' +
|
||||
strokeClasses +
|
||||
(edge.classes ? ' ' + edge.classes : '') +
|
||||
(animationClass ? animationClass : '')
|
||||
)
|
||||
.attr('style', stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles);
|
||||
}
|
||||
|
||||
// DEBUG code, DO NOT REMOVE
|
||||
|
@ -32,7 +32,28 @@ export const styles2Map = (styles: string[]) => {
|
||||
});
|
||||
return styleMap;
|
||||
};
|
||||
|
||||
export const isLabelStyle = (key: string) => {
|
||||
return (
|
||||
key === 'color' ||
|
||||
key === 'font-size' ||
|
||||
key === 'font-family' ||
|
||||
key === 'font-weight' ||
|
||||
key === 'font-style' ||
|
||||
key === 'text-decoration' ||
|
||||
key === 'text-align' ||
|
||||
key === 'text-transform' ||
|
||||
key === 'line-height' ||
|
||||
key === 'letter-spacing' ||
|
||||
key === 'word-spacing' ||
|
||||
key === 'text-shadow' ||
|
||||
key === 'text-overflow' ||
|
||||
key === 'white-space' ||
|
||||
key === 'word-wrap' ||
|
||||
key === 'word-break' ||
|
||||
key === 'overflow-wrap' ||
|
||||
key === 'hyphens'
|
||||
);
|
||||
};
|
||||
export const styles2String = (node: Node) => {
|
||||
const { stylesArray } = compileStyles(node);
|
||||
const labelStyles: string[] = [];
|
||||
@ -42,26 +63,7 @@ export const styles2String = (node: Node) => {
|
||||
|
||||
stylesArray.forEach((style) => {
|
||||
const key = style[0];
|
||||
if (
|
||||
key === 'color' ||
|
||||
key === 'font-size' ||
|
||||
key === 'font-family' ||
|
||||
key === 'font-weight' ||
|
||||
key === 'font-style' ||
|
||||
key === 'text-decoration' ||
|
||||
key === 'text-align' ||
|
||||
key === 'text-transform' ||
|
||||
key === 'line-height' ||
|
||||
key === 'letter-spacing' ||
|
||||
key === 'word-spacing' ||
|
||||
key === 'text-shadow' ||
|
||||
key === 'text-overflow' ||
|
||||
key === 'white-space' ||
|
||||
key === 'word-wrap' ||
|
||||
key === 'word-break' ||
|
||||
key === 'overflow-wrap' ||
|
||||
key === 'hyphens'
|
||||
) {
|
||||
if (isLabelStyle(key)) {
|
||||
labelStyles.push(style.join(':') + ' !important');
|
||||
} else {
|
||||
nodeStyles.push(style.join(':') + ' !important');
|
||||
|
@ -96,11 +96,14 @@ export interface Edge {
|
||||
label?: string;
|
||||
classes?: string;
|
||||
style?: string[];
|
||||
animate?: boolean;
|
||||
animation?: 'fast' | 'slow';
|
||||
// Properties common to both Flowchart and State Diagram edges
|
||||
arrowhead?: string;
|
||||
arrowheadStyle?: string;
|
||||
arrowTypeEnd?: string;
|
||||
arrowTypeStart?: string;
|
||||
cssCompiledStyles?: string[];
|
||||
// Flowchart specific properties
|
||||
defaultInterpolate?: string;
|
||||
end?: string;
|
||||
|
@ -27,7 +27,28 @@ const getStyles = (
|
||||
font-size: ${options.fontSize};
|
||||
fill: ${options.textColor}
|
||||
}
|
||||
|
||||
@keyframes edge-animation-frame {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
@keyframes dash {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
& .edge-animation-slow {
|
||||
stroke-dasharray: 9,5 !important;
|
||||
stroke-dashoffset: 900;
|
||||
animation: dash 50s linear infinite;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
& .edge-animation-fast {
|
||||
stroke-dasharray: 9,5 !important;
|
||||
stroke-dashoffset: 900;
|
||||
animation: dash 20s linear infinite;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
/* Classes common for multiple diagrams */
|
||||
|
||||
& .error-icon {
|
||||
|
@ -12,6 +12,11 @@ export interface NodeMetaData {
|
||||
assigned?: string;
|
||||
ticket?: string;
|
||||
}
|
||||
|
||||
export interface EdgeMetaData {
|
||||
animation?: 'fast' | 'slow';
|
||||
animate?: boolean;
|
||||
}
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
|
||||
export interface Point {
|
||||
|
@ -937,8 +937,12 @@ export const getEdgeId = (
|
||||
counter?: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
}
|
||||
},
|
||||
id?: string
|
||||
) => {
|
||||
if (id) {
|
||||
return id;
|
||||
}
|
||||
return `${prefix ? `${prefix}_` : ''}${from}_${to}_${counter}${suffix ? `_${suffix}` : ''}`;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user