diff --git a/cypress/platform/current.html b/cypress/platform/current.html
index ef57f9205..d158c27e4 100644
--- a/cypress/platform/current.html
+++ b/cypress/platform/current.html
@@ -36,14 +36,14 @@
graph TB
A[apan klättrar]-- i träd -->B
- subgraph Test
+ subgraph id1 [Test with title wider then the node in the subgraph]
B
end
flowchart TB
A[apan klättrar]-- i träd -->B
- subgraph Test
+ subgraph id1 [Test with title wider then the node in the subgraph]
B
end
diff --git a/docs/flowchart.md b/docs/flowchart.md
index ee4f0e130..b5a6044cc 100644
--- a/docs/flowchart.md
+++ b/docs/flowchart.md
@@ -409,6 +409,22 @@ graph TB
end
```
+ You can also set an excplicit id for the subgraph.
+
+```
+graph TB
+ c1-->a2
+ subgraph ide1 [one]
+ a1-->a2
+ end
+ ```
+```mermaid
+graph TB
+ c1-->a2
+ subgraph id1 [one]
+ a1-->a2
+ end
+ ```
## Interaction
diff --git a/src/dagre-wrapper/GraphObjects.md b/src/dagre-wrapper/GraphObjects.md
new file mode 100644
index 000000000..9a982feb8
--- /dev/null
+++ b/src/dagre-wrapper/GraphObjects.md
@@ -0,0 +1,38 @@
+# Graph objects and their properties
+
+Explains the representation of various objects used to render the flow charts and what the properties mean. This ofc from the perspective of the dagre-wrapper.
+
+## node
+
+Sample object:
+```json
+{
+ "labelType":"svg",
+ "labelStyle":"",
+ "shape":"rect",
+ "label":{},
+ "labelText":"Test",
+ "rx":0,"ry":0,
+ "class":"default",
+ "style":"",
+ "id":"Test",
+ "type":"group",
+ "padding":15}
+```
+
+This is set by the renderer of the diagram and insert the data that the wrapper neds for rendering.
+
+| property | description |
+| ---------- | ----------------------------------------------------------------------------------------------------------- |
+| labelType | If the label should be html label or a svg label. Should we continue to support both? |
+| labelStyle | Css styles for the label. Not currently used. |
+| shape | The shape of the node. Currently on rect is suppoerted. This will change. |
+| label | ?? |
+| labelText | The text on the label |
+| rx | The corner radius - maybe part of the shape instead? |
+| ry | The corner radius - maybe part of the shape instead? |
+| class | Class to be set for the shape |
+| style | Css styles for the actual shape |
+| id | id of the shape |
+| type | if set to group then this node indicates a cluster. |
+| padding | Padding. Passed from the renderr as this might differ between react for different diagrams. Maybe obsolete. |
diff --git a/src/dagre-wrapper/clusters.js b/src/dagre-wrapper/clusters.js
new file mode 100644
index 000000000..05667aadc
--- /dev/null
+++ b/src/dagre-wrapper/clusters.js
@@ -0,0 +1,70 @@
+import intersectRect from './intersect/intersect-rect';
+import { logger } from '../logger'; // eslint-disable-line
+import createLabel from './createLabel';
+
+const rect = (parent, node) => {
+ // Add outer g element
+ const shapeSvg = parent
+ .insert('g')
+ .attr('class', 'cluster')
+ .attr('id', node.id);
+
+ // add the rect
+ const rect = shapeSvg.insert('rect', ':first-child');
+
+ // Create the label and insert it after the rect
+ const label = shapeSvg.insert('g').attr('class', 'cluster-label');
+
+ const text = label.node().appendChild(createLabel(node.labelText, node.labelStyle));
+
+ // Get the size of the label
+ const bbox = text.getBBox();
+
+ const padding = 0 * node.padding;
+ const halfPadding = padding / 2;
+
+ // center the rect around its coordinate
+ rect
+ .attr('rx', node.rx)
+ .attr('ry', node.ry)
+ .attr('x', node.x - node.width / 2 - halfPadding)
+ .attr('y', node.y - node.height / 2 - halfPadding)
+ .attr('width', node.width + padding)
+ .attr('height', node.height + padding);
+
+ const adj = (node.width + node.padding - bbox.width) / 2;
+
+ // Center the label
+ label.attr('transform', 'translate(' + adj + ', ' + (node.y - node.height / 2) + ')');
+ // label.attr('transform', 'translate(' + 70 + ', ' + -node.height / 2 + ')');
+
+ const rectBox = rect.node().getBBox();
+ node.width = rectBox.width;
+ node.height = rectBox.height;
+
+ node.intersect = function(point) {
+ return intersectRect(node, point);
+ };
+
+ return shapeSvg;
+};
+
+const shapes = { rect };
+
+const clusterElems = {};
+
+export const insertCluster = (elem, node) => {
+ clusterElems[node.id] = shapes[node.shape](elem, node);
+};
+export const getClusterTitleWidth = (elem, node) => {
+ const label = createLabel(node.labelText, node.labelStyle);
+ elem.node().appendChild(label);
+ const width = label.getBBox().width;
+ elem.node().removeChild(label);
+ return width;
+};
+
+export const positionCluster = node => {
+ const el = clusterElems[node.id];
+ el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
+};
diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js
index 1785cc15d..63fef85aa 100644
--- a/src/dagre-wrapper/index.js
+++ b/src/dagre-wrapper/index.js
@@ -1,6 +1,7 @@
import dagre from 'dagre';
import insertMarkers from './markers';
import { insertNode, positionNode } from './nodes';
+import { insertCluster, positionCluster, getClusterTitleWidth } from './clusters';
import { insertEdgeLabel, positionEdgeLabel, insertEdge } from './edges';
import { logger } from '../logger';
@@ -14,29 +15,50 @@ export const render = (elem, graph) => {
// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
// to the abstract node and is later used by dagre for the layout
+ const nodes2expand = [];
graph.nodes().forEach(function(v) {
- logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
- insertNode(nodes, graph.node(v));
+ const node = graph.node(v);
+ logger.info('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
+ if (node.type !== 'group') {
+ insertNode(nodes, graph.node(v));
+ } else {
+ const width = getClusterTitleWidth(clusters, node);
+ const children = graph.children(v);
+ nodes2expand.push({ id: children[0], width });
+ }
+ });
+
+ nodes2expand.forEach(item => {
+ const node = graph.node(item.id);
+ node.width = item.width;
});
// Inster labels, this will insert them into the dom so that the width can be calculated
graph.edges().forEach(function(e) {
- logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
+ logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
insertEdgeLabel(edgeLabels, graph.edge(e));
});
+ logger.info('#############################################');
+ logger.info('### Layout ###');
+ logger.info('#############################################');
dagre.layout(graph);
// Move the nodes to the correct place
graph.nodes().forEach(function(v) {
- logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
- positionNode(graph.node(v));
+ const node = graph.node(v);
+ logger.info('Node ' + v + ': ' + JSON.stringify(graph.node(v)));
+ if (node.type !== 'group') {
+ positionNode(node);
+ } else {
+ insertCluster(clusters, node);
+ }
});
// Move the edge labels to the correct place after layout
graph.edges().forEach(function(e) {
const edge = graph.edge(e);
- logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge));
+ logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge));
insertEdge(edgePaths, edge);
positionEdgeLabel(edge);
diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js
index 8a07391d3..29f599fe8 100644
--- a/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/src/diagrams/flowchart/flowRenderer-v2.js
@@ -140,6 +140,8 @@ export const addVertices = function(vert, g, svgId) {
class: classStr,
style: styles.style,
id: vertex.id,
+ width: vertex.type === 'group' ? 500 : undefined,
+ type: vertex.type,
padding: getConfig().flowchart.padding
});
});