mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-21 21:24:16 +01:00
Compare commits
1 Commits
bug/2492-f
...
6790-Fix-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ee7ef09c4 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Support edge animation in hand drawn look
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Resolved parsing error where direction TD was not recognized within subgraphs
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Correct viewBox casing and make SVGs responsive
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Improve participant parsing and prevent recursive loops on invalid syntax
|
||||
@@ -1 +1 @@
|
||||
./packages/mermaid/CHANGELOG.md
|
||||
./packages/mermaid/CHANGELOG.md
|
||||
@@ -1 +1 @@
|
||||
./packages/mermaid/src/docs/community/contributing.md
|
||||
./packages/mermaid/src/docs/community/contributing.md
|
||||
@@ -6,7 +6,6 @@ interface CypressConfig {
|
||||
listUrl?: boolean;
|
||||
listId?: string;
|
||||
name?: string;
|
||||
screenshot?: boolean;
|
||||
}
|
||||
type CypressMermaidConfig = MermaidConfig & CypressConfig;
|
||||
|
||||
@@ -91,7 +90,7 @@ export const renderGraph = (
|
||||
|
||||
export const openURLAndVerifyRendering = (
|
||||
url: string,
|
||||
{ screenshot = true, ...options }: CypressMermaidConfig,
|
||||
options: CypressMermaidConfig,
|
||||
validation?: any
|
||||
): void => {
|
||||
const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
||||
@@ -99,15 +98,12 @@ export const openURLAndVerifyRendering = (
|
||||
cy.visit(url);
|
||||
cy.window().should('have.property', 'rendered', true);
|
||||
cy.get('svg').should('be.visible');
|
||||
cy.get('svg').should('not.have.attr', 'viewbox');
|
||||
|
||||
if (validation) {
|
||||
cy.get('svg').should(validation);
|
||||
}
|
||||
|
||||
if (screenshot) {
|
||||
verifyScreenshot(name);
|
||||
}
|
||||
verifyScreenshot(name);
|
||||
};
|
||||
|
||||
export const verifyScreenshot = (name: string): void => {
|
||||
|
||||
@@ -1029,19 +1029,4 @@ graph TD
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('FDH49: should add edge animation', () => {
|
||||
renderGraph(
|
||||
`
|
||||
flowchart TD
|
||||
A(["Start"]) L_A_B_0@--> B{"Decision"}
|
||||
B --> C["Option A"] & D["Option B"]
|
||||
style C stroke-width:4px,stroke-dasharray: 5
|
||||
L_A_B_0@{ animation: slow }
|
||||
L_B_D_0@{ animation: fast }`,
|
||||
{ look: 'handDrawn', screenshot: false }
|
||||
);
|
||||
cy.get('path#L_A_B_0').should('have.class', 'edge-animation-slow');
|
||||
cy.get('path#L_B_D_0').should('have.class', 'edge-animation-fast');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -774,21 +774,6 @@ describe('Graph', () => {
|
||||
expect(svg).to.not.have.attr('style');
|
||||
});
|
||||
});
|
||||
it('40: should add edge animation', () => {
|
||||
renderGraph(
|
||||
`
|
||||
flowchart TD
|
||||
A(["Start"]) L_A_B_0@--> B{"Decision"}
|
||||
B --> C["Option A"] & D["Option B"]
|
||||
style C stroke-width:4px,stroke-dasharray: 5
|
||||
L_A_B_0@{ animation: slow }
|
||||
L_B_D_0@{ animation: fast }`,
|
||||
{ screenshot: false }
|
||||
);
|
||||
// Verify animation classes are applied to both edges
|
||||
cy.get('path#L_A_B_0').should('have.class', 'edge-animation-slow');
|
||||
cy.get('path#L_B_D_0').should('have.class', 'edge-animation-fast');
|
||||
});
|
||||
it('58: handle styling with style expressions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -988,19 +973,4 @@ graph TD
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('70: should render a subgraph with direction TD', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
flowchart LR
|
||||
subgraph A
|
||||
direction TD
|
||||
a --> b
|
||||
end
|
||||
`,
|
||||
{
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -603,10 +603,6 @@
|
||||
</div>
|
||||
<div class="test">
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
theme: dark
|
||||
---
|
||||
classDiagram
|
||||
test ()--() test2
|
||||
</pre>
|
||||
|
||||
@@ -21,7 +21,7 @@ title: Animal example
|
||||
classDiagram
|
||||
note "From Duck till Zebra"
|
||||
Animal <|-- Duck
|
||||
note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"
|
||||
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
@@ -50,7 +50,7 @@ title: Animal example
|
||||
classDiagram
|
||||
note "From Duck till Zebra"
|
||||
Animal <|-- Duck
|
||||
note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"
|
||||
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
|
||||
@@ -627,7 +627,7 @@ export class ClassDB implements DiagramDB {
|
||||
padding: config.class!.padding ?? 16,
|
||||
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
||||
shape: 'rect',
|
||||
cssStyles: [],
|
||||
cssStyles: ['fill: none', 'stroke: black'],
|
||||
look: config.look,
|
||||
};
|
||||
nodes.push(node);
|
||||
|
||||
@@ -13,30 +13,6 @@ const getStyles = (options) =>
|
||||
|
||||
}
|
||||
|
||||
.cluster-label text {
|
||||
fill: ${options.titleColor};
|
||||
}
|
||||
.cluster-label span {
|
||||
color: ${options.titleColor};
|
||||
}
|
||||
.cluster-label span p {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.cluster rect {
|
||||
fill: ${options.clusterBkg};
|
||||
stroke: ${options.clusterBorder};
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.cluster text {
|
||||
fill: ${options.titleColor};
|
||||
}
|
||||
|
||||
.cluster span {
|
||||
color: ${options.titleColor};
|
||||
}
|
||||
|
||||
.nodeLabel, .edgeLabel {
|
||||
color: ${options.classText};
|
||||
}
|
||||
|
||||
@@ -266,156 +266,4 @@ describe('[Arrows] when parsing', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Issue #2492: Node names starting with o/x should not be consumed by arrow markers', () => {
|
||||
it('should handle node names starting with "o" after plain arrows', function () {
|
||||
const res = flow.parser.parse('graph TD;\ndev---ops;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('dev').id).toBe('dev');
|
||||
expect(vert.get('ops').id).toBe('ops');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('dev');
|
||||
expect(edges[0].end).toBe('ops');
|
||||
expect(edges[0].type).toBe('arrow_open');
|
||||
expect(edges[0].stroke).toBe('normal');
|
||||
});
|
||||
|
||||
it('should handle node names starting with "x" after plain arrows', function () {
|
||||
const res = flow.parser.parse('graph TD;\ndev---xerxes;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('dev').id).toBe('dev');
|
||||
expect(vert.get('xerxes').id).toBe('xerxes');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('dev');
|
||||
expect(edges[0].end).toBe('xerxes');
|
||||
expect(edges[0].type).toBe('arrow_open');
|
||||
expect(edges[0].stroke).toBe('normal');
|
||||
});
|
||||
|
||||
it('should still support circle arrows with spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;\nA --o 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].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe('arrow_circle');
|
||||
expect(edges[0].stroke).toBe('normal');
|
||||
});
|
||||
|
||||
it('should still support cross arrows with spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;\nC --x D;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('C').id).toBe('C');
|
||||
expect(vert.get('D').id).toBe('D');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('C');
|
||||
expect(edges[0].end).toBe('D');
|
||||
expect(edges[0].type).toBe('arrow_cross');
|
||||
expect(edges[0].stroke).toBe('normal');
|
||||
});
|
||||
|
||||
it('should support circle arrows to uppercase nodes without spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;\nA--oB;');
|
||||
|
||||
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].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe('arrow_circle');
|
||||
expect(edges[0].stroke).toBe('normal');
|
||||
});
|
||||
|
||||
it('should support cross arrows to uppercase nodes without spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;\nA--xBar;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('A').id).toBe('A');
|
||||
expect(vert.get('Bar').id).toBe('Bar');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('A');
|
||||
expect(edges[0].end).toBe('Bar');
|
||||
expect(edges[0].type).toBe('arrow_cross');
|
||||
expect(edges[0].stroke).toBe('normal');
|
||||
});
|
||||
|
||||
it('should handle thick arrows with lowercase node names starting with "o"', function () {
|
||||
const res = flow.parser.parse('graph TD;\nalpha===omega;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('alpha').id).toBe('alpha');
|
||||
expect(vert.get('omega').id).toBe('omega');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('alpha');
|
||||
expect(edges[0].end).toBe('omega');
|
||||
expect(edges[0].type).toBe('arrow_open');
|
||||
expect(edges[0].stroke).toBe('thick');
|
||||
});
|
||||
|
||||
it('should handle dotted arrows with lowercase node names starting with "o"', function () {
|
||||
const res = flow.parser.parse('graph TD;\nfoo-.-opus;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('foo').id).toBe('foo');
|
||||
expect(vert.get('opus').id).toBe('opus');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('foo');
|
||||
expect(edges[0].end).toBe('opus');
|
||||
expect(edges[0].type).toBe('arrow_open');
|
||||
expect(edges[0].stroke).toBe('dotted');
|
||||
});
|
||||
|
||||
it('should still support dotted circle arrows with spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;\nB -.-o C;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('B').id).toBe('B');
|
||||
expect(vert.get('C').id).toBe('C');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('B');
|
||||
expect(edges[0].end).toBe('C');
|
||||
expect(edges[0].type).toBe('arrow_circle');
|
||||
expect(edges[0].stroke).toBe('dotted');
|
||||
});
|
||||
|
||||
it('should still support thick cross arrows with spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;\nC ==x D;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert.get('C').id).toBe('C');
|
||||
expect(vert.get('D').id).toBe('D');
|
||||
expect(edges.length).toBe(1);
|
||||
expect(edges[0].start).toBe('C');
|
||||
expect(edges[0].end).toBe('D');
|
||||
expect(edges[0].type).toBe('arrow_cross');
|
||||
expect(edges[0].stroke).toBe('thick');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import flow from './flowParser.js';
|
||||
import { FlowDB } from '../flowDb.js';
|
||||
|
||||
describe('Flowchart arrow parsing - Issue #2492', () => {
|
||||
let flowDb: FlowDB;
|
||||
|
||||
beforeEach(() => {
|
||||
flowDb = new FlowDB();
|
||||
flow.parser.yy = flowDb;
|
||||
flowDb.clear();
|
||||
});
|
||||
|
||||
describe('Solid arrows with markers', () => {
|
||||
it('should parse --> followed by uppercase node', () => {
|
||||
const diagram = 'graph TD\nA-->B';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --> followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA-->b';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --> followed by space', () => {
|
||||
const diagram = 'graph TD\nA--> B';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --- followed by uppercase node (issue #2492)', () => {
|
||||
const diagram = 'graph TD\ndev---Ops';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --- followed by lowercase node (issue #2492)', () => {
|
||||
const diagram = 'graph TD\ndev---ops';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --o followed by uppercase node', () => {
|
||||
const diagram = 'graph TD\nA--oB';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --o followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA--ob';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --x followed by uppercase node', () => {
|
||||
const diagram = 'graph TD\nA--xBar';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --x followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA--xbar';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Thick arrows with markers', () => {
|
||||
it('should parse ==> followed by uppercase node', () => {
|
||||
const diagram = 'graph TD\nA==>B';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse ==> followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA==>b';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse === followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA===b';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dotted arrows with markers', () => {
|
||||
it('should parse -.-> followed by uppercase node', () => {
|
||||
const diagram = 'graph TD\nA-.->B';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse -.-> followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA-.->b';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse -.- followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA-.-b';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Arrows with edge text', () => {
|
||||
it('should parse arrow with edge text followed by uppercase node', () => {
|
||||
const diagram = 'graph TD\nA-->|text|B';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse arrow with edge text followed by lowercase node', () => {
|
||||
const diagram = 'graph TD\nA-->|text|b';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse multiple arrows with edge text (regression test)', () => {
|
||||
const diagram = 'graph TD\nA-->|Get money|B\nB-->C\nC-->|One|D\nC-->|Two|E';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Arrows followed by digits', () => {
|
||||
it('should parse --> followed by digit', () => {
|
||||
const diagram = 'graph LR\n47-->48';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse --> followed by node starting with digit', () => {
|
||||
const diagram = 'graph LR\nA-->48(Node)';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should parse complex diagram with digit node IDs (Sample 4)', () => {
|
||||
const diagram =
|
||||
'graph LR\n47(SAM.CommonFA.FMESummary)-->48(SAM.CommonFA.CommonFAFinanceBudget)\n37(SAM.CommonFA.BudgetSubserviceLineVolume)-->48(SAM.CommonFA.CommonFAFinanceBudget)';
|
||||
expect(() => flow.parser.parse(diagram)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -140,7 +140,6 @@ that id.
|
||||
.*direction\s+BT[^\n]* return 'direction_bt';
|
||||
.*direction\s+RL[^\n]* return 'direction_rl';
|
||||
.*direction\s+LR[^\n]* return 'direction_lr';
|
||||
.*direction\s+TD[^\n]* return 'direction_td';
|
||||
|
||||
[^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; }
|
||||
[0-9]+ return 'NUM';
|
||||
@@ -152,29 +151,17 @@ that id.
|
||||
"," return 'COMMA';
|
||||
"*" return 'MULT';
|
||||
|
||||
<INITIAL,edgeText>\s*[xo<]?\-\-+[-xo>]\s+ { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\-+[-xo>](?=[A-Z]) { return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\-+[-xo>](?=[a-z]) { return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\-+[-xo>](?=[0-9]) { return 'LINK'; }
|
||||
<INITIAL,edgeText>\s*[xo<]?\-\-+[-xo>](?=\s*\|) { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\-\s* { this.pushState("edgeText"); return 'START_LINK'; }
|
||||
<edgeText>[^-]|\-(?!\-)+ return 'EDGE_TEXT';
|
||||
<INITIAL,edgeText>\s*[xo<]?\-\-+[-xo>]\s* { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\-\s* { this.pushState("edgeText"); return 'START_LINK'; }
|
||||
<edgeText>[^-]|\-(?!\-)+ return 'EDGE_TEXT';
|
||||
|
||||
<INITIAL,thickEdgeText>\s*[xo<]?\=\=+[=xo>]\s+ { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\=\=+[=xo>](?=[A-Z]) { return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\=\=+[=xo>](?=[a-z]) { return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\=\=+[=xo>](?=[0-9]) { return 'LINK'; }
|
||||
<INITIAL,thickEdgeText>\s*[xo<]?\=\=+[=xo>](?=\s*\|) { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\=\=\s* { this.pushState("thickEdgeText"); return 'START_LINK'; }
|
||||
<thickEdgeText>[^=]|\=(?!=) return 'EDGE_TEXT';
|
||||
<INITIAL,thickEdgeText>\s*[xo<]?\=\=+[=xo>]\s* { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\=\=\s* { this.pushState("thickEdgeText"); return 'START_LINK'; }
|
||||
<thickEdgeText>[^=]|\=(?!=) return 'EDGE_TEXT';
|
||||
|
||||
<INITIAL,dottedEdgeText>\s*[xo<]?\-?\.+\-[xo>]?\s+ { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-?\.+\-[xo>]?(?=[A-Z]) { return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-?\.+\-[xo>]?(?=[a-z]) { return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-?\.+\-[xo>]?(?=[0-9]) { return 'LINK'; }
|
||||
<INITIAL,dottedEdgeText>\s*[xo<]?\-?\.+\-[xo>]?(?=\s*\|) { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\.\s* { this.pushState("dottedEdgeText"); return 'START_LINK'; }
|
||||
<dottedEdgeText>[^\.]|\.(?!-) return 'EDGE_TEXT';
|
||||
<INITIAL,dottedEdgeText>\s*[xo<]?\-?\.+\-[xo>]?\s* { this.popState(); return 'LINK'; }
|
||||
<INITIAL>\s*[xo<]?\-\.\s* { this.pushState("dottedEdgeText"); return 'START_LINK'; }
|
||||
<dottedEdgeText>[^\.]|\.(?!-) return 'EDGE_TEXT';
|
||||
|
||||
|
||||
<*>\s*\~\~[\~]+\s* return 'LINK';
|
||||
@@ -639,8 +626,6 @@ direction
|
||||
{ $$={stmt:'dir', value:'RL'};}
|
||||
| direction_lr
|
||||
{ $$={stmt:'dir', value:'LR'};}
|
||||
| direction_td
|
||||
{ $$={stmt:'dir', value:'TD'};}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
@@ -309,21 +309,4 @@ describe('when parsing subgraphs', function () {
|
||||
expect(subgraphA.nodes).toContain('a');
|
||||
expect(subgraphA.nodes).not.toContain('c');
|
||||
});
|
||||
it('should correctly parse direction TD inside a subgraph', function () {
|
||||
const res = flow.parser.parse(`
|
||||
graph LR
|
||||
subgraph WithTD
|
||||
direction TD
|
||||
A1 --> A2
|
||||
end
|
||||
`);
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.dir).toBe('TD');
|
||||
expect(subgraph.nodes).toContain('A1');
|
||||
expect(subgraph.nodes).toContain('A2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
const svgWidth = bitWidth * bitsPerRow + 2;
|
||||
const svg: SVG = selectSvgElement(id);
|
||||
|
||||
svg.attr('viewBox', `0 0 ${svgWidth} ${svgHeight}`);
|
||||
svg.attr('viewbox', `0 0 ${svgWidth} ${svgHeight}`);
|
||||
configureSvgSize(svg, svgHeight, svgWidth, config.useMaxWidth);
|
||||
|
||||
for (const [word, packet] of words.entries()) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Diagram } from '../../Diagram.js';
|
||||
import type { RadarDiagramConfig } from '../../config.type.js';
|
||||
import type { DiagramRenderer, DrawDefinition, SVG, SVGGroup } from '../../diagram-api/types.js';
|
||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import type { RadarDB, RadarAxis, RadarCurve } from './types.js';
|
||||
|
||||
const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
@@ -54,9 +53,11 @@ const drawFrame = (svg: SVG, config: Required<RadarDiagramConfig>): SVGGroup =>
|
||||
x: config.marginLeft + config.width / 2,
|
||||
y: config.marginTop + config.height / 2,
|
||||
};
|
||||
configureSvgSize(svg, totalHeight, totalWidth, config.useMaxWidth ?? true);
|
||||
|
||||
svg.attr('viewBox', `0 0 ${totalWidth} ${totalHeight}`);
|
||||
// Initialize the SVG
|
||||
svg
|
||||
.attr('viewbox', `0 0 ${totalWidth} ${totalHeight}`)
|
||||
.attr('width', totalWidth)
|
||||
.attr('height', totalHeight);
|
||||
// g element to center the radar chart
|
||||
return svg.append('g').attr('transform', `translate(${center.x}, ${center.y})`);
|
||||
};
|
||||
|
||||
@@ -32,14 +32,13 @@
|
||||
<CONFIG>[^\}]+ { return 'CONFIG_CONTENT'; }
|
||||
<CONFIG>\} { this.popState(); this.popState(); return 'CONFIG_END'; }
|
||||
<ID>[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; }
|
||||
<ID>[^<>:\n,;@\s]+(?=\s+as\s) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||
<ID>[^<>:\n,;@]+(?=\s*[\n;#]|$) { yytext = yytext.trim(); this.popState(); return 'ACTOR'; }
|
||||
<ID>[^<>:\n,;@]*\<[^\n]* { this.popState(); return 'INVALID'; }
|
||||
<ID>[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||
"box" { this.begin('LINE'); return 'box'; }
|
||||
"participant" { this.begin('ID'); return 'participant'; }
|
||||
"actor" { this.begin('ID'); return 'participant_actor'; }
|
||||
"create" return 'create';
|
||||
"destroy" { this.begin('ID'); return 'destroy'; }
|
||||
<ID>[^<\->\->:\n,;]+?([\-]*[^<\->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
|
||||
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
|
||||
"loop" { this.begin('LINE'); return 'loop'; }
|
||||
@@ -146,7 +145,6 @@ line
|
||||
: SPACE statement { $$ = $2 }
|
||||
| statement { $$ = $1 }
|
||||
| NEWLINE { $$=[]; }
|
||||
| INVALID { $$=[]; }
|
||||
;
|
||||
|
||||
box_section
|
||||
@@ -413,4 +411,4 @@ text2
|
||||
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) }
|
||||
;
|
||||
|
||||
%%
|
||||
%%
|
||||
|
||||
@@ -2609,17 +2609,5 @@ Bob->>Alice:Got it!
|
||||
expect(actors.get('E').type).toBe('entity');
|
||||
expect(actors.get('E').description).toBe('E');
|
||||
});
|
||||
it('should handle fail parsing when alias token causes conflicts in participant definition', async () => {
|
||||
let error = false;
|
||||
try {
|
||||
await Diagram.fromText(`
|
||||
sequenceDiagram
|
||||
participant SAS MyServiceWithMoreThan20Chars <br> service decription
|
||||
`);
|
||||
} catch (e) {
|
||||
error = true;
|
||||
}
|
||||
expect(error).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ title: Animal example
|
||||
classDiagram
|
||||
note "From Duck till Zebra"
|
||||
Animal <|-- Duck
|
||||
note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"
|
||||
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
|
||||
@@ -27,6 +27,53 @@ import { log } from '../../../logger.js';
|
||||
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
|
||||
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||
|
||||
/**
|
||||
* Apply absolute note positioning after dagre layout
|
||||
* This fixes the issue where TB and LR directions position notes differently
|
||||
* by making note positioning truly absolute
|
||||
*/
|
||||
const positionNotes = (graph) => {
|
||||
const noteStatePairs = [];
|
||||
|
||||
graph.nodes().forEach((nodeId) => {
|
||||
const node = graph.node(nodeId);
|
||||
if (node.position && node.shape === 'note') {
|
||||
const edges = graph.nodeEdges(nodeId);
|
||||
|
||||
for (const edge of edges) {
|
||||
const otherNodeId = edge.v === nodeId ? edge.w : edge.v;
|
||||
const otherNode = graph.node(otherNodeId);
|
||||
|
||||
if (otherNode && otherNode.shape !== 'note' && otherNode.shape !== 'noteGroup') {
|
||||
noteStatePairs.push({
|
||||
noteId: nodeId,
|
||||
noteNode: node,
|
||||
stateId: otherNodeId,
|
||||
stateNode: otherNode,
|
||||
position: node.position,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
noteStatePairs.forEach(({ noteNode, stateNode, position }) => {
|
||||
const spacing = 60;
|
||||
|
||||
let noteX = noteNode.x;
|
||||
let noteY = stateNode.y;
|
||||
|
||||
if (position === 'right of') {
|
||||
noteX = stateNode.x + stateNode.width / 2 + spacing + noteNode.width / 2;
|
||||
} else if (position === 'left of') {
|
||||
noteX = stateNode.x - stateNode.width / 2 - spacing - noteNode.width / 2;
|
||||
}
|
||||
|
||||
noteNode.x = noteX;
|
||||
noteNode.y = noteY;
|
||||
});
|
||||
};
|
||||
|
||||
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
||||
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
||||
const dir = graph.graph().rankdir;
|
||||
@@ -164,6 +211,9 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
||||
|
||||
dagreLayout(graph);
|
||||
|
||||
// Apply absolute note positioning after dagre layout
|
||||
positionNotes(graph);
|
||||
|
||||
log.info('Graph after layout:', JSON.stringify(graphlibJson.write(graph)));
|
||||
// Move the nodes to the correct place
|
||||
let diff = 0;
|
||||
|
||||
@@ -605,14 +605,6 @@ export const insertEdge = function (
|
||||
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
|
||||
let strokeColor = edgeStyles.find((style) => style?.startsWith('stroke:'));
|
||||
|
||||
let animationClass = '';
|
||||
if (edge.animate) {
|
||||
animationClass = 'edge-animation-fast';
|
||||
}
|
||||
if (edge.animation) {
|
||||
animationClass = 'edge-animation-' + edge.animation;
|
||||
}
|
||||
|
||||
let animatedEdge = false;
|
||||
if (edge.look === 'handDrawn') {
|
||||
const rc = rough.svg(elem);
|
||||
@@ -628,13 +620,7 @@ export const insertEdge = function (
|
||||
svgPath = select(svgPathNode)
|
||||
.select('path')
|
||||
.attr('id', edge.id)
|
||||
.attr(
|
||||
'class',
|
||||
' ' +
|
||||
strokeClasses +
|
||||
(edge.classes ? ' ' + edge.classes : '') +
|
||||
(animationClass ? ' ' + animationClass : '')
|
||||
)
|
||||
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
|
||||
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
|
||||
let d = svgPath.attr('d');
|
||||
svgPath.attr('d', d);
|
||||
@@ -642,6 +628,13 @@ export const insertEdge = function (
|
||||
} 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;
|
||||
}
|
||||
|
||||
const pathStyle =
|
||||
(stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles) +
|
||||
@@ -653,10 +646,7 @@ export const insertEdge = function (
|
||||
.attr('id', edge.id)
|
||||
.attr(
|
||||
'class',
|
||||
' ' +
|
||||
strokeClasses +
|
||||
(edge.classes ? ' ' + edge.classes : '') +
|
||||
(animationClass ? ' ' + animationClass : '')
|
||||
' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '')
|
||||
)
|
||||
.attr('style', pathStyle);
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ const lollipop = (elem, type, id) => {
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('circle')
|
||||
.attr('stroke', 'black')
|
||||
.attr('fill', 'transparent')
|
||||
.attr('cx', 7)
|
||||
.attr('cy', 7)
|
||||
@@ -146,6 +147,7 @@ const lollipop = (elem, type, id) => {
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('circle')
|
||||
.attr('stroke', 'black')
|
||||
.attr('fill', 'transparent')
|
||||
.attr('cx', 7)
|
||||
.attr('cy', 7)
|
||||
|
||||
Reference in New Issue
Block a user