diff --git a/cypress/integration/rendering/stateDiagram.spec.js b/cypress/integration/rendering/stateDiagram.spec.js index 6ae7cbe4e..78ea61cfe 100644 --- a/cypress/integration/rendering/stateDiagram.spec.js +++ b/cypress/integration/rendering/stateDiagram.spec.js @@ -49,6 +49,23 @@ describe('State diagram', () => { state "Long state description" as XState1 state "Another Long state description" as XState2 XState2 : New line + XState1 --> XState2 + `, + { logLevel: 0 } + ); + cy.get('svg'); + }); + it('should render composit states', () => { + imgSnapshotTest( + ` + stateDiagram + [*] --> NotShooting + + state NotShooting { + [*] --> Idle + Idle --> Configuring : EvConfig + Configuring --> Idle : EvConfig + } `, { logLevel: 0 } ); diff --git a/src/diagrams/state/parser/stateDiagram.jison b/src/diagrams/state/parser/stateDiagram.jison index c54ccd357..d23193dbb 100644 --- a/src/diagrams/state/parser/stateDiagram.jison +++ b/src/diagrams/state/parser/stateDiagram.jison @@ -89,30 +89,52 @@ start : SPACE start | NL start - | SD document { return $2; } + | SD document { console.warn('Root document', $2); return $2; } ; document : /* empty */ { $$ = [] } - | document line {$1.push($2);$$ = $1} + | document line { + if($2!='nl'){ + $1.push($2);$$ = $1 + } + console.warn('Got document',$1, $2); + } ; line - : SPACE statement { console.log('here');$$ = $2 } - | statement {console.log('line', $1); $$ = $1 } - | NL { $$=[];} + : SPACE statement { console.warn('here');$$ = $2 } + | statement {console.warn('line', $1); $$ = $1 } + | NL { console.warn('NL'); $$='nl';} ; statement - : 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());} + : idStatement DESCR { $$={ stmt: 'state', id: $1, type: 'default', description: $2.trim()};} + | idStatement '-->' idStatement + { + /*console.warn('got id', $1);yy.addRelation($1, $3);*/ + $$={ stmt: 'relation', state1: { stmt: 'state', id: $1, type: 'default', description: '' }, state2:{ stmt: 'state', id: $3 ,type: 'default', description: ''}}; + } + | idStatement '-->' idStatement DESCR + { + /*yy.addRelation($1, $3, $4.substr(1).trim());*/ + $$={ stmt: 'relation', state1: { stmt: 'state', id: $1, type: 'default', description: '' }, state2:{ stmt: 'state', id: $3 ,type: 'default', description: ''}, description: $4.substr(1).trim()}; + } | HIDE_EMPTY | scale WIDTH | COMPOSIT_STATE | COMPOSIT_STATE STRUCT_START document STRUCT_STOP - | STATE_DESCR AS ID {yy.addState($3, 'default');yy.addDescription($3, $1);} + { + console.warn('Adding document for state without id ', $3); + // yy.addDocument('noId'); + $$={ stmt: 'state', id: 'noId', type: 'default', description: '', doc: $3 } + } + | STATE_DESCR AS ID { $$={id: $3, type: 'default', description: $1.trim()};} | STATE_DESCR AS ID STRUCT_START document STRUCT_STOP + { + //console.warn('Adding document for state with id ', $3, $4); yy.addDocument($3); + $$={ stmt: 'state', id: $3, type: 'default', description: $1, doc: $5 } + } | FORK | JOIN | CONCURRENT @@ -129,112 +151,5 @@ notePosition : left_of | right_of ; -// statement -// : 'participant' actor 'AS' restOfLine 'NL' {$2.description=$4; $$=$2;} -// | 'participant' actor 'NL' {$$=$2;} -// | signal 'NL' -// | 'activate' actor 'NL' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};} -// | 'deactivate' actor 'NL' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};} -// | note_statement 'NL' -// | title text2 'NL' {$$=[{type:'setTitle', text:$2}]} -// | 'loop' restOfLine document end -// { -// $3.unshift({type: 'loopStart', loopText:$2, signalType: yy.LINETYPE.LOOP_START}); -// $3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END}); -// $$=$3;} -// | 'rect' restOfLine document end -// { -// $3.unshift({type: 'rectStart', color:$2, signalType: yy.LINETYPE.RECT_START }); -// $3.push({type: 'rectEnd', color:$2, signalType: yy.LINETYPE.RECT_END }); -// $$=$3;} -// | opt restOfLine document end -// { -// $3.unshift({type: 'optStart', optText:$2, signalType: yy.LINETYPE.OPT_START}); -// $3.push({type: 'optEnd', optText:$2, signalType: yy.LINETYPE.OPT_END}); -// $$=$3;} -// | alt restOfLine else_sections end -// { -// // Alt start -// $3.unshift({type: 'altStart', altText:$2, signalType: yy.LINETYPE.ALT_START}); -// // Content in alt is already in $3 -// // End -// $3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END}); -// $$=$3;} -// | par restOfLine par_sections end -// { -// // Parallel start -// $3.unshift({type: 'parStart', parText:$2, signalType: yy.LINETYPE.PAR_START}); -// // Content in par is already in $3 -// // End -// $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END}); -// $$=$3;} -// ; - -// par_sections -// : document -// | document and restOfLine par_sections -// { $$ = $1.concat([{type: 'and', parText:$3, signalType: yy.LINETYPE.PAR_AND}, $4]); } -// ; - -// else_sections -// : document -// | document else restOfLine else_sections -// { $$ = $1.concat([{type: 'else', altText:$3, signalType: yy.LINETYPE.ALT_ELSE}, $4]); } -// ; - -// note_statement -// : 'note' placement actor text2 -// { -// $$ = [$3, {type:'addNote', placement:$2, actor:$3.actor, text:$4}];} -// | 'note' 'over' actor_pair text2 -// { -// // Coerce actor_pair into a [to, from, ...] array -// $2 = [].concat($3, $3).slice(0, 2); -// $2[0] = $2[0].actor; -// $2[1] = $2[1].actor; -// $$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$4}];} -// ; - -// spaceList -// : SPACE spaceList -// | SPACE -// ; -// actor_pair -// : actor ',' actor { $$ = [$1, $3]; } -// | actor { $$ = $1; } -// ; - -// placement -// : 'left_of' { $$ = yy.PLACEMENT.LEFTOF; } -// | 'right_of' { $$ = yy.PLACEMENT.RIGHTOF; } -// ; - -// signal -// : actor signaltype '+' actor text2 -// { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, -// {type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $4} -// ]} -// | actor signaltype '-' actor text2 -// { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, -// {type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $1} -// ]} -// | actor signaltype actor text2 -// { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} -// ; - -// actor -// : ACTOR {$$={type: 'addActor', actor:$1}} -// ; - -// signaltype -// : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } -// | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } -// | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } -// | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } -// | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } -// | DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; } -// ; - -// text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ; %% diff --git a/src/diagrams/state/stateDb.js b/src/diagrams/state/stateDb.js index 5a24f5680..aff96b909 100644 --- a/src/diagrams/state/stateDb.js +++ b/src/diagrams/state/stateDb.js @@ -1,7 +1,18 @@ import { logger } from '../../logger'; -let relations = []; -let states = {}; +const newDoc = () => { + return { + relations: [], + states: {}, + documents: {} + }; +}; + +let documents = { + root: newDoc() +}; + +let currentDocument = documents.root; let startCnt = 0; let endCnt = 0; @@ -14,8 +25,9 @@ let endCnt = 0; * @param style */ export const addState = function(id, type) { - if (typeof states[id] === 'undefined') { - states[id] = { + console.warn('Add state', id); + if (typeof currentDocument.states[id] === 'undefined') { + currentDocument.states[id] = { id: id, descriptions: [], type @@ -24,21 +36,30 @@ export const addState = function(id, type) { }; export const clear = function() { - relations = []; - states = {}; + documents = { + root: newDoc() + }; }; export const getState = function(id) { - return states[id]; + return currentDocument.states[id]; +}; +export const addDocument = id => { + console.warn(currentDocument, documents); + currentDocument.documents[id] = newDoc(); + currentDocument.documents[id].parent = currentDocument; + currentDocument = currentDocument.documents[id]; }; export const getStates = function() { - return states; + return currentDocument.states; +}; +export const logDocuments = function() { + console.warn('Documents = ', documents); }; - export const getRelations = function() { // const relations1 = [{ id1: 'start1', id2: 'state1' }, { id1: 'state1', id2: 'exit1' }]; // return relations; - return relations; + return currentDocument.relations; }; export const addRelation = function(_id1, _id2, title) { @@ -59,11 +80,11 @@ export const addRelation = function(_id1, _id2, title) { console.log(id1, id2, title); addState(id1, type1); addState(id2, type2); - relations.push({ id1, id2, title }); + currentDocument.relations.push({ id1, id2, title }); }; export const addDescription = function(id, _descr) { - const theState = states[id]; + const theState = currentDocument.states[id]; let descr = _descr; if (descr[0] === ':') { descr = descr.substr(1).trim(); @@ -72,12 +93,6 @@ export const addDescription = function(id, _descr) { theState.descriptions.push(descr); }; -export const addMembers = function(className, MembersArr) { - if (Array.isArray(MembersArr)) { - MembersArr.forEach(member => addMember(className, member)); - } -}; - export const cleanupLabel = function(label) { if (label.substring(0, 1) === ':') { return label.substr(2).trim(); @@ -106,8 +121,9 @@ export default { getRelations, addRelation, addDescription, - addMembers, cleanupLabel, lineType, - relationType + relationType, + logDocuments, + addDocument }; diff --git a/src/diagrams/state/stateRenderer.js b/src/diagrams/state/stateRenderer.js index 800465845..eb58c034d 100644 --- a/src/diagrams/state/stateRenderer.js +++ b/src/diagrams/state/stateRenderer.js @@ -39,94 +39,6 @@ const getGraphId = function(label) { * 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'); - - 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', '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', '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') @@ -434,6 +346,7 @@ const drawState = function(elem, stateDef) { export const draw = function(text, id) { parser.yy.clear(); parser.parse(text); + stateDb.logDocuments(); logger.info('Rendering diagram ' + text); // /// / Fetch the default direction, use TD if none was found @@ -442,10 +355,11 @@ export const draw = function(text, id) { // // Layout graph, Create a new directed graph const graph = new graphlib.Graph({ - multigraph: false + multigraph: false, + compound: true }); - // // Set an object for the graph label + // Set an object for the graph label graph.setGraph({ isMultiGraph: false }); @@ -457,22 +371,41 @@ export const draw = function(text, id) { const states = stateDb.getStates(); const keys = Object.keys(states); + total = keys.length; for (let i = 0; i < keys.length; i++) { const stateDef = states[keys[i]]; const node = drawState(diagram, stateDef); + // const nodeAppendix = drawStartState(diagram, stateDef); + // 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. graph.setNode(node.id, node); + // graph.setNode(node.id + 'note', nodeAppendix); + + // let parent = 'p1'; + // if (node.id === 'XState1') { + // parent = 'p2'; + // } + + // graph.setParent(node.id, parent); + // graph.setParent(node.id + 'note', parent); + // logger.info('Org height: ' + node.height); } + console.info('Count=', graph.nodeCount()); const relations = stateDb.getRelations(); relations.forEach(function(relation) { graph.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), { + relation: relation, + width: 38 + }); + console.warn(getGraphId(relation.id1), getGraphId(relation.id2), { relation: relation }); + // graph.setEdge(getGraphId(relation.id1), getGraphId(relation.id2)); }); dagre.layout(graph); graph.nodes().forEach(function(v) {