diff --git a/cypress/platform/class.html b/cypress/platform/class.html
new file mode 100644
index 000000000..f443ed2c1
--- /dev/null
+++ b/cypress/platform/class.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+ info below
+
+ classDiagram-v2
+ 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)
+
+
+
+
+
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/diagrams/class/classRenderer-v2.js b/src/diagrams/class/classRenderer-v2.js
new file mode 100644
index 000000000..2f7dad20e
--- /dev/null
+++ b/src/diagrams/class/classRenderer-v2.js
@@ -0,0 +1,521 @@
+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);
+
+ // 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 = 'rect';
+ break;
+ default:
+ _shape = 'rect';
+ }
+ // Add the node
+ g.setNode(vertex.id, {
+ 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
+ });
+
+ 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.arrowStartType = getArrowMarker(edge.relation.type1);
+ edgeData.arrowEndType = 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, ['point', 'circle', 'cross'], '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/mermaidAPI.js b/src/mermaidAPI.js
index ea2c856a3..df0968571 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';
@@ -597,6 +598,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;
@@ -837,6 +842,11 @@ const render = function(id, _txt, cb, container) {
classRenderer.setConf(config.class);
classRenderer.draw(txt, id);
break;
+ case 'classDiagram':
+ config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
+ classRendererV2.setConf(config.class);
+ classRendererV2.draw(txt, id);
+ break;
case 'state':
// config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
stateRenderer.setConf(config.state);
diff --git a/src/utils.js b/src/utils.js
index ee3dc66fe..2faa59a54 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -169,10 +169,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';
}