Merge pull request #1567 from mermaid-js/feature/Issue-1465_Class_migration

Feature/issue 1465 class migration
This commit is contained in:
Knut Sveidqvist
2020-07-22 21:35:01 +02:00
committed by GitHub
15 changed files with 989 additions and 63 deletions

View File

@@ -0,0 +1,66 @@
<html>
<head>
<link
href="https://fonts.googleapis.com/css?family=Montserrat&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap" rel="stylesheet">
<style>
body {
background: rgb(221, 208, 208);
/*background:#333;*/
font-family: 'Arial';
}
h1 { color: white;}
.mermaid2 {
display: none;
}
</style>
</head>
<body>
<h1>info below</h1>
<div class="mermaid" style="width: 100%; height: 20%;">
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()
&lt;&lt;interface&gt;&gt; classB
classB : method2() int
</div>
<script src="./mermaid.js"></script>
<script>
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'default',
// arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 0,
flowchart: { curve: 'linear', "htmlLabels": false },
// gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50, showSequenceNumbers: true },
// sequenceDiagram: { actorMargin: 300 } // deprecated
fontFamily: '"arial", sans-serif',
curve: 'linear',
securityLevel: 'loose'
});
function callback(){alert('It worked');}
</script>
</body>
</html>

View File

@@ -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 | | id | id of the shape |
| type | if set to group then this node indicates *a cluster*. | | 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. | | padding | Padding. Passed from the render as this might differ between different diagrams. Maybe obsolete. |
| data | Non-generic data specific to the shape. |
# edge # edge

View File

@@ -297,35 +297,60 @@ export const insertEdge = function(elem, e, edge, clusterDb, diagramType, graph)
url = url.replace(/\(/g, '\\('); url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)'); url = url.replace(/\)/g, '\\)');
} }
logger.info('arrowType', edge.arrowType); logger.info('arrowTypeStart', edge.arrowTypeStart);
switch (edge.arrowType) { 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': case 'arrow_cross':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
break; 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': case 'arrow_point':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
break; 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': case 'arrow_barb':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
break; 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': case 'arrow_circle':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
break; break;
case 'double_arrow_circle': case 'aggregation':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')');
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')'); 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; break;
default: default:
} }

View File

@@ -20,12 +20,12 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line const elem = _elem.insert('g').attr('class', 'root'); // eslint-disable-line
if (!graph.nodes()) { if (!graph.nodes()) {
log.trace('No nodes found for', graph); log.info('No nodes found for', graph);
} else { } else {
log.trace('Recursive render', graph.nodes()); log.info('Recursive render', graph.nodes());
} }
if (graph.edges().length > 0) { 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 clusters = elem.insert('g').attr('class', 'clusters'); // eslint-disable-line
const edgePaths = elem.insert('g').attr('class', 'edgePaths'); const edgePaths = elem.insert('g').attr('class', 'edgePaths');
@@ -39,14 +39,14 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
if (typeof parentCluster !== 'undefined') { if (typeof parentCluster !== 'undefined') {
const data = JSON.parse(JSON.stringify(parentCluster.clusterData)); const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
// data.clusterPositioning = true; // data.clusterPositioning = true;
log.trace('Setting data for cluster', data); log.info('Setting data for cluster', data);
graph.setNode(parentCluster.id, data); graph.setNode(parentCluster.id, data);
graph.setParent(v, 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) { if (node && node.clusterNode) {
// const children = graph.children(v); // 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)); const newEl = recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
updateNodeBounds(node, newEl); updateNodeBounds(node, newEl);
setNodeElem(newEl, node); setNodeElem(newEl, node);
@@ -56,12 +56,12 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
if (graph.children(v).length > 0) { if (graph.children(v).length > 0) {
// This is a cluster but not to be rendered recusively // This is a cluster but not to be rendered recusively
// Render as before // Render as before
log.trace('Cluster - the non recursive path', v, node.id, node, graph); log.info('Cluster - the non recursive path', v, node.id, node, graph);
log.trace(findNonClusterChild(node.id, graph)); log.info(findNonClusterChild(node.id, graph));
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node }; clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
// insertCluster(clusters, graph.node(v)); // insertCluster(clusters, graph.node(v));
} else { } 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); 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 // TODO: pick optimal child in the cluster to us as link anchor
graph.edges().forEach(function(e) { graph.edges().forEach(function(e) {
const edge = graph.edge(e.v, e.w, e.name); const edge = graph.edge(e.v, e.w, e.name);
log.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); log.info('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 + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// Check if link is either from or to a cluster // 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); insertEdgeLabel(edgeLabels, edge);
}); });
@@ -89,11 +89,11 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
log.info('#############################################'); log.info('#############################################');
log.info(graph); log.info(graph);
dagre.layout(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 // Move the nodes to the correct place
graph.nodes().forEach(function(v) { graph.nodes().forEach(function(v) {
const node = graph.node(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( log.info(
'Position ' + v + ': (' + node.x, 'Position ' + v + ': (' + node.x,
',' + node.y, ',' + node.y,

View File

@@ -29,7 +29,7 @@ const extension = (elem, type, id) => {
elem elem
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-extensionEnd ' + type) .attr('id', type + '-extensionEnd')
.attr('class', 'marker extension ' + type) .attr('class', 'marker extension ' + type)
.attr('refX', 19) .attr('refX', 19)
.attr('refY', 7) .attr('refY', 7)
@@ -45,7 +45,7 @@ const composition = (elem, type) => {
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-compositionStart') .attr('id', type + '-compositionStart')
.attr('class', 'marker extension ' + type) .attr('class', 'marker composition ' + type)
.attr('refX', 0) .attr('refX', 0)
.attr('refY', 7) .attr('refY', 7)
.attr('markerWidth', 190) .attr('markerWidth', 190)
@@ -58,7 +58,7 @@ const composition = (elem, type) => {
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-compositionEnd') .attr('id', type + '-compositionEnd')
.attr('class', 'marker extension ' + type) .attr('class', 'marker composition ' + type)
.attr('refX', 19) .attr('refX', 19)
.attr('refY', 7) .attr('refY', 7)
.attr('markerWidth', 20) .attr('markerWidth', 20)
@@ -72,7 +72,7 @@ const aggregation = (elem, type) => {
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-aggregationStart') .attr('id', type + '-aggregationStart')
.attr('class', 'marker extension ' + type) .attr('class', 'marker aggregation ' + type)
.attr('refX', 0) .attr('refX', 0)
.attr('refY', 7) .attr('refY', 7)
.attr('markerWidth', 190) .attr('markerWidth', 190)
@@ -85,7 +85,7 @@ const aggregation = (elem, type) => {
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-aggregationEnd') .attr('id', type + '-aggregationEnd')
.attr('class', 'marker ' + type) .attr('class', 'marker aggregation ' + type)
.attr('refX', 19) .attr('refX', 19)
.attr('refY', 7) .attr('refY', 7)
.attr('markerWidth', 20) .attr('markerWidth', 20)
@@ -99,7 +99,7 @@ const dependency = (elem, type) => {
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-dependencyStart') .attr('id', type + '-dependencyStart')
.attr('class', 'marker extension ' + type) .attr('class', 'marker dependency ' + type)
.attr('refX', 0) .attr('refX', 0)
.attr('refY', 7) .attr('refY', 7)
.attr('markerWidth', 190) .attr('markerWidth', 190)
@@ -112,7 +112,7 @@ const dependency = (elem, type) => {
.append('defs') .append('defs')
.append('marker') .append('marker')
.attr('id', type + '-dependencyEnd') .attr('id', type + '-dependencyEnd')
.attr('class', 'marker ' + type) .attr('class', 'marker dependency ' + type)
.attr('refX', 19) .attr('refX', 19)
.attr('refY', 7) .attr('refY', 7)
.attr('markerWidth', 20) .attr('markerWidth', 20)

View File

@@ -1,8 +1,8 @@
import intersect from './intersect/index.js';
import { select } from 'd3'; import { select } from 'd3';
import { logger } from '../logger'; // eslint-disable-line import { logger } from '../logger'; // eslint-disable-line
import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util'; import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util';
import { getConfig } from '../config'; import { getConfig } from '../config';
import intersect from './intersect/index.js';
import createLabel from './createLabel'; import createLabel from './createLabel';
import note from './shapes/note'; import note from './shapes/note';
@@ -539,6 +539,219 @@ const end = (parent, node) => {
return shapeSvg; 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('<br/>'), 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 = { const shapes = {
question, question,
rect, rect,
@@ -558,7 +771,8 @@ const shapes = {
note, note,
subroutine, subroutine,
fork: forkJoin, fork: forkJoin,
join: forkJoin join: forkJoin,
class_box
}; };
let nodeElems = {}; let nodeElems = {};

View File

@@ -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 => `<i class='${s.replace(':', ' ')}'></i>`
// )
// };
// 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 = '<span class="edgeLabel">' + edge.text + '</span>';
} 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;
}

View File

@@ -12,6 +12,7 @@
\%\%[^\n]*\n* /* do nothing */ \%\%[^\n]*\n* /* do nothing */
\n+ return 'NEWLINE'; \n+ return 'NEWLINE';
\s+ /* skip whitespace */ \s+ /* skip whitespace */
"classDiagram-v2" return 'CLASS_DIAGRAM';
"classDiagram" return 'CLASS_DIAGRAM'; "classDiagram" return 'CLASS_DIAGRAM';
[\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';} [\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';}
<struct><<EOF>> return "EOF_IN_STRUCT"; <struct><<EOF>> return "EOF_IN_STRUCT";

View File

@@ -10,6 +10,21 @@ const getStyles = options =>
font-weight: bolder; 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 { g.clickable {
cursor: pointer; cursor: pointer;
@@ -47,15 +62,51 @@ g.classGroup line {
stroke-dasharray: 3; stroke-dasharray: 3;
} }
#compositionStart, #compositionEnd, #dependencyStart, #dependencyEnd, #extensionStart, #extensionEnd { #compositionStart, .composition {
fill: ${options.lineColor}; fill: ${options.lineColor} !important;
stroke: ${options.lineColor}; stroke: ${options.lineColor} !important;
stroke-width: 1; stroke-width: 1;
} }
#aggregationStart, #aggregationEnd { #compositionEnd, .composition {
fill: ${options.nodeBkg}; fill: ${options.lineColor} !important;
stroke: ${options.lineColor}; 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; stroke-width: 1;
} }
`; `;

View File

@@ -197,8 +197,34 @@ export const addEdges = function(edges, g) {
edgeData.arrowhead = 'normal'; edgeData.arrowhead = 'normal';
} }
logger.info(edgeData, edge); // Check of arrow types, placed here in order not to break old rendering
edgeData.arrowType = edge.type; 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 style = '';
let labelStyle = ''; let labelStyle = '';

View File

@@ -179,7 +179,7 @@ const setupDoc = (g, parent, doc, altFlag) => {
const edgeData = { const edgeData = {
id: 'edge' + cnt, id: 'edge' + cnt,
arrowhead: 'normal', arrowhead: 'normal',
arrowType: 'arrow_barb', arrowTypeEnd: 'arrow_barb',
style: 'fill:none', style: 'fill:none',
labelStyle: '', labelStyle: '',
label: item.description, label: item.description,

View File

@@ -27,6 +27,7 @@ import ganttRenderer from './diagrams/gantt/ganttRenderer';
import ganttParser from './diagrams/gantt/parser/gantt'; import ganttParser from './diagrams/gantt/parser/gantt';
import ganttDb from './diagrams/gantt/ganttDb'; import ganttDb from './diagrams/gantt/ganttDb';
import classRenderer from './diagrams/class/classRenderer'; import classRenderer from './diagrams/class/classRenderer';
import classRendererV2 from './diagrams/class/classRenderer-v2';
import classParser from './diagrams/class/parser/classDiagram'; import classParser from './diagrams/class/parser/classDiagram';
import classDb from './diagrams/class/classDb'; import classDb from './diagrams/class/classDb';
import stateRenderer from './diagrams/state/stateRenderer'; import stateRenderer from './diagrams/state/stateRenderer';
@@ -94,6 +95,10 @@ function parse(text) {
parser = classParser; parser = classParser;
parser.parser.yy = classDb; parser.parser.yy = classDb;
break; break;
case 'classDiagram':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'state': case 'state':
parser = stateParser; parser = stateParser;
parser.parser.yy = stateDb; parser.parser.yy = stateDb;
@@ -342,6 +347,11 @@ const render = function(id, _txt, cb, container) {
classRenderer.setConf(cnf.class); classRenderer.setConf(cnf.class);
classRenderer.draw(txt, id); classRenderer.draw(txt, id);
break; break;
case 'classDiagram':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classRendererV2.setConf(cnf.class);
classRendererV2.draw(txt, id);
break;
case 'state': case 'state':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateRenderer.setConf(cnf.state); stateRenderer.setConf(cnf.state);

View File

@@ -14,7 +14,8 @@ const themes = {
'flowchart-v2': flowchart, 'flowchart-v2': flowchart,
sequence, sequence,
gantt, gantt,
class: classDiagram, classDiagram,
'classDiagram-v2': classDiagram,
stateDiagram, stateDiagram,
state: stateDiagram, state: stateDiagram,
git, git,

View File

@@ -10,6 +10,11 @@ g.classGroup text {
} }
} }
.divider {
stroke: $nodeBorder;
stroke-width: 1;
}
g.clickable { g.clickable {
cursor: pointer; cursor: pointer;
} }
@@ -47,45 +52,45 @@ g.classGroup line {
} }
@mixin composition { @mixin composition {
fill: $nodeBorder; fill: $nodeBorder !important ;
stroke: $nodeBorder; stroke: $nodeBorder !important ;
stroke-width: 1; stroke-width: 1;
} }
#compositionStart { #compositionStart, .composition {
@include composition; @include composition;
} }
#compositionEnd { #compositionEnd, .composition {
@include composition; @include composition;
} }
@mixin aggregation { @mixin aggregation {
fill: $nodeBkg; fill: $nodeBkg !important ;
stroke: $nodeBorder; stroke: $nodeBorder !important ;
stroke-width: 1; stroke-width: 1;
} }
#aggregationStart { #aggregationStart, .aggregation {
@include aggregation; @include aggregation;
} }
#aggregationEnd { #aggregationEnd, .aggregation {
@include aggregation; @include aggregation;
} }
#dependencyStart { #dependencyStart, .dependency {
@include composition; @include composition;
} }
#dependencyEnd { #dependencyEnd, .dependency {
@include composition; @include composition;
} }
#extensionStart { #extensionStart , .extension{
@include composition; @include composition;
} }
#extensionEnd { #extensionEnd, .extension {
@include composition; @include composition;
} }

View File

@@ -184,10 +184,13 @@ export const detectType = function(text) {
if (text.match(/^\s*gantt/)) { if (text.match(/^\s*gantt/)) {
return 'gantt'; return 'gantt';
} }
if (text.match(/^\s*classDiagram-v2/)) {
return 'classDiagram';
}
if (text.match(/^\s*classDiagram/)) { if (text.match(/^\s*classDiagram/)) {
return 'class'; return 'class';
} }
if (text.match(/^\s*stateDiagram-v2/)) { if (text.match(/^\s*stateDiagram-v2/)) {
return 'stateDiagram'; return 'stateDiagram';
} }