From 159a3a37062dadd458761531369dc6caf9ffbbef Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Wed, 25 Mar 2020 20:16:27 +0100 Subject: [PATCH] #1295 Start shape and handling of setting a unique start id per cluster level --- cypress/platform/current.html | 25 +- src/dagre-wrapper/edges.js | 30 +- src/dagre-wrapper/index.js | 4 +- src/dagre-wrapper/nodes.js | 26 +- src/diagrams/state/stateDb.js | 23 ++ src/diagrams/state/stateRenderer-v2.js | 388 +++---------------------- src/themes/state.scss | 5 + 7 files changed, 129 insertions(+), 372 deletions(-) diff --git a/cypress/platform/current.html b/cypress/platform/current.html index cb7b67f70..c79769cee 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -43,12 +43,27 @@
stateDiagram-v2 - state apa { - Still + [*]-->TV + + state TV { + [*] --> Off: Off to start with + On --> Off : Turn off + Off --> On : Turn on + } + + TV--> Console + + state Console { + [*] --> Off2: Off to start with + On2--> Off2 : Turn off + Off2 --> On2 : Turn on + On2-->Playing + + state Playing { + Alive --> Dead + Dead-->Alive } - state "APAN" as apa - banana --> apa - banana --> Still + }
diff --git a/src/dagre-wrapper/edges.js b/src/dagre-wrapper/edges.js index 2a7c6fd34..ace564aa8 100644 --- a/src/dagre-wrapper/edges.js +++ b/src/dagre-wrapper/edges.js @@ -82,7 +82,7 @@ const outsideNode = (node, point) => { // return { x: insidePoint.x + r, y: insidePoint.y + q }; // }; const intersection = (node, outsidePoint, insidePoint) => { - logger.info('intersection', outsidePoint, insidePoint, node); + // logger.info('intersection', outsidePoint, insidePoint, node); const x = node.x; const y = node.y; @@ -93,8 +93,6 @@ const intersection = (node, outsidePoint, insidePoint) => { const h = node.height / 2; let q = h - dy; - logger.info('q och r', q, r); - const Q = Math.abs(outsidePoint.y - insidePoint.y); const R = Math.abs(outsidePoint.x - insidePoint.x); if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h || false) { // eslint-disable-line @@ -120,8 +118,8 @@ export const insertEdge = function(elem, edge, clusterDb) { logger.info('\n\n\n\n'); let points = edge.points; if (edge.toCluster) { - logger.info('edge', edge); - logger.info('to cluster', clusterDb[edge.toCluster]); + // logger.info('edge', edge); + // logger.info('to cluster', clusterDb[edge.toCluster]); points = []; let lastPointOutside; let isInside = false; @@ -129,11 +127,11 @@ export const insertEdge = function(elem, edge, clusterDb) { const node = clusterDb[edge.toCluster].node; if (!outsideNode(node, point) && !isInside) { - logger.info('inside', edge.toCluster, point); + // logger.info('inside', edge.toCluster, point); // First point inside the rect const insterection = intersection(node, lastPointOutside, point); - logger.info('intersect', inter.rect(node, lastPointOutside)); + // logger.info('intersect', inter.rect(node, lastPointOutside)); points.push(insterection); // points.push(insterection); isInside = true; @@ -145,8 +143,8 @@ export const insertEdge = function(elem, edge, clusterDb) { } if (edge.fromCluster) { - logger.info('edge', edge); - logger.info('from cluster', clusterDb[edge.toCluster]); + // logger.info('edge', edge); + // logger.info('from cluster', clusterDb[edge.toCluster]); const updatedPoints = []; let lastPointOutside; let isInside = false; @@ -155,7 +153,7 @@ export const insertEdge = function(elem, edge, clusterDb) { const node = clusterDb[edge.fromCluster].node; if (!outsideNode(node, point) && !isInside) { - logger.info('inside', edge.toCluster, point); + // logger.info('inside', edge.toCluster, point); // First point inside the rect const insterection = intersection(node, lastPointOutside, point); @@ -165,7 +163,7 @@ export const insertEdge = function(elem, edge, clusterDb) { isInside = true; } else { // at the outside - logger.info('Outside point', point); + // logger.info('Outside point', point); if (!isInside) updatedPoints.unshift(point); } lastPointOutside = point; @@ -173,9 +171,9 @@ export const insertEdge = function(elem, edge, clusterDb) { points = updatedPoints; } - logger.info('Poibts', points); + // logger.info('Poibts', points); - logger.info('Edge', edge); + // logger.info('Edge', edge); // The data for our line const lineData = points.filter(p => !Number.isNaN(p.y)); @@ -188,8 +186,8 @@ export const insertEdge = function(elem, edge, clusterDb) { }) .y(function(d) { return d.y; - }); - // .curve(d3.curveBasis); + }) + .curve(d3.curveBasis); const svgPath = elem .append('path') @@ -219,7 +217,7 @@ export const insertEdge = function(elem, edge, clusterDb) { url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } - logger.info('arrowType', edge.arrowType); + // logger.info('arrowType', edge.arrowType); switch (edge.arrowType) { case 'arrow_cross': svgPath.attr('marker-end', 'url(' + url + '#' + 'crossEnd' + ')'); diff --git a/src/dagre-wrapper/index.js b/src/dagre-wrapper/index.js index 398a387f1..610cb5508 100644 --- a/src/dagre-wrapper/index.js +++ b/src/dagre-wrapper/index.js @@ -74,7 +74,7 @@ export const render = (elem, graph, markers) => { // Move the nodes to the correct place graph.nodes().forEach(function(v) { const node = graph.node(v); - logger.trace('Node ' + v + ': ' + JSON.stringify(graph.node(v))); + logger.info('Node ' + v + ': ' + JSON.stringify(graph.node(v))); if (node.type !== 'group') { positionNode(node); } else { @@ -86,7 +86,7 @@ export const render = (elem, graph, markers) => { // Move the edge labels to the correct place after layout graph.edges().forEach(function(e) { const edge = graph.edge(e); - logger.trace('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge)); + logger.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); insertEdge(edgePaths, edge, clusterDb); positionEdgeLabel(edge); diff --git a/src/dagre-wrapper/nodes.js b/src/dagre-wrapper/nodes.js index 78c8d98c8..ed897a5bf 100644 --- a/src/dagre-wrapper/nodes.js +++ b/src/dagre-wrapper/nodes.js @@ -353,6 +353,28 @@ const circle = (parent, node) => { return shapeSvg; }; +const start = (parent, node) => { + const shapeSvg = parent + .insert('g') + .attr('class', 'node default') + .attr('id', node.id); + const circle = shapeSvg.insert('circle', ':first-child'); + + // center the circle around its coordinate + circle + .attr('class', 'state-start') + .attr('r', 7) + .attr('width', 14) + .attr('height', 14); + + updateNodeBounds(node, circle); + + node.intersect = function(point) { + return intersect.circle(node, point); + }; + + return shapeSvg; +}; const shapes = { question, @@ -366,7 +388,9 @@ const shapes = { trapezoid, inv_trapezoid, rect_right_inv_arrow, - cylinder + cylinder, + start, + end: start }; let nodeElems = {}; diff --git a/src/diagrams/state/stateDb.js b/src/diagrams/state/stateDb.js index b433da339..3419fdad5 100644 --- a/src/diagrams/state/stateDb.js +++ b/src/diagrams/state/stateDb.js @@ -8,6 +8,28 @@ const setRootDoc = o => { const getRootDoc = () => rootDoc; +const docTranslator = (parent, node, first) => { + if (node.stmt === 'relation') { + docTranslator(parent, node.state1, true); + docTranslator(parent, node.state2, false); + } else { + if (node.stmt === 'state') { + if (node.id === '[*]') { + node.id = first ? parent.id + '_start' : parent.id + '_end'; + node.start = first; + } + } + + if (node.doc) { + node.doc.forEach(docNode => docTranslator(node, docNode, true)); + } + } +}; +const getRootDocV2 = () => { + docTranslator({ id: 'root' }, rootDoc, true); + return rootDoc; +}; + const extract = doc => { // const res = { states: [], relations: [] }; clear(); @@ -175,5 +197,6 @@ export default { logDocuments, getRootDoc, setRootDoc, + getRootDocV2, extract }; diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js index f450ee2a1..9d03fa382 100644 --- a/src/diagrams/state/stateRenderer-v2.js +++ b/src/diagrams/state/stateRenderer-v2.js @@ -19,228 +19,6 @@ export const setConf = function(cnf) { const nodeDb = {}; -/** - * 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 addVertices = function(vert, g, svgId) { - const svg = d3.select(`[id="${svgId}"]`); - const keys = Object.keys(vert); - - // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition - keys.forEach(function(id) { - const vertex = vert[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 = 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) { - // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? - const node = { - label: vertexText.replace( - /fa[lrsb]?:fa-[\w-]+/g, - s => `` - ) - }; - 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(//gi); - - 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 'round': - radious = 5; - _shape = 'rect'; - break; - case 'square': - _shape = 'rect'; - break; - case 'diamond': - _shape = 'question'; - break; - case 'hexagon': - _shape = 'hexagon'; - break; - case 'odd': - _shape = 'rect_left_inv_arrow'; - break; - case 'lean_right': - _shape = 'lean_right'; - break; - case 'lean_left': - _shape = 'lean_left'; - break; - case 'trapezoid': - _shape = 'trapezoid'; - break; - case 'inv_trapezoid': - _shape = 'inv_trapezoid'; - break; - case 'odd_right': - _shape = 'rect_left_inv_arrow'; - break; - case 'circle': - _shape = 'circle'; - break; - case 'ellipse': - _shape = 'ellipse'; - break; - case 'stadium': - _shape = 'stadium'; - break; - case 'cylinder': - _shape = 'cylinder'; - break; - case 'group': - _shape = 'rect'; - break; - default: - _shape = 'rect'; - } - // Add the node - g.setNode(vertex.id, { - labelType: 'svg', - labelStyle: styles.labelStyle, - shape: _shape, - label: vertexNode, - 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 addEdges = function(edges, g) { - let cnt = 0; - - let defaultStyle; - let defaultLabelStyle; - - if (typeof edges.defaultStyle !== 'undefined') { - const defaultStyles = getStylesFromArray(edges.defaultStyle); - defaultStyle = defaultStyles.style; - defaultLabelStyle = defaultStyles.labelStyle; - } - - edges.forEach(function(edge) { - cnt++; - const edgeData = {}; - edgeData.id = 'id' + cnt; - // Set link type for rendering - if (edge.type === 'arrow_open') { - edgeData.arrowhead = 'none'; - } else { - edgeData.arrowhead = 'normal'; - } - edgeData.arrowType = edge.type; - - let style = ''; - let labelStyle = ''; - - if (typeof edge.style !== 'undefined') { - const styles = getStylesFromArray(edge.style); - style = styles.style; - labelStyle = styles.labelStyle; - } else { - switch (edge.stroke) { - case 'normal': - style = 'fill:none'; - if (typeof defaultStyle !== 'undefined') { - style = defaultStyle; - } - if (typeof defaultLabelStyle !== 'undefined') { - labelStyle = defaultLabelStyle; - } - break; - case 'dotted': - style = 'fill:none;stroke-width:2px;stroke-dasharray:3;'; - break; - case 'thick': - style = ' stroke-width: 3.5px;fill:none'; - break; - } - } - - edgeData.style = style; - edgeData.labelStyle = labelStyle; - - if (typeof edge.interpolate !== 'undefined') { - edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear); - } else if (typeof edges.defaultInterpolate !== 'undefined') { - edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear); - } else { - edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear); - } - - 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) { - edgeData.labelType = 'html'; - edgeData.label = '' + edge.text + ''; - } else { - edgeData.labelType = 'text'; - edgeData.label = edge.text.replace(//gi, '\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.start, edge.end, edgeData, cnt); - }); -}; - /** * Returns the all the styles from classDef statements in the graph definition. * @returns {object} classDef styles @@ -256,39 +34,33 @@ export const getClasses = function(text) { return stateDb.getClasses(); }; -const setupNode = (g, parent, node) => { - // logger.trace('node', node); - // , { - // labelType: 'svg', - // labelStyle: '', - // shape: 'rect', - // label: node.description || node.id, - // labelText: node.description || node.id, - // rx: 0, - // ry: 0, - // class: 'default', //classStr, - // style: '', //styles.style, - // id: node.id, - // // width: node.type === 'group' ? 500 : undefined, - // // type: node.type, - // padding: 15 //getConfig().flowchart.padding - // }); +const setupNode = (g, parent, node, first) => { // Add the node if (node.id !== 'root') { + let shape = 'rect'; + if (node.start === true) { + shape = 'start'; + } + if (node.start === false) { + shape = 'end'; + } + if (!nodeDb[node.id]) { nodeDb[node.id] = { - id: node.id + id: node.id, + shape, + description: node.id }; } + // Description + if (node.description) { + nodeDb[node.id].description = node.description; + } + // Save data for description and group so that for instance a statement without description overwrites // one with description - // Description - if (nodeDb[node.id].description) { - nodeDb[node.id].description = node.description; - } - // group if (!nodeDb[node.id].type && node.doc) { logger.info('Setting cluser for ', node.id); @@ -298,9 +70,9 @@ const setupNode = (g, parent, node) => { const nodeData = { labelType: 'svg', labelStyle: '', - shape: 'rect', + shape: nodeDb[node.id].shape, label: node.id, - labelText: node.id, + labelText: nodeDb[node.id].description, // label: nodeDb[node.id].description || node.id, // labelText: nodeDb[node.id].description || node.id, rx: 0, @@ -313,7 +85,6 @@ const setupNode = (g, parent, node) => { }; g.setNode(node.id, nodeData); - logger.warn('nodeData = ', node.id, nodeData); } if (parent) { @@ -332,9 +103,9 @@ const setupDoc = (g, parent, doc) => { logger.trace('items', doc); doc.forEach(item => { if (item.stmt === 'state' || item.stmt === 'default') { - setupNode(g, parent, item); + setupNode(g, parent, item, true); } else if (item.stmt === 'relation') { - setupNode(g, parent, item.state1); + setupNode(g, parent, item.state1, true); setupNode(g, parent, item.state2); const edgeData = { arrowhead: 'normal', @@ -344,9 +115,23 @@ const setupDoc = (g, parent, doc) => { arrowheadStyle: 'fill: #333', labelpos: 'c', labelType: 'text', - label: '' + label: '', + // curve: d3.curveNatural, + curve: d3.curveStep + // curve: d3.curveMonotoneX }; - g.setEdge(item.state1.id, item.state2.id, edgeData, cnt); + let startId = item.state1.id; + let endId = item.state2.id; + + // if (parent && startId === '[*]') { + // startId = parent.id + '_start'; + // } + + // if (parent && endId === '[*]') { + // startId = parent.id + '_end'; + // } + + g.setEdge(startId, endId, edgeData, cnt); cnt++; } }); @@ -396,36 +181,9 @@ export const draw = function(text, id) { return {}; }); - setupNode(g, undefined, stateDb.getRootDoc()); - - // let subG; - // const subGraphs = stateDb.getSubGraphs(); - // for (let i = subGraphs.length - 1; i >= 0; i--) { - // subG = subGraphs[i]; - // stateDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes); - // } - - // // Fetch the verices/nodes and edges/links from the parsed graph definition - // const vert = stateDb.getVertices(); - - // const edges = stateDb.getEdges(); - - // logger.trace(edges); - // let i = 0; - // for (i = subGraphs.length - 1; i >= 0; i--) { - // subG = subGraphs[i]; - - // d3.selectAll('cluster').append('text'); - - // for (let j = 0; j < subG.nodes.length; j++) { - // g.setParent(subG.nodes[j], subG.id); - // } - // } - // addVertices(vert, g, id); - // addEdges(edges, g); - - // Add custom shapes - // flowChartShapes.addToRenderV2(addShape); + // logger.info(stateDb.getRootDoc()); + logger.info(stateDb.getRootDocV2()); + setupNode(g, undefined, stateDb.getRootDocV2(), true); // Set up an SVG group so that we can translate the final graph. const svg = d3.select(`[id="${id}"]`); @@ -433,11 +191,6 @@ export const draw = function(text, id) { // Run the renderer. This is what draws the final graph. const element = d3.select('#' + id + ' g'); render(element, g, ['point', 'circle', 'cross']); - // // dagre.layout(g); - - // element.selectAll('g.node').attr('title', function() { - // return stateDb.getTooltip(this.id); - // }); const padding = 8; const svgBounds = svg.node().getBBox(); @@ -461,31 +214,6 @@ export const draw = function(text, id) { .select('g') .attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`); - // Index nodes - // stateDb.indexNodes('subGraph' + i); - - // // reposition labels - // for (i = 0; i < subGraphs.length; i++) { - // subG = subGraphs[i]; - - // if (subG.title !== 'undefined') { - // const clusterRects = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"] rect'); - // const clusterEl = document.querySelectorAll('#' + id + ' [id="' + subG.id + '"]'); - - // const xPos = clusterRects[0].x.baseVal.value; - // const yPos = clusterRects[0].y.baseVal.value; - // const width = clusterRects[0].width.baseVal.value; - // const cluster = d3.select(clusterEl[0]); - // const te = cluster.select('.label'); - // te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`); - // te.attr('id', id + 'Text'); - - // for (let j = 0; j < subG.classes.length; j++) { - // clusterEl[0].classList.add(subG.classes[j]); - // } - // } - // } - // Add label rects for non html labels if (!conf.htmlLabels) { const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); @@ -505,46 +233,10 @@ export const draw = function(text, id) { label.insertBefore(rect, label.firstChild); } } - - // // If node has a link, wrap it in an anchor SVG object. - // const keys = Object.keys(vert); - // keys.forEach(function(key) { - // const vertex = vert[key]; - - // if (vertex.link) { - // const node = d3.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, - addVertices, - addEdges, getClasses, draw }; diff --git a/src/themes/state.scss b/src/themes/state.scss index 3f0fda551..ab16ce68e 100644 --- a/src/themes/state.scss +++ b/src/themes/state.scss @@ -67,3 +67,8 @@ g.stateGroup line { font-family: 'trebuchet ms', verdana, arial; font-family: var(--mermaid-font-family); } + +.node circle.state-start { + fill: black; + stroke: black; +} \ No newline at end of file