From 94afcfb6f954cf77fa9cf0ac1a8834d3baed2d14 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sun, 29 Sep 2019 15:50:43 +0200 Subject: [PATCH] #945 Rendering of state descriptions --- .../rendering/stateDiagram.spec.js | 12 +++ src/diagrams/state/parser/stateDiagram.jison | 4 +- src/diagrams/state/stateDb.js | 17 ++-- src/diagrams/state/stateRenderer.js | 91 ++++++++++++------- 4 files changed, 82 insertions(+), 42 deletions(-) diff --git a/cypress/integration/rendering/stateDiagram.spec.js b/cypress/integration/rendering/stateDiagram.spec.js index 6733c4ca4..6ae7cbe4e 100644 --- a/cypress/integration/rendering/stateDiagram.spec.js +++ b/cypress/integration/rendering/stateDiagram.spec.js @@ -42,4 +42,16 @@ describe('State diagram', () => { ); cy.get('svg'); }); + it('should render state descriptions', () => { + imgSnapshotTest( + ` + stateDiagram + state "Long state description" as XState1 + state "Another Long state description" as XState2 + XState2 : New line + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); }); diff --git a/src/diagrams/state/parser/stateDiagram.jison b/src/diagrams/state/parser/stateDiagram.jison index 0659258c6..c54ccd357 100644 --- a/src/diagrams/state/parser/stateDiagram.jison +++ b/src/diagrams/state/parser/stateDiagram.jison @@ -104,14 +104,14 @@ line ; statement - : idStatement DESCR + : idStatement DESCR {yy.addState($1, 'default');yy.addDescription($1, $2.trim());} | idStatement '-->' idStatement {yy.addRelation($1, $3);} | idStatement '-->' idStatement DESCR {yy.addRelation($1, $3, $4.substr(1).trim());} | HIDE_EMPTY | scale WIDTH | COMPOSIT_STATE | COMPOSIT_STATE STRUCT_START document STRUCT_STOP - | STATE_DESCR AS ID + | STATE_DESCR AS ID {yy.addState($3, 'default');yy.addDescription($3, $1);} | STATE_DESCR AS ID STRUCT_START document STRUCT_STOP | FORK | JOIN diff --git a/src/diagrams/state/stateDb.js b/src/diagrams/state/stateDb.js index d3db25e20..5a24f5680 100644 --- a/src/diagrams/state/stateDb.js +++ b/src/diagrams/state/stateDb.js @@ -62,15 +62,14 @@ export const addRelation = function(_id1, _id2, title) { relations.push({ id1, id2, title }); }; -export const addMember = function(className, member) { - const theState = states[className]; - if (typeof member === 'string') { - if (member.substr(-1) === ')') { - theState.methods.push(member); - } else { - theState.members.push(member); - } +export const addDescription = function(id, _descr) { + const theState = states[id]; + let descr = _descr; + if (descr[0] === ':') { + descr = descr.substr(1).trim(); } + + theState.descriptions.push(descr); }; export const addMembers = function(className, MembersArr) { @@ -106,7 +105,7 @@ export default { getStates, getRelations, addRelation, - addMember, + addDescription, addMembers, cleanupLabel, lineType, diff --git a/src/diagrams/state/stateRenderer.js b/src/diagrams/state/stateRenderer.js index be5dd1ea8..800465845 100644 --- a/src/diagrams/state/stateRenderer.js +++ b/src/diagrams/state/stateRenderer.js @@ -222,6 +222,63 @@ const drawSimpleState = (g, stateDef) => { return state; }; +/** + * Draws a state with descriptions + * @param {*} g + * @param {*} stateDef + */ +const drawDescrState = (g, stateDef) => { + const addTspan = function(textEl, txt, isFirst) { + const tSpan = textEl + .append('tspan') + .attr('x', 2 * conf.padding) + .text(txt); + if (!isFirst) { + tSpan.attr('dy', conf.textHeight); + } + }; + const title = g + .append('text') + .attr('x', 2 * conf.padding) + .attr('y', conf.textHeight + 1.5 * conf.padding) + .attr('font-size', 24) + .attr('class', 'state-title') + .text(stateDef.id); + + const titleHeight = title.node().getBBox().height; + + const description = g + .append('text') // text label for the x axis + .attr('x', conf.padding) + .attr('y', titleHeight + conf.padding * 0.2 + conf.dividerMargin + conf.textHeight) + .attr('fill', 'white') + .attr('class', 'state-description'); + + let isFirst = true; + stateDef.descriptions.forEach(function(descr) { + addTspan(description, descr, isFirst); + isFirst = false; + }); + + const descrLine = g + .append('line') // text label for the x axis + .attr('x1', conf.padding) + .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2) + .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2) + .attr('class', 'descr-divider'); + const descrBox = description.node().getBBox(); + descrLine.attr('x2', descrBox.width + 3 * conf.padding); + // const classBox = title.node().getBBox(); + + g.insert('rect', ':first-child') + .attr('x', conf.padding) + .attr('y', conf.padding) + .attr('width', descrBox.width + 2 * conf.padding) + .attr('height', descrBox.height + titleHeight + 2 * conf.padding) + .attr('rx', '5'); + + return g; +}; const drawEndState = g => { g.append('circle') .style('stroke', 'black') @@ -286,31 +343,11 @@ const drawEdge = function(elem, path, relation) { url = url.replace(/\)/g, '\\)'); } - // svgPath.attr( - // 'marker-start', - // 'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'Start' + ')' - // ); svgPath.attr( 'marker-end', 'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')' ); - // Figure ou where to put the label given the points - // 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; - // } - - // console.log('calcLabelPosition', utils); - if (typeof relation.title !== 'undefined') { const g = elem.append('g').attr('class', 'classLabel'); const label = g @@ -360,16 +397,6 @@ const drawEdge = function(elem, path, relation) { const drawState = function(elem, stateDef) { // logger.info('Rendering class ' + stateDef); - const addTspan = function(textEl, txt, isFirst) { - const tSpan = textEl - .append('tspan') - .attr('x', conf.padding) - .text(txt); - if (!isFirst) { - tSpan.attr('dy', conf.textHeight); - } - }; - const id = stateDef.id; const stateInfo = { id: id, @@ -385,7 +412,9 @@ const drawState = function(elem, stateDef) { if (stateDef.type === 'start') drawStartState(g); if (stateDef.type === 'end') drawEndState(g); - if (stateDef.type === 'default') drawSimpleState(g, stateDef); + if (stateDef.type === 'default' && stateDef.descriptions.length === 0) + drawSimpleState(g, stateDef); + if (stateDef.type === 'default' && stateDef.descriptions.length > 0) drawDescrState(g, stateDef); const stateBox = g.node().getBBox();