From 5b3d0af3111841cee251229f94e5f13e96918d4f Mon Sep 17 00:00:00 2001 From: David Evans Date: Sun, 12 Nov 2017 14:14:56 +0000 Subject: [PATCH] Add support for wavy connection lines [#24] --- README.md | 2 +- screenshots/ConnectionTypes.png | Bin 58572 -> 60290 bytes scripts/core/ArrayUtilities.js | 14 ++- scripts/core/ArrayUtilities_spec.js | 8 +- scripts/main.js | 4 + scripts/sequence/CodeMirrorMode.js | 37 +++---- scripts/sequence/Parser.js | 35 +++++-- scripts/sequence/Parser_spec.js | 64 ++++++++----- scripts/sequence/Tokeniser.js | 8 +- scripts/sequence/components/Connect.js | 127 +++++++++++++++++++++---- scripts/sequence/themes/Basic.js | 9 ++ scripts/sequence/themes/Chunky.js | 9 ++ 12 files changed, 235 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index e7b6b57..6effe22 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Foo -> +Bar: Foo asks Bar [ <- Foo: To the left Foo -> ]: To the right Foo <- ]: From the right -[ -> ]: Left to right! +[ ~> ]: Wavy left to right! # (etc.) ``` diff --git a/screenshots/ConnectionTypes.png b/screenshots/ConnectionTypes.png index 1918926f8bab8903396083d0c62542fcb2a34e61..613312c48b8324445b52f8e378df8d2cb33f278c 100644 GIT binary patch delta 14301 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 delta 13722 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

MEg(@PWxrTNqjJ}~m%l3#2G2pqfOEWP7U&*S zP^ckS)4UMXCl3fLKF@SvU~$*suHV<_PrsTq0-CrUb66fB#4g15I?A2Ujrd5t|M8T3 zd03CNtq4hID`ve2jCy+%E0<#wL7&XUEiIVDGLKvX))%qnrYZ_2OJ83k@l@RSarFJSJOIg&0Su*4x^KdS=McDlSNR| z`Ifq?<|_b7+lwi~=fb3KE~IQy6MKBc=uE_v(}a_BiJqNs=u!(1*741U*Z(_qaw%bf z>OCm2U-T|z1Qa8G1;xl(_uV5ADhe&~or(hB5&l7c`3ij}s=!_U&TAnBD*vhRFDR|Q zGg=787WrSeG5Ft*V|+OD4{(DRvlV>w@5rxz0!e@a;ujtNPw^3`KUyh|A$&((1Aqei z&j?HrYzi`+f6NcDkN>SBhcy}zLD{{4s`QT!u6iA2Z9 u!oDxYyyLzX!D8FDadceKZfR=tvVySPdesE { return list[list.length - 1]; } - function combineRecur(parts, position, str, target) { + function combineRecur(parts, position, current, target) { if(position >= parts.length) { - target.push(str); + target.push(current.slice()); return; } const choices = parts[position]; if(!Array.isArray(choices)) { - combineRecur(parts, position + 1, str + choices, target); + current.push(choices); + combineRecur(parts, position + 1, current, target); + current.pop(); return; } for(let i = 0; i < choices.length; ++ i) { - combineRecur(parts, position + 1, str + choices[i], target); + current.push(choices[i]); + combineRecur(parts, position + 1, current, target); + current.pop(); } } function combine(parts) { const target = []; - combineRecur(parts, 0, '', target); + combineRecur(parts, 0, [], target); return target; } diff --git a/scripts/core/ArrayUtilities_spec.js b/scripts/core/ArrayUtilities_spec.js index 7648652..c0fb9c9 100644 --- a/scripts/core/ArrayUtilities_spec.js +++ b/scripts/core/ArrayUtilities_spec.js @@ -179,10 +179,10 @@ defineDescribe('ArrayUtilities', ['./ArrayUtilities'], (array) => { ['Ff'], ]); expect(list).toEqual([ - 'AaCcEeFf', - 'AaDdEeFf', - 'BbCcEeFf', - 'BbDdEeFf', + ['Aa', 'Cc', 'Ee', 'Ff'], + ['Aa', 'Dd', 'Ee', 'Ff'], + ['Bb', 'Cc', 'Ee', 'Ff'], + ['Bb', 'Dd', 'Ee', 'Ff'], ]); }); }); diff --git a/scripts/main.js b/scripts/main.js index 1b9f45f..f5d703e 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -53,6 +53,10 @@ title: 'Open arrow', code: '{Agent1} ->> {Agent2}: {Message}', }, + { + title: 'Wavy line', + code: '{Agent1} ~> {Agent2}: {Message}', + }, { title: 'Self-connection', code: '{Agent1} -> {Agent1}: {Message}', diff --git a/scripts/sequence/CodeMirrorMode.js b/scripts/sequence/CodeMirrorMode.js index b4f21a2..7a16b40 100644 --- a/scripts/sequence/CodeMirrorMode.js +++ b/scripts/sequence/CodeMirrorMode.js @@ -3,17 +3,10 @@ define(['core/ArrayUtilities'], (array) => { const CM_ERROR = {type: 'error line-error', then: {'': 0}}; - const CM_COMMANDS = ((() => { + const makeCommands = ((() => { const end = {type: '', suggest: '\n', then: {}}; const hiddenEnd = {type: '', then: {}}; - const ARROWS = array.combine([ - ['', '<', '<<'], - ['-', '--'], - ['', '>', '>>'], - ]); - array.removeAll(ARROWS, ['-', '--']); - const textToEnd = {type: 'string', then: {'': 0, '\n': end}}; const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: { '': 0, @@ -108,7 +101,7 @@ define(['core/ArrayUtilities'], (array) => { }; } - function makeCMConnect() { + function makeCMConnect(arrows) { const connect = { type: 'keyword', suggest: true, @@ -124,11 +117,11 @@ define(['core/ArrayUtilities'], (array) => { }, '': 0, }; - ARROWS.forEach((arrow) => (then[arrow] = connect)); + arrows.forEach((arrow) => (then[arrow] = connect)); return makeOpBlock({type: 'variable', suggest: 'Agent', then}); } - return {type: 'error line-error', then: Object.assign({ + const BASE_THEN = { 'title': {type: 'keyword', suggest: true, then: { '': textToEnd, }}, @@ -220,7 +213,14 @@ define(['core/ArrayUtilities'], (array) => { }}, }}, }}, - }, makeCMConnect())}; + }; + + return (arrows) => { + return { + type: 'error line-error', + then: Object.assign(BASE_THEN, makeCMConnect(arrows)), + }; + }; })()); function cmCappedToken(token, current) { @@ -294,12 +294,12 @@ define(['core/ArrayUtilities'], (array) => { } } - function cmCheckToken(state, eol) { + function cmCheckToken(state, eol, commands) { const suggestions = { type: '', value: '', }; - let current = CM_COMMANDS; + let current = commands; const path = [current]; state.line.forEach((token, i) => { @@ -336,8 +336,9 @@ define(['core/ArrayUtilities'], (array) => { } return class Mode { - constructor(tokenDefinitions) { + constructor(tokenDefinitions, arrows) { this.tokenDefinitions = tokenDefinitions; + this.commands = makeCommands(arrows); this.lineComment = '#'; } @@ -348,7 +349,7 @@ define(['core/ArrayUtilities'], (array) => { currentQuoted: false, knownAgent: [], knownLabel: [], - beginCompletions: cmMakeCompletions({}, [CM_COMMANDS]), + beginCompletions: cmMakeCompletions({}, [this.commands]), completions: [], nextCompletions: [], valid: true, @@ -397,7 +398,7 @@ define(['core/ArrayUtilities'], (array) => { return 'comment'; } state.line.push({v: state.current, q: state.currentQuoted}); - return cmCheckToken(state, stream.eol()); + return cmCheckToken(state, stream.eol(), this.commands); } _tokenEOLFound(stream, state, block) { @@ -406,7 +407,7 @@ define(['core/ArrayUtilities'], (array) => { return 'comment'; } state.line.push(({v: state.current, q: state.currentQuoted})); - const type = cmCheckToken(state, false); + const type = cmCheckToken(state, false, this.commands); state.line.pop(); return type; } diff --git a/scripts/sequence/Parser.js b/scripts/sequence/Parser.js index 1f142f2..cb3422d 100644 --- a/scripts/sequence/Parser.js +++ b/scripts/sequence/Parser.js @@ -18,19 +18,32 @@ define([ }; const CONNECT_TYPES = ((() => { - const lTypes = ['', '<', '<<']; - const mTypes = ['-', '--']; - const rTypes = ['', '>', '>>']; - const arrows = array.combine([lTypes, mTypes, rTypes]); - array.removeAll(arrows, mTypes); + const lTypes = [ + {tok: '', type: 0}, + {tok: '<', type: 1}, + {tok: '<<', type: 2}, + ]; + const mTypes = [ + {tok: '-', type: 'solid'}, + {tok: '--', type: 'dash'}, + {tok: '~', type: 'wave'}, + ]; + const rTypes = [ + {tok: '', type: 0}, + {tok: '>', type: 1}, + {tok: '>>', type: 2}, + ]; + const arrows = (array.combine([lTypes, mTypes, rTypes]) + .filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0)) + ); const types = new Map(); arrows.forEach((arrow) => { - types.set(arrow, { - line: arrow.includes('--') ? 'dash' : 'solid', - left: lTypes.indexOf(arrow.substr(0, arrow.indexOf('-'))), - right: rTypes.indexOf(arrow.substr(arrow.lastIndexOf('-') + 1)), + types.set(arrow.map((part) => part.tok).join(''), { + line: arrow[1].type, + left: arrow[0].type, + right: arrow[2].type, }); }); @@ -411,7 +424,9 @@ define([ return class Parser { getCodeMirrorMode() { - return SHARED_TOKENISER.getCodeMirrorMode(); + return SHARED_TOKENISER.getCodeMirrorMode( + Array.from(CONNECT_TYPES.keys()) + ); } getCodeMirrorHints() { diff --git a/scripts/sequence/Parser_spec.js b/scripts/sequence/Parser_spec.js index 68f247a..9224627 100644 --- a/scripts/sequence/Parser_spec.js +++ b/scripts/sequence/Parser_spec.js @@ -195,22 +195,30 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { it('recognises all types of connection', () => { const parsed = parser.parse( - 'A -> B\n' + - 'A <- B\n' + - 'A <-> B\n' + - 'A --> B\n' + - 'A <-- B\n' + - 'A <--> B\n' + - 'A ->> B\n' + - 'A <<- B\n' + - 'A <<->> B\n' + - 'A <->> B\n' + - 'A <<-> B\n' + - 'A -->> B\n' + - 'A <<-- B\n' + - 'A <<-->> B\n' + - 'A <-->> B\n' + - 'A <<--> B\n' + 'A->B\n' + + 'A->>B\n' + + 'A<-B\n' + + 'A<->B\n' + + 'A<->>B\n' + + 'A<<-B\n' + + 'A<<->B\n' + + 'A<<->>B\n' + + 'A-->B\n' + + 'A-->>B\n' + + 'A<--B\n' + + 'A<-->B\n' + + 'A<-->>B\n' + + 'A<<--B\n' + + 'A<<-->B\n' + + 'A<<-->>B\n' + + 'A~>B\n' + + 'A~>>B\n' + + 'A<~B\n' + + 'A<~>B\n' + + 'A<~>>B\n' + + 'A<<~B\n' + + 'A<<~>B\n' + + 'A<<~>>B\n' ); expect(parsed.stages).toEqual([ PARSED.connect(['A', 'B'], { @@ -219,21 +227,29 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { right: 1, label: '', }), + PARSED.connect(['A', 'B'], {line: 'solid', left: 0, right: 2}), PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 0}), PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 1}), + PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 2}), + PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 0}), + PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 1}), + PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 2}), PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 1}), + PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 2}), PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 0}), PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 1}), - PARSED.connect(['A', 'B'], {line: 'solid', left: 0, right: 2}), - PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 0}), - PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 2}), - PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 2}), - PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 1}), - PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 2}), - PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 0}), - PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 2}), PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 2}), + PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 0}), PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 1}), + PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 2}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 1}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 2}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 1, right: 0}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 1, right: 1}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 1, right: 2}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 0}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 1}), + PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 2}), ]); }); diff --git a/scripts/sequence/Tokeniser.js b/scripts/sequence/Tokeniser.js index e9a9779..268f6cf 100644 --- a/scripts/sequence/Tokeniser.js +++ b/scripts/sequence/Tokeniser.js @@ -31,8 +31,8 @@ define(['./CodeMirrorMode'], (CMMode) => { unescape, baseToken: {q: true}, }, - {start: /(?=[^ \t\r\n:+\-*!<>,])/y, end: /(?=[ \t\r\n:+\-*!<>,])|$/y}, - {start: /(?=[\-<>])/y, end: /(?=[^\-<>])|$/y}, + {start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y}, + {start: /(?=[\-~<>])/y, end: /(?=[^\-~<>])|$/y}, {start: /,/y, baseToken: {v: ','}}, {start: /:/y, baseToken: {v: ':'}}, {start: /!/y, baseToken: {v: '!'}}, @@ -195,8 +195,8 @@ define(['./CodeMirrorMode'], (CMMode) => { return tokens; } - getCodeMirrorMode() { - return new CMMode(TOKENS); + getCodeMirrorMode(arrows) { + return new CMMode(TOKENS, arrows); } splitLines(tokens) { diff --git a/scripts/sequence/components/Connect.js b/scripts/sequence/components/Connect.js index dd45fd6..1805b28 100644 --- a/scripts/sequence/components/Connect.js +++ b/scripts/sequence/components/Connect.js @@ -90,6 +90,105 @@ define([ new Arrowhead('double'), ]; + function makeWavyLineHeights(height) { + return [ + 0, + -height * 2 / 3, + -height, + -height * 2 / 3, + 0, + height * 2 / 3, + height, + height * 2 / 3, + ]; + } + + class ConnectingLine { + renderFlat(container, {x1, x2, y}, attrs) { + const ww = attrs['wave-width']; + const hh = attrs['wave-height']; + + if(!ww || !hh) { + container.appendChild(svg.make('line', Object.assign({ + 'x1': x1, + 'y1': y, + 'x2': x2, + 'y2': y, + }, attrs))); + return; + } + + const heights = makeWavyLineHeights(hh); + const dw = ww / heights.length; + let p = 0; + + let points = ''; + for(let x = x1; x + dw <= x2; x += dw) { + points += ( + x + ' ' + + (y + heights[(p ++) % heights.length]) + ' ' + ); + } + points += x2 + ' ' + y; + container.appendChild(svg.make('polyline', Object.assign({ + points, + }, attrs))); + } + + renderRev(container, {xL1, xL2, y1, y2, xR}, attrs) { + const r = (y2 - y1) / 2; + const ww = attrs['wave-width']; + const hh = attrs['wave-height']; + + if(!ww || !hh) { + container.appendChild(svg.make('path', Object.assign({ + 'd': ( + 'M ' + xL1 + ' ' + y1 + + ' L ' + xR + ' ' + y1 + + ' A ' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 + + ' L ' + xL2 + ' ' + y2 + ), + }, attrs))); + return; + } + + const heights = makeWavyLineHeights(hh); + const dw = ww / heights.length; + let p = 0; + + let points = ''; + for(let x = xL1; x + dw <= xR; x += dw) { + points += ( + x + ' ' + + (y1 + heights[(p ++) % heights.length]) + ' ' + ); + } + + const ym = (y1 + y2) / 2; + for(let t = 0; t + dw / r <= Math.PI; t += dw / r) { + const h = heights[(p ++) % heights.length]; + points += ( + (xR + Math.sin(t) * (r - h)) + ' ' + + (ym - Math.cos(t) * (r - h)) + ' ' + ); + } + + for(let x = xR; x - dw >= xL2; x -= dw) { + points += ( + x + ' ' + + (y2 - heights[(p ++) % heights.length]) + ' ' + ); + } + + points += xL2 + ' ' + y2; + container.appendChild(svg.make('polyline', Object.assign({ + points, + }, attrs))); + } + } + + const CONNECTING_LINE = new ConnectingLine(); + class Connect extends BaseComponent { separation({label, agentNames, options}, env) { const config = env.theme.connect; @@ -179,16 +278,13 @@ define([ const y1 = y0 + r * 2; const lineAttrs = config.lineAttrs[options.line]; - env.shapeLayer.appendChild(svg.make('path', Object.assign({ - 'd': ( - 'M ' + (lineX + lArrow.lineGap(env.theme, lineAttrs)) + - ' ' + y0 + - ' L ' + x1 + ' ' + y0 + - ' A ' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + y1 + - ' L ' + (lineX + rArrow.lineGap(env.theme, lineAttrs)) + - ' ' + y1 - ), - }, lineAttrs))); + CONNECTING_LINE.renderRev(env.shapeLayer, { + xL1: lineX + lArrow.lineGap(env.theme, lineAttrs), + xL2: lineX + rArrow.lineGap(env.theme, lineAttrs), + y1: y0, + y2: y1, + xR: x1, + }, lineAttrs); lArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y0, dir: 1}); rArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y1, dir: 1}); @@ -241,12 +337,11 @@ define([ }); const lineAttrs = config.lineAttrs[options.line]; - env.shapeLayer.appendChild(svg.make('line', Object.assign({ - 'x1': x0 + lArrow.lineGap(env.theme, lineAttrs) * dir, - 'y1': y, - 'x2': x1 - rArrow.lineGap(env.theme, lineAttrs) * dir, - 'y2': y, - }, lineAttrs))); + CONNECTING_LINE.renderFlat(env.shapeLayer, { + x1: x0 + lArrow.lineGap(env.theme, lineAttrs) * dir, + x2: x1 - rArrow.lineGap(env.theme, lineAttrs) * dir, + y, + }, lineAttrs); lArrow.render(env.shapeLayer, env.theme, {x: x0, y, dir}); rArrow.render(env.shapeLayer, env.theme, {x: x1, y, dir: -dir}); diff --git a/scripts/sequence/themes/Basic.js b/scripts/sequence/themes/Basic.js index d62ec07..79e5632 100644 --- a/scripts/sequence/themes/Basic.js +++ b/scripts/sequence/themes/Basic.js @@ -70,6 +70,15 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => { 'stroke-width': 1, 'stroke-dasharray': '4, 2', }, + 'wave': { + 'fill': 'none', + 'stroke': '#000000', + 'stroke-width': 1, + 'stroke-linejoin': 'round', + 'stroke-linecap': 'round', + 'wave-width': 6, + 'wave-height': 0.5, + }, }, arrow: { single: { diff --git a/scripts/sequence/themes/Chunky.js b/scripts/sequence/themes/Chunky.js index dc41f61..8ff48ac 100644 --- a/scripts/sequence/themes/Chunky.js +++ b/scripts/sequence/themes/Chunky.js @@ -76,6 +76,15 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => { 'stroke-width': 3, 'stroke-dasharray': '10, 4', }, + 'wave': { + 'fill': 'none', + 'stroke': '#000000', + 'stroke-width': 3, + 'stroke-linejoin': 'round', + 'stroke-linecap': 'round', + 'wave-width': 10, + 'wave-height': 1, + }, }, arrow: { single: {