diff --git a/cypress/platform/class.html b/cypress/platform/class.html
index 7ed5f5cac..8118ca474 100644
--- a/cypress/platform/class.html
+++ b/cypress/platform/class.html
@@ -30,9 +30,10 @@
classK ..> classL : Dependency
classM ..|> classN : Realization
classO .. classP : Link(Dashed)
- classA : +attr1
+ classA : +attr1
classA : attr2
classA : method1()
+ <<interface>> classB
classB : method2() int
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/nodes.js b/src/dagre-wrapper/nodes.js
index 624dc5272..8af32528e 100644
--- a/src/dagre-wrapper/nodes.js
+++ b/src/dagre-wrapper/nodes.js
@@ -1,10 +1,11 @@
-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';
+import intersectRect from './intersect/intersect-rect';
const question = (parent, node) => {
const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
@@ -539,6 +540,214 @@ 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
+ select(classTitleLabel).attr(
+ 'transform',
+ 'translate( ' + -maxWidth + node.padding / 2 + ', ' + ((-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 +767,8 @@ const shapes = {
note,
subroutine,
fork: forkJoin,
- join: forkJoin
+ join: forkJoin,
+ class_box
};
let nodeElems = {};
diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js
index 79dca1d1f..7c609fe0f 100644
--- a/src/diagrams/class/classDb.js
+++ b/src/diagrams/class/classDb.js
@@ -1,3 +1,4 @@
+
import { select } from 'd3';
import { logger } from '../../logger';
import { getConfig } from '../../config';
diff --git a/src/diagrams/class/classRenderer-v2.js b/src/diagrams/class/classRenderer-v2.js
index 7e5176d35..aa37de74c 100644
--- a/src/diagrams/class/classRenderer-v2.js
+++ b/src/diagrams/class/classRenderer-v2.js
@@ -85,16 +85,17 @@ export const addClasses = function(classes, g) {
// Set the shape based parameters
switch (vertex.type) {
case 'class':
- _shape = 'rect';
+ _shape = 'class_box';
break;
default:
- _shape = 'rect';
+ _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,
diff --git a/src/themes/class.scss b/src/themes/class.scss
index a5545d9b2..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;
}