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