diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 831a5d33a..c01729c84 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -59,8 +59,16 @@
stateDiagram-v2 - Second --> Third - Second --> Fourth + state fork_state <> + [*] --> fork_state + fork_state --> State2 + fork_state --> State3 + + state join_state < > + State2 --> join_state + State3 --> join_state + join_state --> State4 + State4 --> [*]
diff --git a/packages/mermaid/src/diagrams/state/stateCommon.ts b/packages/mermaid/src/diagrams/state/stateCommon.ts
index 7d80f41e0..e847d1514 100644
--- a/packages/mermaid/src/diagrams/state/stateCommon.ts
+++ b/packages/mermaid/src/diagrams/state/stateCommon.ts
@@ -28,6 +28,7 @@ export const G_EDGE_LABELTYPE = 'text';
export const G_EDGE_THICKNESS = 'normal';
export const CSS_EDGE = 'transition';
+export const CSS_DIAGRAM = 'statediagram';
export default {
DEFAULT_DIAGRAM_DIRECTION,
@@ -44,4 +45,5 @@ export default {
G_EDGE_LABELTYPE,
G_EDGE_THICKNESS,
CSS_EDGE,
+ CSS_DIAGRAM,
};
diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js
index e9a4148b9..89bfb55d2 100644
--- a/packages/mermaid/src/diagrams/state/stateDb.js
+++ b/packages/mermaid/src/diagrams/state/stateDb.js
@@ -27,6 +27,7 @@ import {
G_EDGE_THICKNESS,
CSS_EDGE,
} from './stateCommon.js';
+import { rect } from 'dagre-d3-es/src/dagre-js/intersect/index.js';
const START_NODE = '[*]';
const START_TYPE = 'start';
@@ -555,9 +556,36 @@ const dataFetcher = (parentId, doc, nodes, edges) => {
stateKeys.forEach((key) => {
const item = currentDocument.states[key];
+ console.log('Item:', item);
+
+ let itemShape = 'rect';
+ if (item.type === 'default' && item.id === 'root_start') {
+ itemShape = 'stateStart';
+ }
+ if (item.type === 'default' && item.id === 'root_end') {
+ itemShape = 'stateEnd';
+ }
+
+ if (item.type === 'fork' || item.type === 'join') {
+ itemShape = 'forkJoin';
+ }
+
+ if (item.type === 'choice') {
+ itemShape = 'choice';
+ }
+
+ if (item.id === '' && item.type === 'default') {
+ //ignore this item
+ return;
+ }
+
+ if (item.id === '' && item.type === 'default') {
+ //ignore this item
+ return;
+ }
if (parentId) {
- nodes.push({ ...item, labelText: item.id, labelType: 'text', parentId, shape: 'rect' });
+ nodes.push({ ...item, labelText: item.id, labelType: 'text', parentId, shape: itemShape });
} else {
nodes.push({
...item,
@@ -565,7 +593,7 @@ const dataFetcher = (parentId, doc, nodes, edges) => {
// description: item.id,
labelType: 'text',
labelStyle: '',
- shape: 'rect',
+ shape: itemShape,
padding: 15,
classes: ' statediagram-state',
});
diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts b/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts
index bf72d3cbb..4a44d8345 100644
--- a/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts
+++ b/packages/mermaid/src/diagrams/state/stateRenderer-v3-unified.ts
@@ -8,6 +8,7 @@ import { render } from '../../rendering-util/render.js';
import insertElementsForSize, {
getDiagramElements,
} from '../../rendering-util/inserElementsForSize.js';
+import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import {
DEFAULT_DIAGRAM_DIRECTION,
DEFAULT_NESTED_DOC_DIR,
@@ -15,6 +16,7 @@ import {
STMT_RELATION,
DEFAULT_STATE_TYPE,
DIVIDER_TYPE,
+ CSS_DIAGRAM,
} from './stateCommon.js';
// Configuration
@@ -93,6 +95,8 @@ export const draw = async function (text: string, id: string, _version: string,
data4Layout.diagramId = id;
console.log('REF1:', data4Layout);
await render(data4Layout, svg, element);
+ const padding = 8;
+ setupViewPortForSVG(svg, padding, CSS_DIAGRAM, conf.useMaxWidth);
};
export default {
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js
index 88db04393..bf25716de 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js
+++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js
@@ -1,5 +1,9 @@
import { log } from '$root/logger.js';
-import { rect } from './shapes/rect.js';
+import { rect } from './shapes/rect.ts';
+import { stateStart } from './shapes/stateStart.ts';
+import { stateEnd } from './shapes/stateEnd.ts';
+import { forkJoin } from './shapes/forkJoin.ts';
+import { choice } from './shapes/choice.ts';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
const formatClass = (str) => {
@@ -11,6 +15,10 @@ const formatClass = (str) => {
const shapes = {
rect,
+ stateStart,
+ stateEnd,
+ forkJoin,
+ choice,
};
let nodeElems = {};
@@ -19,9 +27,9 @@ export const insertNode = async (elem, node, dir) => {
let newEl;
let el;
- console.log('insertNode element', elem, elem.node(), rect);
// debugger;
// Add link when appropriate
+ console.log('node.link', node.link);
if (node.link) {
let target;
if (getConfig().securityLevel === 'sandbox') {
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/choice.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/choice.ts
new file mode 100644
index 000000000..04d4466ad
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/choice.ts
@@ -0,0 +1,37 @@
+import intersect from '../intersect/index.js';
+import type { Node } from '$root/rendering-util/types.d.ts';
+import type { SVG } from '$root/diagram-api/types.js';
+
+export const choice = (parent: SVG, node: Node) => {
+ const shapeSvg = parent
+ .insert('g')
+ .attr('class', 'node default')
+ .attr('id', node.domId || node.id);
+
+ const s = 28;
+ const points = [
+ { x: 0, y: s / 2 },
+ { x: s / 2, y: 0 },
+ { x: 0, y: -s / 2 },
+ { x: -s / 2, y: 0 },
+ ];
+
+ const choice = shapeSvg.insert('polygon', ':first-child').attr(
+ 'points',
+ points
+ .map(function (d) {
+ return d.x + ',' + d.y;
+ })
+ .join(' ')
+ );
+ // center the circle around its coordinate
+ choice.attr('class', 'state-start').attr('r', 7).attr('width', 28).attr('height', 28);
+ node.width = 28;
+ node.height = 28;
+
+ node.intersect = function (point) {
+ return intersect.circle(node, 14, point);
+ };
+
+ return shapeSvg;
+};
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts
new file mode 100644
index 000000000..657749051
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts
@@ -0,0 +1,51 @@
+import { log } from '$root/logger.js';
+import { updateNodeBounds } from './util.js';
+import intersect from '../intersect/index.js';
+import type { Node } from '$root/rendering-util/types.d.ts';
+import type { SVG } from '$root/diagram-api/types.js';
+
+export const forkJoin = (parent: SVG, node: Node, dir: string) => {
+ const shapeSvg = parent
+ .insert('g')
+ .attr('class', 'node default')
+ .attr('id', node.domId || node.id);
+
+ let width = 70;
+ let height = 10;
+
+ if (dir === 'LR') {
+ width = 10;
+ height = 70;
+ }
+
+ const shape = shapeSvg
+ .append('rect')
+ .attr('x', (-1 * width) / 2)
+ .attr('y', (-1 * height) / 2)
+ .attr('width', width)
+ .attr('height', height)
+ .attr('class', 'fork-join');
+
+ updateNodeBounds(node, shape);
+ let nodeHeight = 0;
+ let nodeWidth = 0;
+ let nodePadding = 10;
+ if (node.height) {
+ nodeHeight = node.height;
+ }
+ if (node.width) {
+ nodeWidth = node.width;
+ }
+
+ if (node.padding) {
+ nodePadding = node.padding;
+ }
+
+ node.height = nodeHeight + nodePadding / 2;
+ node.width = nodeWidth + nodePadding / 2;
+ node.intersect = function (point) {
+ return intersect.rect(node, point);
+ };
+
+ return shapeSvg;
+};
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/rect.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/rect.ts
index 03abb1f93..30469e8d9 100644
--- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/rect.ts
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/rect.ts
@@ -4,6 +4,7 @@ import intersect from '../intersect/index.js';
import type { Node } from '$root/rendering-util/types.d.ts';
import rough from 'roughjs';
import { select } from 'd3';
+
/**
*
* @param rect
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts
new file mode 100644
index 000000000..3f968fe86
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts
@@ -0,0 +1,26 @@
+import { log } from '$root/logger.js';
+import { updateNodeBounds } from './util.js';
+import intersect from '../intersect/index.js';
+import type { Node } from '$root/rendering-util/types.d.ts';
+import type { SVG } from '$root/diagram-api/types.js';
+
+export const stateEnd = (parent: SVG, node: Node) => {
+ const shapeSvg = parent
+ .insert('g')
+ .attr('class', 'node default')
+ .attr('id', node.domId || node.id);
+ const innerCircle = shapeSvg.insert('circle', ':first-child');
+ const circle = shapeSvg.insert('circle', ':first-child');
+
+ circle.attr('class', 'state-start').attr('r', 7).attr('width', 14).attr('height', 14);
+
+ innerCircle.attr('class', 'state-end').attr('r', 5).attr('width', 10).attr('height', 10);
+
+ updateNodeBounds(node, circle);
+
+ node.intersect = function (point) {
+ return intersect.circle(node, 7, point);
+ };
+
+ return shapeSvg;
+};
diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateStart.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateStart.ts
new file mode 100644
index 000000000..20dc861e9
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateStart.ts
@@ -0,0 +1,24 @@
+import { log } from '$root/logger.js';
+import { updateNodeBounds } from './util.js';
+import intersect from '../intersect/index.js';
+import type { Node } from '$root/rendering-util/types.d.ts';
+import type { SVG } from '$root/diagram-api/types.js';
+
+export const stateStart = (parent: SVG, node: Node) => {
+ const shapeSvg = parent
+ .insert('g')
+ .attr('class', 'node default')
+ .attr('id', node.domId || node.id);
+ const circle = shapeSvg.insert('circle', ':first-child');
+
+ // center the circle around its coordinate
+ circle.attr('class', 'state-start').attr('r', 7).attr('width', 14).attr('height', 14);
+
+ updateNodeBounds(node, circle);
+
+ node.intersect = function (point) {
+ return intersect.circle(node, 7, point);
+ };
+
+ return shapeSvg;
+};
diff --git a/packages/mermaid/src/rendering-util/setupViewPortForSVG.ts b/packages/mermaid/src/rendering-util/setupViewPortForSVG.ts
new file mode 100644
index 000000000..1fa2de1fd
--- /dev/null
+++ b/packages/mermaid/src/rendering-util/setupViewPortForSVG.ts
@@ -0,0 +1,40 @@
+import { configureSvgSize } from '$root/setupGraphViewbox.js';
+import type { SVG } from '$root/diagram-api/types.js';
+import { log } from '$root/logger.js';
+
+export const setupViewPortForSVG = (
+ svg: SVG,
+ padding: number,
+ cssDiagram: string,
+ useMaxWidth: boolean
+) => {
+ // Initialize the SVG element and set the diagram class
+ svg.attr('class', cssDiagram);
+
+ // Calculate the dimensions and position with padding
+ const { width, height, x, y } = calculateDimensionsWithPadding(svg, padding);
+
+ // Configure the size and aspect ratio of the SVG
+ configureSvgSize(svg, height, width, useMaxWidth);
+
+ // Update the viewBox to ensure all content is visible with padding
+ const viewBox = createViewBox(x, y, width, height, padding);
+ svg.attr('viewBox', viewBox);
+
+ // Log the viewBox configuration for debugging
+ log.debug(`viewBox configured: ${viewBox}`);
+};
+
+const calculateDimensionsWithPadding = (svg: SVG, padding: number) => {
+ const bounds = svg.node()?.getBBox() || { width: 0, height: 0, x: 0, y: 0 };
+ return {
+ width: bounds.width + padding * 2,
+ height: bounds.height + padding * 2,
+ x: bounds.x,
+ y: bounds.y,
+ };
+};
+
+const createViewBox = (x: number, y: number, width: number, height: number, padding: number) => {
+ return `${x - padding} ${y - padding} ${width} ${height}`;
+};
diff --git a/packages/mermaid/src/rendering-util/types.d.ts b/packages/mermaid/src/rendering-util/types.d.ts
index 38a48b38d..e69537e81 100644
--- a/packages/mermaid/src/rendering-util/types.d.ts
+++ b/packages/mermaid/src/rendering-util/types.d.ts
@@ -33,8 +33,10 @@ interface Node {
tooltip?: string;
type: string;
width?: number;
- intersect?: (point: any) => any;
+ height?: number;
+
// Specific properties for State Diagram nodes TODO remove and use generic properties
+ intersect?: (point: any) => any;
style?: string;
class?: string;
borders?: string;