diff --git a/cypress/platform/class.html b/cypress/platform/class.html
new file mode 100644
index 000000000..6fc4290e6
--- /dev/null
+++ b/cypress/platform/class.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ info below
+
+ classDiagram-v2
+ class BankAccount{
+ +String owner
+ +BigDecimal balance
+ +deposit(amount) bool
+ +withdrawl(amount) int
+ }
+ classA --|> classB : Inheritance
+ classC --* classD : Composition
+ classE --o classF : Aggregation
+ classG --> classH : Association
+ classI -- classJ : Link(Solid)
+ classK ..> classL : Dependency
+ classM ..|> classN : Realization
+ classO .. classP : Link(Dashed)
+ classA : +attr1
+ classA : attr2
+ classA : method1()
+ <<interface>> classB
+ classB : method2() int
+
+
+
+
+
diff --git a/src/dagre-wrapper/GraphObjects.md b/src/dagre-wrapper/GraphObjects.md
index 91bd3eee1..30365e1c8 100644
--- a/src/dagre-wrapper/GraphObjects.md
+++ b/src/dagre-wrapper/GraphObjects.md
@@ -84,6 +84,7 @@ This is set by the renderer of the diagram and insert the data that the wrapper
| id | id of the shape |
| type | if set to group then this node indicates *a cluster*. |
| padding | Padding. Passed from the render as this might differ between different diagrams. Maybe obsolete. |
+| data | Non-generic data specific to the shape. |
# edge
diff --git a/src/dagre-wrapper/edges.js b/src/dagre-wrapper/edges.js
index ce11fdc56..121f79b0a 100644
--- a/src/dagre-wrapper/edges.js
+++ b/src/dagre-wrapper/edges.js
@@ -297,35 +297,60 @@ export const insertEdge = function(elem, e, edge, clusterDb, diagramType, graph)
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
}
- logger.info('arrowType', edge.arrowType);
- switch (edge.arrowType) {
+ logger.info('arrowTypeStart', edge.arrowTypeStart);
+ logger.info('arrowTypeEnd', edge.arrowTypeEnd);
+
+ switch (edge.arrowTypeStart) {
+ case 'arrow_cross':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
+ break;
+ case 'arrow_point':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
+ break;
+ case 'arrow_barb':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
+ break;
+ case 'arrow_circle':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
+ break;
+ case 'aggregation':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')');
+ break;
+ case 'extension':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')');
+ break;
+ case 'composition':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')');
+ break;
+ case 'dependency':
+ svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')');
+ break;
+ default:
+ }
+ switch (edge.arrowTypeEnd) {
case 'arrow_cross':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
break;
- case 'double_arrow_cross':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
- break;
case 'arrow_point':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
break;
- case 'double_arrow_point':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
- break;
case 'arrow_barb':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
break;
- case 'double_arrow_barb':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barnEnd' + ')');
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
- break;
case 'arrow_circle':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
break;
- case 'double_arrow_circle':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
+ case 'aggregation':
+ svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')');
+ break;
+ case 'extension':
+ svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')');
+ break;
+ case 'composition':
+ svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')');
+ break;
+ case 'dependency':
+ svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')');
break;
default:
}
diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js
index a9d537cca..712fee30f 100644
--- a/src/dagre-wrapper/index.js
+++ b/src/dagre-wrapper/index.js
@@ -20,12 +20,12 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line
if (!graph.nodes()) {
- log.trace('No nodes found for', graph);
+ log.info('No nodes found for', graph);
} else {
- log.trace('Recursive render', graph.nodes());
+ log.info('Recursive render', graph.nodes());
}
if (graph.edges().length > 0) {
- log.trace('Recursive edges', graph.edge(graph.edges()[0]));
+ log.info('Recursive edges', graph.edge(graph.edges()[0]));
}
const clusters = elem.insert('g').attr('class', 'clusters'); // eslint-disable-line
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
@@ -39,14 +39,14 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
if (typeof parentCluster !== 'undefined') {
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
// data.clusterPositioning = true;
- log.trace('Setting data for cluster', data);
+ log.info('Setting data for cluster', data);
graph.setNode(parentCluster.id, data);
graph.setParent(v, parentCluster.id, data);
}
- log.trace('(Insert) Node ' + v + ': ' + JSON.stringify(graph.node(v)));
+ log.info('(Insert) Node ' + v + ': ' + JSON.stringify(graph.node(v)));
if (node && node.clusterNode) {
// const children = graph.children(v);
- log.trace('Cluster identified', v, node, graph.node(v));
+ log.info('Cluster identified', v, node, graph.node(v));
const newEl = recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
updateNodeBounds(node, newEl);
setNodeElem(newEl, node);
@@ -56,12 +56,12 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
if (graph.children(v).length > 0) {
// This is a cluster but not to be rendered recusively
// Render as before
- log.trace('Cluster - the non recursive path', v, node.id, node, graph);
- log.trace(findNonClusterChild(node.id, graph));
+ log.info('Cluster - the non recursive path', v, node.id, node, graph);
+ log.info(findNonClusterChild(node.id, graph));
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
// insertCluster(clusters, graph.node(v));
} else {
- log.trace('Node - the non recursive path', v, node.id, node);
+ log.info('Node - the non recursive path', v, node.id, node);
insertNode(nodes, graph.node(v), dir);
}
}
@@ -73,11 +73,11 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
// TODO: pick optimal child in the cluster to us as link anchor
graph.edges().forEach(function(e) {
const edge = graph.edge(e.v, e.w, e.name);
- log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
- log.trace('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
+ log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
+ log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// Check if link is either from or to a cluster
- log.trace('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
+ log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
insertEdgeLabel(edgeLabels, edge);
});
@@ -89,11 +89,11 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
log.info('#############################################');
log.info(graph);
dagre.layout(graph);
- log.trace('Graph after layout:', graphlib.json.write(graph));
+ log.info('Graph after layout:', graphlib.json.write(graph));
// Move the nodes to the correct place
graph.nodes().forEach(function(v) {
const node = graph.node(v);
- log.trace('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
+ log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
log.info(
'Position ' + v + ': (' + node.x,
',' + node.y,
diff --git a/src/dagre-wrapper/markers.js b/src/dagre-wrapper/markers.js
index 84a65dd68..9d879c08b 100644
--- a/src/dagre-wrapper/markers.js
+++ b/src/dagre-wrapper/markers.js
@@ -29,7 +29,7 @@ const extension = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-extensionEnd ' + type)
+ .attr('id', type + '-extensionEnd')
.attr('class', 'marker extension ' + type)
.attr('refX', 19)
.attr('refY', 7)
@@ -45,7 +45,7 @@ const composition = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-compositionStart')
- .attr('class', 'marker extension ' + type)
+ .attr('class', 'marker composition ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@@ -58,7 +58,7 @@ const composition = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-compositionEnd')
- .attr('class', 'marker extension ' + type)
+ .attr('class', 'marker composition ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
@@ -72,7 +72,7 @@ const aggregation = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-aggregationStart')
- .attr('class', 'marker extension ' + type)
+ .attr('class', 'marker aggregation ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@@ -85,7 +85,7 @@ const aggregation = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-aggregationEnd')
- .attr('class', 'marker ' + type)
+ .attr('class', 'marker aggregation ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
@@ -99,7 +99,7 @@ const dependency = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-dependencyStart')
- .attr('class', 'marker extension ' + type)
+ .attr('class', 'marker dependency ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@@ -112,7 +112,7 @@ const dependency = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-dependencyEnd')
- .attr('class', 'marker ' + type)
+ .attr('class', 'marker dependency ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js
index 624dc5272..a434ed4ab 100644
--- a/src/dagre-wrapper/nodes.js
+++ b/src/dagre-wrapper/nodes.js
@@ -1,8 +1,8 @@
-import intersect from './intersect/index.js';
import { select } from 'd3';
import { logger } from '../logger'; // eslint-disable-line
import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util';
import { getConfig } from '../config';
+import intersect from './intersect/index.js';
import createLabel from './createLabel';
import note from './shapes/note';
@@ -539,6 +539,219 @@ const end = (parent, node) => {
return shapeSvg;
};
+const class_box = (parent, node) => {
+ const halfPadding = node.padding / 2;
+ const rowPadding = 4;
+ const lineHeight = 8;
+
+ let classes;
+ if (!node.classes) {
+ classes = 'node default';
+ } else {
+ classes = 'node ' + node.classes;
+ }
+ // Add outer g element
+ const shapeSvg = parent
+ .insert('g')
+ .attr('class', classes)
+ .attr('id', node.id);
+
+ // Create the title label and insert it after the rect
+ const rect = shapeSvg.insert('rect', ':first-child');
+ const topLine = shapeSvg.insert('line');
+ const bottomLine = shapeSvg.insert('line');
+ let maxWidth = 0;
+ let maxHeight = rowPadding;
+
+ const labelContainer = shapeSvg.insert('g').attr('class', 'label');
+ let verticalPos = 0;
+ const hasInterface = node.classData.annotations && node.classData.annotations[0];
+
+ // 1. Create the labels
+ const interfaceLabel = labelContainer
+ .node()
+ .appendChild(createLabel(node.classData.annotations[0], node.labelStyle, true, true));
+ const interfaceBBox = interfaceLabel.getBBox();
+ if (node.classData.annotations[0]) {
+ maxHeight += interfaceBBox.height + rowPadding;
+ maxWidth += interfaceBBox.width;
+ }
+
+ const classTitleLabel = labelContainer
+ .node()
+ .appendChild(createLabel(node.labelText, node.labelStyle, true, true));
+ const classTitleBBox = classTitleLabel.getBBox();
+ maxHeight += classTitleBBox.height + rowPadding;
+ if (classTitleBBox.width > maxWidth) {
+ maxWidth = classTitleBBox.width;
+ }
+ const classAttributes = [];
+ node.classData.members.forEach(str => {
+ const lbl = labelContainer.node().appendChild(createLabel(str, node.labelStyle, true, true));
+ const bbox = lbl.getBBox();
+ if (bbox.width > maxWidth) {
+ maxWidth = bbox.width;
+ }
+ maxHeight += bbox.height + rowPadding;
+ classAttributes.push(lbl);
+ });
+
+ const classMethods = [];
+ node.classData.methods.forEach(str => {
+ const lbl = labelContainer.node().appendChild(createLabel(str, node.labelStyle, true, true));
+ const bbox = lbl.getBBox();
+ if (bbox.width > maxWidth) {
+ maxWidth = bbox.width;
+ }
+ maxHeight += bbox.height + rowPadding;
+
+ classMethods.push(lbl);
+ });
+
+ maxHeight += lineHeight;
+
+ // 2. Position the labels
+
+ // position the interface label
+ if (hasInterface) {
+ select(interfaceLabel).attr(
+ 'transform',
+ 'translate( ' +
+ -(maxWidth + node.padding - interfaceBBox.width / 2) / 2 +
+ ', ' +
+ (-1 * maxHeight) / 2 +
+ ')'
+ );
+ verticalPos = interfaceBBox.height + rowPadding;
+ }
+ // Positin the class title label
+ let diffX = (maxWidth - classTitleBBox.width) / 2;
+ select(classTitleLabel).attr(
+ 'transform',
+ 'translate( ' +
+ ((-1 * maxWidth) / 2 + diffX) +
+ ', ' +
+ ((-1 * maxHeight) / 2 + verticalPos) +
+ ')'
+ );
+ verticalPos += classTitleBBox.height + rowPadding;
+
+ topLine
+ .attr('class', 'divider')
+ .attr('x1', -maxWidth / 2 - halfPadding)
+ .attr('x2', maxWidth / 2 + halfPadding)
+ .attr('y1', -maxHeight / 2 - halfPadding + lineHeight + verticalPos)
+ .attr('y2', -maxHeight / 2 - halfPadding + lineHeight + verticalPos);
+
+ verticalPos += lineHeight;
+
+ classAttributes.forEach(lbl => {
+ select(lbl).attr(
+ 'transform',
+ 'translate( ' +
+ -maxWidth / 2 +
+ ', ' +
+ ((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) +
+ ')'
+ );
+ verticalPos += classTitleBBox.height + rowPadding;
+ });
+
+ bottomLine
+ .attr('class', 'divider')
+ .attr('x1', -maxWidth / 2 - halfPadding)
+ .attr('x2', maxWidth / 2 + halfPadding)
+ .attr('y1', -maxHeight / 2 - halfPadding + lineHeight + verticalPos)
+ .attr('y2', -maxHeight / 2 - halfPadding + lineHeight + verticalPos);
+
+ verticalPos += lineHeight;
+
+ classMethods.forEach(lbl => {
+ select(lbl).attr(
+ 'transform',
+ 'translate( ' + -maxWidth / 2 + ', ' + ((-1 * maxHeight) / 2 + verticalPos) + ')'
+ );
+ verticalPos += classTitleBBox.height + rowPadding;
+ });
+ //
+ let bbox;
+ if (getConfig().flowchart.htmlLabels) {
+ const div = interfaceLabel.children[0];
+ const dv = select(interfaceLabel);
+ bbox = div.getBoundingClientRect();
+ dv.attr('width', bbox.width);
+ dv.attr('height', bbox.height);
+ }
+ // bbox = labelContainer.getBBox();
+
+ // logger.info('Text 2', text2);
+ // const textRows = text2.slice(1, text2.length);
+ // let titleBox = text.getBBox();
+ // const descr = label
+ // .node()
+ // .appendChild(createLabel(textRows.join('
'), node.labelStyle, true, true));
+
+ // if (getConfig().flowchart.htmlLabels) {
+ // const div = descr.children[0];
+ // const dv = select(descr);
+ // bbox = div.getBoundingClientRect();
+ // dv.attr('width', bbox.width);
+ // dv.attr('height', bbox.height);
+ // }
+ // // bbox = label.getBBox();
+ // // logger.info(descr);
+ // select(descr).attr(
+ // 'transform',
+ // 'translate( ' +
+ // // (titleBox.width - bbox.width) / 2 +
+ // (bbox.width > titleBox.width ? 0 : (titleBox.width - bbox.width) / 2) +
+ // ', ' +
+ // (titleBox.height + halfPadding + 5) +
+ // ')'
+ // );
+ // select(text).attr(
+ // 'transform',
+ // 'translate( ' +
+ // // (titleBox.width - bbox.width) / 2 +
+ // (bbox.width < titleBox.width ? 0 : -(titleBox.width - bbox.width) / 2) +
+ // ', ' +
+ // 0 +
+ // ')'
+ // );
+ // // Get the size of the label
+
+ // // Bounding box for title and text
+ // bbox = label.node().getBBox();
+
+ // // Center the label
+ // label.attr(
+ // 'transform',
+ // 'translate(' + -bbox.width / 2 + ', ' + (-bbox.height / 2 - halfPadding + 3) + ')'
+ // );
+
+ rect
+ .attr('class', 'outer title-state')
+ .attr('x', -maxWidth / 2 - halfPadding)
+ .attr('y', -(maxHeight / 2) - halfPadding)
+ .attr('width', maxWidth + node.padding)
+ .attr('height', maxHeight + node.padding);
+
+ // innerLine
+ // .attr('class', 'divider')
+ // .attr('x1', -bbox.width / 2 - halfPadding)
+ // .attr('x2', bbox.width / 2 + halfPadding)
+ // .attr('y1', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding)
+ // .attr('y2', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding);
+
+ updateNodeBounds(node, rect);
+
+ node.intersect = function(point) {
+ return intersect.rect(node, point);
+ };
+
+ return shapeSvg;
+};
+
const shapes = {
question,
rect,
@@ -558,7 +771,8 @@ const shapes = {
note,
subroutine,
fork: forkJoin,
- join: forkJoin
+ join: forkJoin,
+ class_box
};
let nodeElems = {};
diff --git a/src/diagrams/class/classRenderer-v2.js b/src/diagrams/class/classRenderer-v2.js
new file mode 100644
index 000000000..aa37de74c
--- /dev/null
+++ b/src/diagrams/class/classRenderer-v2.js
@@ -0,0 +1,523 @@
+import { select } from 'd3';
+import dagre from 'dagre';
+import graphlib from 'graphlib';
+import { logger } from '../../logger';
+import classDb, { lookUpDomId } from './classDb';
+import { parser } from './parser/classDiagram';
+import svgDraw from './svgDraw';
+import { getConfig } from '../../config';
+import { render } from '../../dagre-wrapper/index.js';
+// import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
+import { curveLinear } from 'd3';
+import { interpolateToCurve, getStylesFromArray } from '../../utils';
+import common from '../common/common';
+
+parser.yy = classDb;
+
+let idCache = {};
+const padding = 20;
+
+const conf = {
+ dividerMargin: 10,
+ padding: 5,
+ textHeight: 10
+};
+
+/**
+ * Function that adds the vertices found during parsing to the graph to be rendered.
+ * @param vert Object containing the vertices.
+ * @param g The graph that is to be drawn.
+ */
+export const addClasses = function(classes, g) {
+ // const svg = select(`[id="${svgId}"]`);
+ const keys = Object.keys(classes);
+ logger.info('keys:', keys);
+ logger.info(classes);
+
+ // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
+ keys.forEach(function(id) {
+ const vertex = classes[id];
+
+ /**
+ * Variable for storing the classes for the vertex
+ * @type {string}
+ */
+ let classStr = 'default';
+ // if (vertex.classes.length > 0) {
+ // classStr = vertex.classes.join(' ');
+ // }
+
+ const styles = { labelStyle: '' }; //getStylesFromArray(vertex.styles);
+
+ // Use vertex id as text in the box if no text is provided by the graph definition
+ let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
+
+ // We create a SVG label, either by delegating to addHtmlLabel or manually
+ // let vertexNode;
+ // if (getConfig().flowchart.htmlLabels) {
+ // const node = {
+ // label: vertexText.replace(
+ // /fa[lrsb]?:fa-[\w-]+/g,
+ // s => ``
+ // )
+ // };
+ // vertexNode = addHtmlLabel(svg, node).node();
+ // vertexNode.parentNode.removeChild(vertexNode);
+ // } else {
+ // const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+ // svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
+
+ // const rows = vertexText.split(common.lineBreakRegex);
+
+ // for (let j = 0; j < rows.length; j++) {
+ // const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
+ // tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
+ // tspan.setAttribute('dy', '1em');
+ // tspan.setAttribute('x', '1');
+ // tspan.textContent = rows[j];
+ // svgLabel.appendChild(tspan);
+ // }
+ // vertexNode = svgLabel;
+ // }
+
+ let radious = 0;
+ let _shape = '';
+ // Set the shape based parameters
+ switch (vertex.type) {
+ case 'class':
+ _shape = 'class_box';
+ break;
+ default:
+ _shape = 'class_box';
+ }
+ // Add the node
+ g.setNode(vertex.id, {
+ labelStyle: styles.labelStyle,
+ shape: _shape,
+ labelText: vertexText,
+ classData: vertex,
+ rx: radious,
+ ry: radious,
+ class: classStr,
+ style: styles.style,
+ id: vertex.id,
+ width: vertex.type === 'group' ? 500 : undefined,
+ type: vertex.type,
+ padding: getConfig().flowchart.padding
+ });
+
+ logger.info('setNode', {
+ labelStyle: styles.labelStyle,
+ shape: _shape,
+ labelText: vertexText,
+ rx: radious,
+ ry: radious,
+ class: classStr,
+ style: styles.style,
+ id: vertex.id,
+ width: vertex.type === 'group' ? 500 : undefined,
+ type: vertex.type,
+ padding: getConfig().flowchart.padding
+ });
+ });
+};
+
+/**
+ * Add edges to graph based on parsed graph defninition
+ * @param {Object} edges The edges to add to the graph
+ * @param {Object} g The graph object
+ */
+export const addRelations = function(relations, g) {
+ let cnt = 0;
+
+ let defaultStyle;
+ let defaultLabelStyle;
+
+ // if (typeof relations.defaultStyle !== 'undefined') {
+ // const defaultStyles = getStylesFromArray(relations.defaultStyle);
+ // defaultStyle = defaultStyles.style;
+ // defaultLabelStyle = defaultStyles.labelStyle;
+ // }
+
+ relations.forEach(function(edge) {
+ cnt++;
+ const edgeData = {};
+ //Set relationship style and line type
+ edgeData.classes = 'relation';
+ edgeData.pattern = edge.relation.lineType == 1 ? 'dashed' : 'solid';
+
+ edgeData.id = 'id' + cnt;
+ // Set link type for rendering
+ if (edge.type === 'arrow_open') {
+ edgeData.arrowhead = 'none';
+ } else {
+ edgeData.arrowhead = 'normal';
+ }
+
+ logger.info(edgeData, edge);
+ //Set relation arrow types
+ edgeData.arrowTypeStart = getArrowMarker(edge.relation.type1);
+ edgeData.arrowTypeEnd = getArrowMarker(edge.relation.type2);
+ let style = '';
+ let labelStyle = '';
+
+ if (typeof edge.style !== 'undefined') {
+ const styles = getStylesFromArray(edge.style);
+ style = styles.style;
+ labelStyle = styles.labelStyle;
+ } else {
+ style = 'fill:none';
+ if (typeof defaultStyle !== 'undefined') {
+ style = defaultStyle;
+ }
+ if (typeof defaultLabelStyle !== 'undefined') {
+ labelStyle = defaultLabelStyle;
+ }
+ }
+
+ edgeData.style = style;
+ edgeData.labelStyle = labelStyle;
+
+ if (typeof edge.interpolate !== 'undefined') {
+ edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
+ } else if (typeof relations.defaultInterpolate !== 'undefined') {
+ edgeData.curve = interpolateToCurve(relations.defaultInterpolate, curveLinear);
+ } else {
+ edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
+ }
+
+ edge.text = edge.title;
+ if (typeof edge.text === 'undefined') {
+ if (typeof edge.style !== 'undefined') {
+ edgeData.arrowheadStyle = 'fill: #333';
+ }
+ } else {
+ edgeData.arrowheadStyle = 'fill: #333';
+ edgeData.labelpos = 'c';
+
+ if (getConfig().flowchart.htmlLabels && false) { // eslint-disable-line
+ edgeData.labelType = 'html';
+ edgeData.label = '' + edge.text + '';
+ } else {
+ edgeData.labelType = 'text';
+ edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
+
+ if (typeof edge.style === 'undefined') {
+ edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
+ }
+
+ edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
+ }
+ }
+ // Add the edge to the graph
+ g.setEdge(edge.id1, edge.id2, edgeData, cnt);
+ });
+};
+
+// Todo optimize
+const getGraphId = function(label) {
+ const keys = Object.keys(idCache);
+
+ for (let i = 0; i < keys.length; i++) {
+ if (idCache[keys[i]].label === label) {
+ return keys[i];
+ }
+ }
+
+ return undefined;
+};
+
+export const setConf = function(cnf) {
+ const keys = Object.keys(cnf);
+
+ keys.forEach(function(key) {
+ conf[key] = cnf[key];
+ });
+};
+
+/**
+ * Draws a flowchart in the tag with id: id based on the graph definition in text.
+ * @param text
+ * @param id
+ */
+export const drawOld = function(text, id) {
+ idCache = {};
+ parser.yy.clear();
+ parser.parse(text);
+
+ logger.info('Rendering diagram ' + text);
+
+ // Fetch the default direction, use TD if none was found
+ const diagram = select(`[id='${id}']`);
+ // insertMarkers(diagram);
+
+ // Layout graph, Create a new directed graph
+ const g = new graphlib.Graph({
+ multigraph: true
+ });
+
+ // Set an object for the graph label
+ g.setGraph({
+ isMultiGraph: true
+ });
+
+ // Default to assigning a new object as a label for each new edge.
+ g.setDefaultEdgeLabel(function() {
+ return {};
+ });
+
+ const classes = classDb.getClasses();
+ logger.info('classes:');
+ logger.info(classes);
+ const keys = Object.keys(classes);
+ for (let i = 0; i < keys.length; i++) {
+ const classDef = classes[keys[i]];
+ const node = svgDraw.drawClass(diagram, classDef, conf);
+ idCache[node.id] = node;
+
+ // Add nodes to the graph. The first argument is the node id. The second is
+ // metadata about the node. In this case we're going to add labels to each of
+ // our nodes.
+ g.setNode(node.id, node);
+
+ logger.info('Org height: ' + node.height);
+ }
+
+ const relations = classDb.getRelations();
+ logger.info('relations:', relations);
+ relations.forEach(function(relation) {
+ logger.info(
+ 'tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation)
+ );
+ g.setEdge(
+ getGraphId(relation.id1),
+ getGraphId(relation.id2),
+ {
+ relation: relation
+ },
+ relation.title || 'DEFAULT'
+ );
+ });
+
+ dagre.layout(g);
+ g.nodes().forEach(function(v) {
+ if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') {
+ logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)));
+ select('#' + lookUpDomId(v)).attr(
+ 'transform',
+ 'translate(' +
+ (g.node(v).x - g.node(v).width / 2) +
+ ',' +
+ (g.node(v).y - g.node(v).height / 2) +
+ ' )'
+ );
+ }
+ });
+
+ g.edges().forEach(function(e) {
+ if (typeof e !== 'undefined' && typeof g.edge(e) !== 'undefined') {
+ logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e)));
+ svgDraw.drawEdge(diagram, g.edge(e), g.edge(e).relation, conf);
+ }
+ });
+
+ const svgBounds = diagram.node().getBBox();
+ const width = svgBounds.width + padding * 2;
+ const height = svgBounds.height + padding * 2;
+
+ if (conf.useMaxWidth) {
+ diagram.attr('width', '100%');
+ diagram.attr('style', `max-width: ${width}px;`);
+ } else {
+ diagram.attr('height', height);
+ diagram.attr('width', width);
+ }
+
+ // Ensure the viewBox includes the whole svgBounds area with extra space for padding
+ const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`;
+ logger.debug(`viewBox ${vBox}`);
+ diagram.attr('viewBox', vBox);
+};
+
+export const draw = function(text, id) {
+ logger.info('Drawing class');
+ classDb.clear();
+ // const parser = classDb.parser;
+ // parser.yy = classDb;
+
+ // Parse the graph definition
+ // try {
+ parser.parse(text);
+ // } catch (err) {
+ // logger.debug('Parsing failed');
+ // }
+
+ // Fetch the default direction, use TD if none was found
+ let dir = 'TD';
+
+ const conf = getConfig().flowchart;
+ logger.info('config:', conf);
+ const nodeSpacing = conf.nodeSpacing || 50;
+ const rankSpacing = conf.rankSpacing || 50;
+
+ // Create the input mermaid.graph
+ const g = new graphlib.Graph({
+ multigraph: true,
+ compound: true
+ })
+ .setGraph({
+ rankdir: dir,
+ nodesep: nodeSpacing,
+ ranksep: rankSpacing,
+ marginx: 8,
+ marginy: 8
+ })
+ .setDefaultEdgeLabel(function() {
+ return {};
+ });
+
+ // let subG;
+ // const subGraphs = flowDb.getSubGraphs();
+ // logger.info('Subgraphs - ', subGraphs);
+ // for (let i = subGraphs.length - 1; i >= 0; i--) {
+ // subG = subGraphs[i];
+ // logger.info('Subgraph - ', subG);
+ // flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
+ // }
+
+ // Fetch the verices/nodes and edges/links from the parsed graph definition
+ const classes = classDb.getClasses();
+ const relations = classDb.getRelations();
+
+ logger.info(relations);
+ // let i = 0;
+ // for (i = subGraphs.length - 1; i >= 0; i--) {
+ // subG = subGraphs[i];
+
+ // selectAll('cluster').append('text');
+
+ // for (let j = 0; j < subG.nodes.length; j++) {
+ // g.setParent(subG.nodes[j], subG.id);
+ // }
+ // }
+ addClasses(classes, g, id);
+ addRelations(relations, g);
+
+ // Add custom shapes
+ // flowChartShapes.addToRenderV2(addShape);
+
+ // Set up an SVG group so that we can translate the final graph.
+ const svg = select(`[id="${id}"]`);
+
+ // Run the renderer. This is what draws the final graph.
+ const element = select('#' + id + ' g');
+ render(element, g, ['aggregation', 'extension', 'composition', 'dependency'], 'classDiagram', id);
+
+ // element.selectAll('g.node').attr('title', function() {
+ // return flowDb.getTooltip(this.id);
+ // });
+
+ const padding = 8;
+ const svgBounds = svg.node().getBBox();
+ const width = svgBounds.width + padding * 2;
+ const height = svgBounds.height + padding * 2;
+ logger.debug(
+ `new ViewBox 0 0 ${width} ${height}`,
+ `translate(${padding - g._label.marginx}, ${padding - g._label.marginy})`
+ );
+
+ if (conf.useMaxWidth) {
+ svg.attr('width', '100%');
+ svg.attr('style', `max-width: ${width}px;`);
+ } else {
+ svg.attr('height', height);
+ svg.attr('width', width);
+ }
+
+ svg.attr('viewBox', `0 0 ${width} ${height}`);
+ svg
+ .select('g')
+ .attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`);
+
+ // Index nodes
+ // flowDb.indexNodes('subGraph' + i);
+
+ // Add label rects for non html labels
+ if (!conf.htmlLabels) {
+ const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
+ for (let k = 0; k < labels.length; k++) {
+ const label = labels[k];
+
+ // Get dimensions of label
+ const dim = label.getBBox();
+
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ rect.setAttribute('rx', 0);
+ rect.setAttribute('ry', 0);
+ rect.setAttribute('width', dim.width);
+ rect.setAttribute('height', dim.height);
+ rect.setAttribute('style', 'fill:#e8e8e8;');
+
+ label.insertBefore(rect, label.firstChild);
+ }
+ }
+
+ // If node has a link, wrap it in an anchor SVG object.
+ // const keys = Object.keys(classes);
+ // keys.forEach(function(key) {
+ // const vertex = classes[key];
+
+ // if (vertex.link) {
+ // const node = select('#' + id + ' [id="' + key + '"]');
+ // if (node) {
+ // const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
+ // link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
+ // link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
+ // link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
+
+ // const linkNode = node.insert(function() {
+ // return link;
+ // }, ':first-child');
+
+ // const shape = node.select('.label-container');
+ // if (shape) {
+ // linkNode.append(function() {
+ // return shape.node();
+ // });
+ // }
+
+ // const label = node.select('.label');
+ // if (label) {
+ // linkNode.append(function() {
+ // return label.node();
+ // });
+ // }
+ // }
+ // }
+ // });
+};
+
+export default {
+ setConf,
+ draw
+};
+function getArrowMarker(type) {
+ let marker;
+ switch (type) {
+ case 0:
+ marker = 'aggregation';
+ break;
+ case 1:
+ marker = 'extension';
+ break;
+ case 2:
+ marker = 'composition';
+ break;
+ case 3:
+ marker = 'dependency';
+ break;
+ default:
+ marker = 'none';
+ }
+ return marker;
+}
diff --git a/src/diagrams/class/parser/classDiagram.jison b/src/diagrams/class/parser/classDiagram.jison
index bd8cf6f59..f53de9c0f 100644
--- a/src/diagrams/class/parser/classDiagram.jison
+++ b/src/diagrams/class/parser/classDiagram.jison
@@ -12,6 +12,7 @@
\%\%[^\n]*\n* /* do nothing */
\n+ return 'NEWLINE';
\s+ /* skip whitespace */
+"classDiagram-v2" return 'CLASS_DIAGRAM';
"classDiagram" return 'CLASS_DIAGRAM';
[\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';}
<> return "EOF_IN_STRUCT";
diff --git a/src/diagrams/class/styles.js b/src/diagrams/class/styles.js
index bf237241e..781f3cd9d 100644
--- a/src/diagrams/class/styles.js
+++ b/src/diagrams/class/styles.js
@@ -10,6 +10,21 @@ const getStyles = options =>
font-weight: bolder;
}
}
+.node rect,
+ .node circle,
+ .node ellipse,
+ .node polygon,
+ .node path {
+ fill: ${options.mainBkg};
+ stroke: ${options.nodeBorder};
+ stroke-width: 1px;
+ }
+
+
+.divider {
+ stroke: ${options.nodeBorder};
+ stroke: 1;
+}
g.clickable {
cursor: pointer;
@@ -47,15 +62,51 @@ g.classGroup line {
stroke-dasharray: 3;
}
-#compositionStart, #compositionEnd, #dependencyStart, #dependencyEnd, #extensionStart, #extensionEnd {
- fill: ${options.lineColor};
- stroke: ${options.lineColor};
+#compositionStart, .composition {
+ fill: ${options.lineColor} !important;
+ stroke: ${options.lineColor} !important;
stroke-width: 1;
}
-#aggregationStart, #aggregationEnd {
- fill: ${options.nodeBkg};
- stroke: ${options.lineColor};
+#compositionEnd, .composition {
+ fill: ${options.lineColor} !important;
+ stroke: ${options.lineColor} !important;
+ stroke-width: 1;
+}
+
+#dependencyStart, .dependency {
+ fill: ${options.lineColor} !important;
+ stroke: ${options.lineColor} !important;
+ stroke-width: 1;
+}
+
+#dependencyStart, .dependency {
+ fill: ${options.lineColor} !important;
+ stroke: ${options.lineColor} !important;
+ stroke-width: 1;
+}
+
+#extensionStart, .extension {
+ fill: ${options.lineColor} !important;
+ stroke: ${options.lineColor} !important;
+ stroke-width: 1;
+}
+
+#extensionEnd, .extension {
+ fill: ${options.lineColor} !important;
+ stroke: ${options.lineColor} !important;
+ stroke-width: 1;
+}
+
+#aggregationStart, .aggregation {
+ fill: ${options.nodeBkg} !important;
+ stroke: ${options.lineColor} !important;
+ stroke-width: 1;
+}
+
+#aggregationEnd, .aggregation {
+ fill: ${options.nodeBkg} !important;
+ stroke: ${options.lineColor} !important;
stroke-width: 1;
}
`;
diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js
index b03991ec9..1bd2db9de 100644
--- a/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/src/diagrams/flowchart/flowRenderer-v2.js
@@ -197,8 +197,34 @@ export const addEdges = function(edges, g) {
edgeData.arrowhead = 'normal';
}
- logger.info(edgeData, edge);
- edgeData.arrowType = edge.type;
+ // Check of arrow types, placed here in order not to break old rendering
+ edgeData.arrowTypeStart = 'arrow_open';
+ edgeData.arrowTypeEnd = 'arrow_open';
+
+ /* eslint-disable no-fallthrough */
+ switch (edge.type) {
+ case 'double_arrow_cross':
+ edgeData.arrowTypeStart = 'arrow_cross';
+ case 'arrow_cross':
+ edgeData.arrowTypeEnd = 'arrow_cross';
+ break;
+ case 'double_arrow_point':
+ edgeData.arrowTypeStart = 'arrow_point';
+ case 'arrow_point':
+ edgeData.arrowTypeEnd = 'arrow_point';
+ break;
+ case 'double_arrow_circle':
+ edgeData.arrowTypeStart = 'arrow_circle';
+ case 'arrow_circle':
+ edgeData.arrowTypeEnd = 'arrow_circle';
+ break;
+ }
+
+ // logger.info('apa', edgeData, edge);
+ // edgeData.arrowTypeStart = edge.arrowTypeStart;
+ // edgeData.arrowTypeStart = edge.arrowTypeStart;
+ // edgeData.arrowType = edgeData.arrowTypeEnd;
+ // logger.info('apa', edgeData, edge);
let style = '';
let labelStyle = '';
diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js
index 9d806d6a1..073654bc8 100644
--- a/src/diagrams/state/stateRenderer-v2.js
+++ b/src/diagrams/state/stateRenderer-v2.js
@@ -179,7 +179,7 @@ const setupDoc = (g, parent, doc, altFlag) => {
const edgeData = {
id: 'edge' + cnt,
arrowhead: 'normal',
- arrowType: 'arrow_barb',
+ arrowTypeEnd: 'arrow_barb',
style: 'fill:none',
labelStyle: '',
label: item.description,
diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js
index 1b2e91da2..0f37186a5 100644
--- a/src/mermaidAPI.js
+++ b/src/mermaidAPI.js
@@ -27,6 +27,7 @@ import ganttRenderer from './diagrams/gantt/ganttRenderer';
import ganttParser from './diagrams/gantt/parser/gantt';
import ganttDb from './diagrams/gantt/ganttDb';
import classRenderer from './diagrams/class/classRenderer';
+import classRendererV2 from './diagrams/class/classRenderer-v2';
import classParser from './diagrams/class/parser/classDiagram';
import classDb from './diagrams/class/classDb';
import stateRenderer from './diagrams/state/stateRenderer';
@@ -94,6 +95,10 @@ function parse(text) {
parser = classParser;
parser.parser.yy = classDb;
break;
+ case 'classDiagram':
+ parser = classParser;
+ parser.parser.yy = classDb;
+ break;
case 'state':
parser = stateParser;
parser.parser.yy = stateDb;
@@ -342,6 +347,11 @@ const render = function(id, _txt, cb, container) {
classRenderer.setConf(cnf.class);
classRenderer.draw(txt, id);
break;
+ case 'classDiagram':
+ cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
+ classRendererV2.setConf(cnf.class);
+ classRendererV2.draw(txt, id);
+ break;
case 'state':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateRenderer.setConf(cnf.state);
diff --git a/src/styles.js b/src/styles.js
index 810760bdb..06769dde6 100644
--- a/src/styles.js
+++ b/src/styles.js
@@ -14,7 +14,8 @@ const themes = {
'flowchart-v2': flowchart,
sequence,
gantt,
- class: classDiagram,
+ classDiagram,
+ 'classDiagram-v2': classDiagram,
stateDiagram,
state: stateDiagram,
git,
diff --git a/src/themes/class.scss b/src/themes/class.scss
index db9a3606b..d29e8fbf3 100644
--- a/src/themes/class.scss
+++ b/src/themes/class.scss
@@ -10,6 +10,11 @@ g.classGroup text {
}
}
+.divider {
+ stroke: $nodeBorder;
+ stroke-width: 1;
+}
+
g.clickable {
cursor: pointer;
}
@@ -47,45 +52,45 @@ g.classGroup line {
}
@mixin composition {
- fill: $nodeBorder;
- stroke: $nodeBorder;
+ fill: $nodeBorder !important ;
+ stroke: $nodeBorder !important ;
stroke-width: 1;
}
-#compositionStart {
+#compositionStart, .composition {
@include composition;
}
-#compositionEnd {
+#compositionEnd, .composition {
@include composition;
}
@mixin aggregation {
- fill: $nodeBkg;
- stroke: $nodeBorder;
+ fill: $nodeBkg !important ;
+ stroke: $nodeBorder !important ;
stroke-width: 1;
}
-#aggregationStart {
+#aggregationStart, .aggregation {
@include aggregation;
}
-#aggregationEnd {
+#aggregationEnd, .aggregation {
@include aggregation;
}
-#dependencyStart {
+#dependencyStart, .dependency {
@include composition;
}
-#dependencyEnd {
+#dependencyEnd, .dependency {
@include composition;
}
-#extensionStart {
+#extensionStart , .extension{
@include composition;
}
-#extensionEnd {
+#extensionEnd, .extension {
@include composition;
}
diff --git a/src/utils.js b/src/utils.js
index 42342d2c0..8fe374b00 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -184,10 +184,13 @@ export const detectType = function(text) {
if (text.match(/^\s*gantt/)) {
return 'gantt';
}
-
+ if (text.match(/^\s*classDiagram-v2/)) {
+ return 'classDiagram';
+ }
if (text.match(/^\s*classDiagram/)) {
return 'class';
}
+
if (text.match(/^\s*stateDiagram-v2/)) {
return 'stateDiagram';
}