Compare commits

..

1 Commits

Author SHA1 Message Date
darshanr0107
2ee7ef09c4 fix: note position in state diagram
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-10-10 18:41:21 +05:30
9 changed files with 55 additions and 88 deletions

View File

@@ -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>

View File

@@ -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"

View File

@@ -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);

View File

@@ -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

View File

@@ -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]}

View File

@@ -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};
} }

View File

@@ -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"

View File

@@ -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;

View File

@@ -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)