From 5b6e9747b7ed8ef498abc6a1882fc01db40a7eb8 Mon Sep 17 00:00:00 2001 From: Dan Shai Date: Fri, 14 Jun 2019 01:22:46 +0300 Subject: [PATCH] fixed class diagram 2nd pr --- src/diagrams/class/classRenderer.js | 580 ++++++++++++++-------------- 1 file changed, 284 insertions(+), 296 deletions(-) diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 2b31c326d..fb74b571f 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -1,456 +1,444 @@ -import dagre from "dagre-layout"; -import graphlib from "graphlibrary"; -import * as d3 from "d3"; +import * as d3 from 'd3' +import dagre from 'dagre-layout' +import graphlib from 'graphlibrary' +import { logger } from '../../logger' +import classDb from './classDb' +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; - -const idCache = {}; - -let classCnt = 0; +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) { +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"); + .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 + .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"); + .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"); + .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"); + .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"); + .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"); + .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"); -}; + .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; -let total = 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); - }); + path.points = path.points.filter(p => !Number.isNaN(p)) // 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; + .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 = ""; + .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, "\\)"); + window.location.search + url = url.replace(/\(/g, '\\(') + url = url.replace(/\)/g, '\\)') } - if (relation.relation.type1 !== "none") { + if (relation.relation.type1 !== 'none') { svgPath.attr( - "marker-start", - "url(" + + 'marker-start', + 'url(' + url + - "#" + + '#' + getRelationType(relation.relation.type1) + - "Start" + - ")" - ); + 'Start' + + ')' + ) } - if (relation.relation.type2 !== "none") { + if (relation.relation.type2 !== 'none') { svgPath.attr( - "marker-end", - "url(" + + 'marker-end', + 'url(' + url + - "#" + + '#' + getRelationType(relation.relation.type2) + - "End" + - ")" - ); + 'End' + + ')' + ) } - let x, y; - const l = path.points.length; + 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; + 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"); + 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); + .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 addTspan = function (textEl, txt, isFirst) { const tSpan = textEl - .append("tspan") - .attr("x", conf.padding) - .text(txt); + .append('tspan') + .attr('x', conf.padding) + .text(txt) if (!isFirst) { - tSpan.attr("dy", conf.textHeight); + tSpan.attr('dy', conf.textHeight) } - }; + } - const id = "classId" + (classCnt % total); + 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"); + .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); + .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); + .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"); + .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) + .append('line') // text label for the x axis + .attr('x1', 0) .attr( - "y1", + 'y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height ) .attr( - "y2", + 'y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height - ); + ) const methods = g - .append("text") // text label for the x axis - .attr("x", conf.padding) + .append('text') // text label for the x axis + .attr('x', conf.padding) .attr( - "y", + 'y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight ) - .attr("fill", "white") - .attr("class", "classText"); + .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); - total = keys.length; + 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) { + const relations = classDb.getRelations() + relations.forEach(function (relation) { logger.info( - "tjoho" + + '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(" + + }) + }) + 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) { - if (typeof e !== "undefined" && typeof g.edge(e) !== "undefined") { + }) + 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); + '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('height', '100%') + diagram.attr('width', '100%') diagram.attr( - "viewBox", - "0 0 " + (g.graph().width + 20) + " " + (g.graph().height + 20) - ); -}; + 'viewBox', + '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20) + ) +} export default { setConf, draw -}; +}