diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 5169fce4b..2b31c326d 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -1,365 +1,456 @@ -import dagre from 'dagre-layout' -import graphlib from 'graphlibrary' -import * as d3 from 'd3' +import dagre from "dagre-layout"; +import graphlib from "graphlibrary"; +import * as d3 from "d3"; -import classDb from './classDb' -import { logger } from '../../logger' -import { parser } from './parser/classDiagram' +import classDb from "./classDb"; +import { logger } from "../../logger"; +import { parser } from "./parser/classDiagram"; -parser.yy = classDb +Array.prototype.removeIf = function(callback) { + var i = this.length; + while (i--) { + if (callback(this[i], i)) { + this.splice(i, 1); + } + } +}; -const idCache = {} +parser.yy = classDb; -let classCnt = 0 +const idCache = {}; + +let classCnt = 0; const conf = { dividerMargin: 10, padding: 5, textHeight: 10 -} +}; // Todo optimize -const getGraphId = function (label) { - const keys = Object.keys(idCache) +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 keys[i]; } } - return undefined -} + return undefined; +}; /** * Setup arrow head and define the marker. The result is appended to the svg. */ -const insertMarkers = function (elem) { - elem.append('defs').append('marker') - .attr('id', 'extensionStart') - .attr('class', 'extension') - .attr('refX', 0) - .attr('refY', 7) - .attr('markerWidth', 190) - .attr('markerHeight', 240) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 1,7 L18,13 V 1 Z') +const insertMarkers = function(elem) { + elem + .append("defs") + .append("marker") + .attr("id", "extensionStart") + .attr("class", "extension") + .attr("refX", 0) + .attr("refY", 7) + .attr("markerWidth", 190) + .attr("markerHeight", 240) + .attr("orient", "auto") + .append("path") + .attr("d", "M 1,7 L18,13 V 1 Z"); - elem.append('defs').append('marker') - .attr('id', 'extensionEnd') - .attr('refX', 19) - .attr('refY', 7) - .attr('markerWidth', 20) - .attr('markerHeight', 28) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 1,1 V 13 L18,7 Z') // this is actual shape for arrowhead + elem + .append("defs") + .append("marker") + .attr("id", "extensionEnd") + .attr("refX", 19) + .attr("refY", 7) + .attr("markerWidth", 20) + .attr("markerHeight", 28) + .attr("orient", "auto") + .append("path") + .attr("d", "M 1,1 V 13 L18,7 Z"); // this is actual shape for arrowhead - elem.append('defs').append('marker') - .attr('id', 'compositionStart') - .attr('class', 'extension') - .attr('refX', 0) - .attr('refY', 7) - .attr('markerWidth', 190) - .attr('markerHeight', 240) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') + elem + .append("defs") + .append("marker") + .attr("id", "compositionStart") + .attr("class", "extension") + .attr("refX", 0) + .attr("refY", 7) + .attr("markerWidth", 190) + .attr("markerHeight", 240) + .attr("orient", "auto") + .append("path") + .attr("d", "M 18,7 L9,13 L1,7 L9,1 Z"); - elem.append('defs').append('marker') - .attr('id', 'compositionEnd') - .attr('refX', 19) - .attr('refY', 7) - .attr('markerWidth', 20) - .attr('markerHeight', 28) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') + elem + .append("defs") + .append("marker") + .attr("id", "compositionEnd") + .attr("refX", 19) + .attr("refY", 7) + .attr("markerWidth", 20) + .attr("markerHeight", 28) + .attr("orient", "auto") + .append("path") + .attr("d", "M 18,7 L9,13 L1,7 L9,1 Z"); - elem.append('defs').append('marker') - .attr('id', 'aggregationStart') - .attr('class', 'extension') - .attr('refX', 0) - .attr('refY', 7) - .attr('markerWidth', 190) - .attr('markerHeight', 240) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') + elem + .append("defs") + .append("marker") + .attr("id", "aggregationStart") + .attr("class", "extension") + .attr("refX", 0) + .attr("refY", 7) + .attr("markerWidth", 190) + .attr("markerHeight", 240) + .attr("orient", "auto") + .append("path") + .attr("d", "M 18,7 L9,13 L1,7 L9,1 Z"); - elem.append('defs').append('marker') - .attr('id', 'aggregationEnd') - .attr('refX', 19) - .attr('refY', 7) - .attr('markerWidth', 20) - .attr('markerHeight', 28) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') + elem + .append("defs") + .append("marker") + .attr("id", "aggregationEnd") + .attr("refX", 19) + .attr("refY", 7) + .attr("markerWidth", 20) + .attr("markerHeight", 28) + .attr("orient", "auto") + .append("path") + .attr("d", "M 18,7 L9,13 L1,7 L9,1 Z"); - elem.append('defs').append('marker') - .attr('id', 'dependencyStart') - .attr('class', 'extension') - .attr('refX', 0) - .attr('refY', 7) - .attr('markerWidth', 190) - .attr('markerHeight', 240) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z') + elem + .append("defs") + .append("marker") + .attr("id", "dependencyStart") + .attr("class", "extension") + .attr("refX", 0) + .attr("refY", 7) + .attr("markerWidth", 190) + .attr("markerHeight", 240) + .attr("orient", "auto") + .append("path") + .attr("d", "M 5,7 L9,13 L1,7 L9,1 Z"); - elem.append('defs').append('marker') - .attr('id', 'dependencyEnd') - .attr('refX', 19) - .attr('refY', 7) - .attr('markerWidth', 20) - .attr('markerHeight', 28) - .attr('orient', 'auto') - .append('path') - .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z') -} + elem + .append("defs") + .append("marker") + .attr("id", "dependencyEnd") + .attr("refX", 19) + .attr("refY", 7) + .attr("markerWidth", 20) + .attr("markerHeight", 28) + .attr("orient", "auto") + .append("path") + .attr("d", "M 18,7 L9,13 L14,7 L9,1 Z"); +}; -let edgeCount = 0 -const drawEdge = function (elem, path, relation) { - const getRelationType = function (type) { +let edgeCount = 0; +let total = 0; +const drawEdge = function(elem, path, relation) { + const getRelationType = function(type) { switch (type) { case classDb.relationType.AGGREGATION: - return 'aggregation' + return "aggregation"; case classDb.relationType.EXTENSION: - return 'extension' + return "extension"; case classDb.relationType.COMPOSITION: - return 'composition' + return "composition"; case classDb.relationType.DEPENDENCY: - return 'dependency' + return "dependency"; } - } + }; + + path.points.removeIf(function(p, idx) { + return isNaN(p.y); + }); // The data for our line - const lineData = path.points + const lineData = path.points; // This is the accessor function we talked about above - const lineFunction = d3.line() - .x(function (d) { - return d.x + const lineFunction = d3 + .line() + .x(function(d) { + return d.x; }) - .y(function (d) { - return d.y + .y(function(d) { + return d.y; }) - .curve(d3.curveBasis) + .curve(d3.curveBasis); - const svgPath = elem.append('path') - .attr('d', lineFunction(lineData)) - .attr('id', 'edge' + edgeCount) - .attr('class', 'relation') - let url = '' + const svgPath = elem + .append("path") + .attr("d", lineFunction(lineData)) + .attr("id", "edge" + edgeCount) + .attr("class", "relation"); + let url = ""; if (conf.arrowMarkerAbsolute) { - url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search - url = url.replace(/\(/g, '\\(') - url = url.replace(/\)/g, '\\)') + url = + window.location.protocol + + "//" + + window.location.host + + window.location.pathname + + window.location.search; + url = url.replace(/\(/g, "\\("); + url = url.replace(/\)/g, "\\)"); } - if (relation.relation.type1 !== 'none') { - svgPath.attr('marker-start', 'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')') + if (relation.relation.type1 !== "none") { + svgPath.attr( + "marker-start", + "url(" + + url + + "#" + + getRelationType(relation.relation.type1) + + "Start" + + ")" + ); } - if (relation.relation.type2 !== 'none') { - svgPath.attr('marker-end', 'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')') + if (relation.relation.type2 !== "none") { + svgPath.attr( + "marker-end", + "url(" + + url + + "#" + + getRelationType(relation.relation.type2) + + "End" + + ")" + ); } - let x, y - const l = path.points.length - if ((l % 2) !== 0) { - const p1 = path.points[Math.floor(l / 2)] - const p2 = path.points[Math.ceil(l / 2)] - x = (p1.x + p2.x) / 2 - y = (p1.y + p2.y) / 2 + let x, y; + const l = path.points.length; + if (l % 2 !== 0 && l > 1) { + const p1 = path.points[Math.floor(l / 2)]; + const p2 = path.points[Math.ceil(l / 2)]; + x = (p1.x + p2.x) / 2; + y = (p1.y + p2.y) / 2; } else { - const p = path.points[Math.floor(l / 2)] - x = p.x - y = p.y + const p = path.points[Math.floor(l / 2)]; + x = p.x; + y = p.y; } - if (typeof relation.title !== 'undefined') { - const g = elem.append('g') - .attr('class', 'classLabel') - const label = g.append('text') - .attr('class', 'label') - .attr('x', x) - .attr('y', y) - .attr('fill', 'red') - .attr('text-anchor', 'middle') - .text(relation.title) + if (typeof relation.title !== "undefined") { + const g = elem.append("g").attr("class", "classLabel"); + const label = g + .append("text") + .attr("class", "label") + .attr("x", x) + .attr("y", y) + .attr("fill", "red") + .attr("text-anchor", "middle") + .text(relation.title); - window.label = label - const bounds = label.node().getBBox() + window.label = label; + const bounds = label.node().getBBox(); - g.insert('rect', ':first-child') - .attr('class', 'box') - .attr('x', bounds.x - conf.padding / 2) - .attr('y', bounds.y - conf.padding / 2) - .attr('width', bounds.width + conf.padding) - .attr('height', bounds.height + conf.padding) + g.insert("rect", ":first-child") + .attr("class", "box") + .attr("x", bounds.x - conf.padding / 2) + .attr("y", bounds.y - conf.padding / 2) + .attr("width", bounds.width + conf.padding) + .attr("height", bounds.height + conf.padding); } - edgeCount++ -} + edgeCount++; +}; -const drawClass = function (elem, classDef) { - logger.info('Rendering class ' + classDef) +const drawClass = function(elem, classDef) { + logger.info("Rendering class " + classDef); - const addTspan = function (textEl, txt, isFirst) { - const tSpan = textEl.append('tspan') - .attr('x', conf.padding) - .text(txt) + const addTspan = function(textEl, txt, isFirst) { + const tSpan = textEl + .append("tspan") + .attr("x", conf.padding) + .text(txt); if (!isFirst) { - tSpan.attr('dy', conf.textHeight) + tSpan.attr("dy", conf.textHeight); } - } + }; - const id = 'classId' + classCnt + const id = "classId" + (classCnt % total); const classInfo = { id: id, label: classDef.id, width: 0, height: 0 - } + }; - const g = elem.append('g') - .attr('id', id) - .attr('class', 'classGroup') - const title = g.append('text') - .attr('x', conf.padding) - .attr('y', conf.textHeight + conf.padding) - .text(classDef.id) + const g = elem + .append("g") + .attr("id", id) + .attr("class", "classGroup"); + const title = g + .append("text") + .attr("x", conf.padding) + .attr("y", conf.textHeight + conf.padding) + .text(classDef.id); - const titleHeight = title.node().getBBox().height + const titleHeight = title.node().getBBox().height; - const membersLine = g.append('line') // text label for the x axis - .attr('x1', 0) - .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2) - .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2) + const membersLine = g + .append("line") // text label for the x axis + .attr("x1", 0) + .attr("y1", conf.padding + titleHeight + conf.dividerMargin / 2) + .attr("y2", conf.padding + titleHeight + conf.dividerMargin / 2); - const members = g.append('text') // text label for the x axis - .attr('x', conf.padding) - .attr('y', titleHeight + (conf.dividerMargin) + conf.textHeight) - .attr('fill', 'white') - .attr('class', 'classText') + const members = g + .append("text") // text label for the x axis + .attr("x", conf.padding) + .attr("y", titleHeight + conf.dividerMargin + conf.textHeight) + .attr("fill", "white") + .attr("class", "classText"); - let isFirst = true - classDef.members.forEach(function (member) { - addTspan(members, member, isFirst) - isFirst = false - }) + let isFirst = true; + classDef.members.forEach(function(member) { + addTspan(members, member, isFirst); + isFirst = false; + }); - const membersBox = members.node().getBBox() + const membersBox = members.node().getBBox(); - const methodsLine = g.append('line') // text label for the x axis - .attr('x1', 0) - .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) - .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) + const methodsLine = g + .append("line") // text label for the x axis + .attr("x1", 0) + .attr( + "y1", + conf.padding + titleHeight + conf.dividerMargin + membersBox.height + ) + .attr( + "y2", + conf.padding + titleHeight + conf.dividerMargin + membersBox.height + ); - const methods = g.append('text') // text label for the x axis - .attr('x', conf.padding) - .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight) - .attr('fill', 'white') - .attr('class', 'classText') + const methods = g + .append("text") // text label for the x axis + .attr("x", conf.padding) + .attr( + "y", + titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight + ) + .attr("fill", "white") + .attr("class", "classText"); - isFirst = true + isFirst = true; - classDef.methods.forEach(function (method) { - addTspan(methods, method, isFirst) - isFirst = false - }) + classDef.methods.forEach(function(method) { + addTspan(methods, method, isFirst); + isFirst = false; + }); - const classBox = g.node().getBBox() - g.insert('rect', ':first-child') - .attr('x', 0) - .attr('y', 0) - .attr('width', classBox.width + 2 * conf.padding) - .attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin) + const classBox = g.node().getBBox(); + g.insert("rect", ":first-child") + .attr("x", 0) + .attr("y", 0) + .attr("width", classBox.width + 2 * conf.padding) + .attr("height", classBox.height + conf.padding + 0.5 * conf.dividerMargin); - membersLine.attr('x2', classBox.width + 2 * conf.padding) - methodsLine.attr('x2', classBox.width + 2 * conf.padding) + membersLine.attr("x2", classBox.width + 2 * conf.padding); + methodsLine.attr("x2", classBox.width + 2 * conf.padding); - classInfo.width = classBox.width + 2 * conf.padding - classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin + classInfo.width = classBox.width + 2 * conf.padding; + classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin; - idCache[id] = classInfo - classCnt++ - return classInfo -} + idCache[id] = classInfo; + classCnt++; + return classInfo; +}; -export const setConf = function (cnf) { - const keys = Object.keys(cnf) +export const setConf = function(cnf) { + const keys = Object.keys(cnf); - keys.forEach(function (key) { - conf[key] = cnf[key] - }) -} + 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 draw = function (text, id) { - parser.yy.clear() - parser.parse(text) +export const draw = function(text, id) { + parser.yy.clear(); + parser.parse(text); - logger.info('Rendering diagram ' + text) + logger.info("Rendering diagram " + text); /// / Fetch the default direction, use TD if none was found - const diagram = d3.select(`[id="${id}"]`) - insertMarkers(diagram) + const diagram = d3.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 {} - }) + g.setDefaultEdgeLabel(function() { + return {}; + }); - const classes = classDb.getClasses() - const keys = Object.keys(classes) + const classes = classDb.getClasses(); + const keys = Object.keys(classes); + total = keys.length; for (let i = 0; i < keys.length; i++) { - const classDef = classes[keys[i]] - const node = drawClass(diagram, classDef) + const classDef = classes[keys[i]]; + const node = drawClass(diagram, classDef); // 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) + g.setNode(node.id, node); + logger.info("Org height: " + node.height); } - const relations = classDb.getRelations() - 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 }) - }) - dagre.layout(g) - g.nodes().forEach(function (v) { - if (typeof v !== 'undefined') { - logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v))) - d3.select('#' + v).attr('transform', 'translate(' + (g.node(v).x - (g.node(v).width / 2)) + ',' + (g.node(v).y - (g.node(v).height / 2)) + ' )') + const relations = classDb.getRelations(); + 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 + }); + }); + 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))); + d3.select("#" + 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) { - logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))) - drawEdge(diagram, g.edge(e), g.edge(e).relation) - }) + }); + 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)) + ); + drawEdge(diagram, g.edge(e), g.edge(e).relation); + } + }); - diagram.attr('height', '100%') - diagram.attr('width', '100%') - diagram.attr('viewBox', '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20)) -} + diagram.attr("height", "100%"); + diagram.attr("width", "100%"); + diagram.attr( + "viewBox", + "0 0 " + (g.graph().width + 20) + " " + (g.graph().height + 20) + ); +}; export default { setConf, draw -} +};