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