mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-03 20:34:20 +01:00
Compare commits
1 Commits
feature/47
...
6790-Fix-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ee7ef09c4 |
@@ -603,10 +603,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="test">
|
<div class="test">
|
||||||
<pre class="mermaid">
|
<pre class="mermaid">
|
||||||
---
|
|
||||||
config:
|
|
||||||
theme: dark
|
|
||||||
---
|
|
||||||
classDiagram
|
classDiagram
|
||||||
test ()--() test2
|
test ()--() test2
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -702,7 +702,6 @@ classDiagram
|
|||||||
It is possible to bind a click event to a node. The click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
It is possible to bind a click event to a node. The click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||||
|
|
||||||
You would define these actions on a separate line after all classes have been declared.
|
You would define these actions on a separate line after all classes have been declared.
|
||||||
If you have classes defined within a namespace, you can also add interaction definitions within the namespace definition, after the class(es) is defined
|
|
||||||
|
|
||||||
```
|
```
|
||||||
action className "reference" "tooltip"
|
action className "reference" "tooltip"
|
||||||
|
|||||||
@@ -627,7 +627,7 @@ export class ClassDB implements DiagramDB {
|
|||||||
padding: config.class!.padding ?? 16,
|
padding: config.class!.padding ?? 16,
|
||||||
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
||||||
shape: 'rect',
|
shape: 'rect',
|
||||||
cssStyles: [],
|
cssStyles: ['fill: none', 'stroke: black'],
|
||||||
look: config.look,
|
look: config.look,
|
||||||
};
|
};
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
|
|||||||
@@ -88,50 +88,6 @@ describe('given a basic class diagram, ', function () {
|
|||||||
expect(relations[0].title).toBe('generates');
|
expect(relations[0].title).toBe('generates');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle link statements within namespaces', function () {
|
|
||||||
spyOn(classDb, 'setLink');
|
|
||||||
const str = `classDiagram
|
|
||||||
namespace MyNamespace {
|
|
||||||
class UserService {
|
|
||||||
+createUser()
|
|
||||||
+deleteUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
class PaymentService {
|
|
||||||
+processPayment()
|
|
||||||
+refund()
|
|
||||||
}
|
|
||||||
|
|
||||||
link UserService "https://example.com/user-service"
|
|
||||||
link PaymentService "https://example.com/payment-service" "Payment Service Documentation"
|
|
||||||
}`;
|
|
||||||
|
|
||||||
parser.parse(str);
|
|
||||||
|
|
||||||
// Verify setLink was called for both classes
|
|
||||||
expect(classDb.setLink).toHaveBeenCalledWith(
|
|
||||||
'UserService',
|
|
||||||
'https://example.com/user-service'
|
|
||||||
);
|
|
||||||
expect(classDb.setLink).toHaveBeenCalledWith(
|
|
||||||
'PaymentService',
|
|
||||||
'https://example.com/payment-service'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify the classes have the correct links and are in the namespace
|
|
||||||
const userService = classDb.getClass('UserService');
|
|
||||||
const paymentService = classDb.getClass('PaymentService');
|
|
||||||
|
|
||||||
expect(userService.parent).toBe('MyNamespace');
|
|
||||||
expect(userService.link).toBe('https://example.com/user-service');
|
|
||||||
expect(userService.cssClasses).toBe('default clickable');
|
|
||||||
|
|
||||||
expect(paymentService.parent).toBe('MyNamespace');
|
|
||||||
expect(paymentService.link).toBe('https://example.com/payment-service');
|
|
||||||
expect(paymentService.tooltip).toBe('Payment Service Documentation');
|
|
||||||
expect(paymentService.cssClasses).toBe('default clickable');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle accTitle and accDescr', function () {
|
it('should handle accTitle and accDescr', function () {
|
||||||
const str = `classDiagram
|
const str = `classDiagram
|
||||||
accTitle: My Title
|
accTitle: My Title
|
||||||
|
|||||||
@@ -275,25 +275,14 @@ statement
|
|||||||
;
|
;
|
||||||
|
|
||||||
namespaceStatement
|
namespaceStatement
|
||||||
: namespaceIdentifier STRUCT_START namespaceBodyStatements STRUCT_STOP { yy.addClassesToNamespace($1, $3); }
|
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $3); }
|
||||||
| namespaceIdentifier STRUCT_START NEWLINE namespaceBodyStatements STRUCT_STOP { yy.addClassesToNamespace($1, $4); }
|
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP { yy.addClassesToNamespace($1, $4); }
|
||||||
;
|
;
|
||||||
|
|
||||||
namespaceIdentifier
|
namespaceIdentifier
|
||||||
: NAMESPACE namespaceName { $$=$2; yy.addNamespace($2); }
|
: NAMESPACE namespaceName { $$=$2; yy.addNamespace($2); }
|
||||||
;
|
;
|
||||||
|
|
||||||
namespaceBodyStatements
|
|
||||||
: namespaceBodyStatement { $$=[$1].filter(s => s !== null); }
|
|
||||||
| namespaceBodyStatement NEWLINE { $$=[$1].filter(s => s !== null); }
|
|
||||||
| namespaceBodyStatement NEWLINE namespaceBodyStatements { var filtered = [$1].filter(s => s !== null); $3.unshift(...filtered); $$=$3; }
|
|
||||||
;
|
|
||||||
|
|
||||||
namespaceBodyStatement
|
|
||||||
: classStatement { $$=$1; }
|
|
||||||
| clickStatement { $$=null; /* clickStatements don't return class names, but are processed for side effects */ }
|
|
||||||
;
|
|
||||||
|
|
||||||
classStatements
|
classStatements
|
||||||
: classStatement {$$=[$1]}
|
: classStatement {$$=[$1]}
|
||||||
| classStatement NEWLINE {$$=[$1]}
|
| classStatement NEWLINE {$$=[$1]}
|
||||||
|
|||||||
@@ -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 {
|
.nodeLabel, .edgeLabel {
|
||||||
color: ${options.classText};
|
color: ${options.classText};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -452,7 +452,6 @@ classDiagram
|
|||||||
It is possible to bind a click event to a node. The click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
It is possible to bind a click event to a node. The click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||||
|
|
||||||
You would define these actions on a separate line after all classes have been declared.
|
You would define these actions on a separate line after all classes have been declared.
|
||||||
If you have classes defined within a namespace, you can also add interaction definitions within the namespace definition, after the class(es) is defined
|
|
||||||
|
|
||||||
```
|
```
|
||||||
action className "reference" "tooltip"
|
action className "reference" "tooltip"
|
||||||
|
|||||||
@@ -27,6 +27,53 @@ import { log } from '../../../logger.js';
|
|||||||
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
|
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
|
||||||
import { getConfig } from '../../../diagram-api/diagramAPI.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) => {
|
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
||||||
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
||||||
const dir = graph.graph().rankdir;
|
const dir = graph.graph().rankdir;
|
||||||
@@ -164,6 +211,9 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
|
|
||||||
dagreLayout(graph);
|
dagreLayout(graph);
|
||||||
|
|
||||||
|
// Apply absolute note positioning after dagre layout
|
||||||
|
positionNotes(graph);
|
||||||
|
|
||||||
log.info('Graph after layout:', JSON.stringify(graphlibJson.write(graph)));
|
log.info('Graph after layout:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
// Move the nodes to the correct place
|
// Move the nodes to the correct place
|
||||||
let diff = 0;
|
let diff = 0;
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ const lollipop = (elem, type, id) => {
|
|||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
.attr('orient', 'auto')
|
.attr('orient', 'auto')
|
||||||
.append('circle')
|
.append('circle')
|
||||||
|
.attr('stroke', 'black')
|
||||||
.attr('fill', 'transparent')
|
.attr('fill', 'transparent')
|
||||||
.attr('cx', 7)
|
.attr('cx', 7)
|
||||||
.attr('cy', 7)
|
.attr('cy', 7)
|
||||||
@@ -146,6 +147,7 @@ const lollipop = (elem, type, id) => {
|
|||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
.attr('orient', 'auto')
|
.attr('orient', 'auto')
|
||||||
.append('circle')
|
.append('circle')
|
||||||
|
.attr('stroke', 'black')
|
||||||
.attr('fill', 'transparent')
|
.attr('fill', 'transparent')
|
||||||
.attr('cx', 7)
|
.attr('cx', 7)
|
||||||
.attr('cy', 7)
|
.attr('cy', 7)
|
||||||
|
|||||||
Reference in New Issue
Block a user