Add sketch rendering for simple arrows and fix bug where wavy lines do not render when drawn right-to-left [#18]

This commit is contained in:
David Evans 2018-01-06 15:40:54 +00:00
parent 86e8b89a9c
commit 531b284afa
7 changed files with 521 additions and 379 deletions

View File

@ -3969,8 +3969,8 @@ define('sequence/components/Connect',[
) => { ) => {
'use strict'; 'use strict';
function drawHorizontalArrowHead(container, {x, y, dx, dy, attrs}) { function drawHorizontalArrowHead({x, y, dx, dy, attrs}) {
container.appendChild(svg.make( return svg.make(
attrs.fill === 'none' ? 'polyline' : 'polygon', attrs.fill === 'none' ? 'polyline' : 'polygon',
Object.assign({ Object.assign({
'points': ( 'points': (
@ -3979,7 +3979,7 @@ define('sequence/components/Connect',[
(x + dx) + ' ' + (y + dy) (x + dx) + ' ' + (y + dy)
), ),
}, attrs) }, attrs)
)); );
} }
class Arrowhead { class Arrowhead {
@ -4006,15 +4006,16 @@ define('sequence/components/Connect',[
} }
} }
render(layer, theme, {x, y, dir}) { render(layer, theme, pt, dir) {
const config = this.getConfig(theme); const config = this.getConfig(theme);
drawHorizontalArrowHead(layer, { const func = config.render || drawHorizontalArrowHead;
x: x + this.short(theme) * dir, layer.appendChild(func({
y, x: pt.x + this.short(theme) * dir,
y: pt.y,
dx: config.width * dir, dx: config.width * dir,
dy: config.height / 2, dy: config.height / 2,
attrs: config.attrs, attrs: config.attrs,
}); }));
} }
width(theme) { width(theme) {
@ -4063,19 +4064,21 @@ define('sequence/components/Connect',[
]; ];
} }
class ConnectingLine { function renderFlat({x1, dx1, x2, dx2, y}, attrs) {
renderFlat(container, {x1, x2, y}, attrs) {
const ww = attrs['wave-width']; const ww = attrs['wave-width'];
const hh = attrs['wave-height']; const hh = attrs['wave-height'];
if(!ww || !hh) { if(!ww || !hh) {
container.appendChild(svg.make('line', Object.assign({ return {
'x1': x1, shape: svg.make('line', Object.assign({
'x1': x1 + dx1,
'y1': y, 'y1': y,
'x2': x2, 'x2': x2 + dx2,
'y2': y, 'y2': y,
}, attrs))); }, attrs)),
return; p1: {x: x1, y},
p2: {x: x2, y},
};
} }
const heights = makeWavyLineHeights(hh); const heights = makeWavyLineHeights(hh);
@ -4083,33 +4086,40 @@ define('sequence/components/Connect',[
let p = 0; let p = 0;
let points = ''; let points = '';
for(let x = x1; x + dw <= x2; x += dw) { const xL = Math.min(x1 + dx1, x2 + dx2);
const xR = Math.max(x1 + dx1, x2 + dx2);
for(let x = xL; x + dw <= xR; x += dw) {
points += ( points += (
x + ' ' + x + ' ' +
(y + heights[(p ++) % heights.length]) + ' ' (y + heights[(p ++) % heights.length]) + ' '
); );
} }
points += x2 + ' ' + y; points += xR + ' ' + y;
container.appendChild(svg.make('polyline', Object.assign({ return {
points, shape: svg.make('polyline', Object.assign({points}, attrs)),
}, attrs))); p1: {x: x1, y},
p2: {x: x2, y},
};
} }
renderRev(container, {xL1, xL2, y1, y2, xR}, attrs) { function renderRev({xL, dx1, dx2, y1, y2, xR}, attrs) {
const r = (y2 - y1) / 2; const r = (y2 - y1) / 2;
const ww = attrs['wave-width']; const ww = attrs['wave-width'];
const hh = attrs['wave-height']; const hh = attrs['wave-height'];
if(!ww || !hh) { if(!ww || !hh) {
container.appendChild(svg.make('path', Object.assign({ return {
shape: svg.make('path', Object.assign({
'd': ( 'd': (
'M' + xL1 + ' ' + y1 + 'M' + (xL + dx1) + ' ' + y1 +
'L' + xR + ' ' + y1 + 'L' + xR + ' ' + y1 +
'A' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 + 'A' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 +
'L' + xL2 + ' ' + y2 'L' + (xL + dx2) + ' ' + y2
), ),
}, attrs))); }, attrs)),
return; p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
} }
const heights = makeWavyLineHeights(hh); const heights = makeWavyLineHeights(hh);
@ -4117,7 +4127,7 @@ define('sequence/components/Connect',[
let p = 0; let p = 0;
let points = ''; let points = '';
for(let x = xL1; x + dw <= xR; x += dw) { for(let x = xL + dx1; x + dw <= xR; x += dw) {
points += ( points += (
x + ' ' + x + ' ' +
(y1 + heights[(p ++) % heights.length]) + ' ' (y1 + heights[(p ++) % heights.length]) + ' '
@ -4133,21 +4143,20 @@ define('sequence/components/Connect',[
); );
} }
for(let x = xR; x - dw >= xL2; x -= dw) { for(let x = xR; x - dw >= xL + dx2; x -= dw) {
points += ( points += (
x + ' ' + x + ' ' +
(y2 - heights[(p ++) % heights.length]) + ' ' (y2 - heights[(p ++) % heights.length]) + ' '
); );
} }
points += xL2 + ' ' + y2; points += (xL + dx2) + ' ' + y2;
container.appendChild(svg.make('polyline', Object.assign({ return {
points, shape: svg.make('polyline', Object.assign({points}, attrs)),
}, attrs))); p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
} }
}
const CONNECTING_LINE = new ConnectingLine();
class Connect extends BaseComponent { class Connect extends BaseComponent {
separation({label, agentNames, options}, env) { separation({label, agentNames, options}, env) {
@ -4237,17 +4246,19 @@ define('sequence/components/Connect',[
const x1 = Math.max(lineX + rArrow.width(env.theme), x0 + labelW); const x1 = Math.max(lineX + rArrow.width(env.theme), x0 + labelW);
const y1 = y0 + r * 2; const y1 = y0 + r * 2;
const lineAttrs = config.lineAttrs[options.line]; const line = config.line[options.line];
CONNECTING_LINE.renderRev(env.shapeLayer, { const rendered = (line.renderRev || renderRev)({
xL1: lineX + lArrow.lineGap(env.theme, lineAttrs), xL: lineX,
xL2: lineX + rArrow.lineGap(env.theme, lineAttrs), dx1: lArrow.lineGap(env.theme, line.attrs),
dx2: rArrow.lineGap(env.theme, line.attrs),
y1: y0, y1: y0,
y2: y1, y2: y1,
xR: x1, xR: x1,
}, lineAttrs); }, line.attrs);
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y0, dir: 1}); lArrow.render(env.shapeLayer, env.theme, rendered.p1, 1);
rArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y1, dir: 1}); rArrow.render(env.shapeLayer, env.theme, rendered.p2, 1);
const raise = Math.max(height, lArrow.height(env.theme) / 2); const raise = Math.max(height, lArrow.height(env.theme) / 2);
const arrowDip = rArrow.height(env.theme) / 2; const arrowDip = rArrow.height(env.theme) / 2;
@ -4299,15 +4310,18 @@ define('sequence/components/Connect',[
SVGTextBlockClass: env.SVGTextBlockClass, SVGTextBlockClass: env.SVGTextBlockClass,
}); });
const lineAttrs = config.lineAttrs[options.line]; const line = config.line[options.line];
CONNECTING_LINE.renderFlat(env.shapeLayer, { const rendered = (line.render || renderFlat)({
x1: x0 + lArrow.lineGap(env.theme, lineAttrs) * dir, x1: x0,
x2: x1 - rArrow.lineGap(env.theme, lineAttrs) * dir, dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
x2: x1,
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
y, y,
}, lineAttrs); }, line.attrs);
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, {x: x0, y, dir}); lArrow.render(env.shapeLayer, env.theme, rendered.p1, dir);
rArrow.render(env.shapeLayer, env.theme, {x: x1, y, dir: -dir}); rArrow.render(env.shapeLayer, env.theme, rendered.p2, -dir);
const arrowSpread = Math.max( const arrowSpread = Math.max(
lArrow.height(env.theme), lArrow.height(env.theme),
@ -5344,19 +5358,24 @@ define('sequence/themes/Basic',[
connect: { connect: {
loopbackRadius: 6, loopbackRadius: 6,
lineAttrs: { line: {
'solid': { 'solid': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}, },
},
'dash': { 'dash': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'stroke-dasharray': '4, 2', 'stroke-dasharray': '4, 2',
}, },
},
'wave': { 'wave': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
@ -5366,8 +5385,9 @@ define('sequence/themes/Basic',[
'wave-height': 0.5, 'wave-height': 0.5,
}, },
}, },
},
arrow: { arrow: {
single: { 'single': {
width: 5, width: 5,
height: 10, height: 10,
attrs: { attrs: {
@ -5376,7 +5396,7 @@ define('sequence/themes/Basic',[
'stroke-linejoin': 'miter', 'stroke-linejoin': 'miter',
}, },
}, },
double: { 'double': {
width: 4, width: 4,
height: 6, height: 6,
attrs: { attrs: {
@ -5684,19 +5704,24 @@ define('sequence/themes/Chunky',[
connect: { connect: {
loopbackRadius: 8, loopbackRadius: 8,
lineAttrs: { line: {
'solid': { 'solid': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
}, },
},
'dash': { 'dash': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
'stroke-dasharray': '10, 4', 'stroke-dasharray': '10, 4',
}, },
},
'wave': { 'wave': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
@ -5706,6 +5731,7 @@ define('sequence/themes/Chunky',[
'wave-height': 1, 'wave-height': 1,
}, },
}, },
},
arrow: { arrow: {
single: { single: {
width: 10, width: 10,
@ -6386,7 +6412,6 @@ define('sequence/themes/Sketch',[
'use strict'; 'use strict';
// TODO: // TODO:
// * arrows
// * fade starter/terminator sometimes does not fully cover line // * fade starter/terminator sometimes does not fully cover line
// * blocks (if/else/repeat/ref) // * blocks (if/else/repeat/ref)
@ -6407,6 +6432,13 @@ define('sequence/themes/Sketch',[
return r; return r;
} }
const PENCIL = {
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
};
const SETTINGS = { const SETTINGS = {
titleMargin: 10, titleMargin: 10,
outerMargin: 5, outerMargin: 5,
@ -6451,47 +6483,50 @@ define('sequence/themes/Sketch',[
connect: { connect: {
loopbackRadius: 6, loopbackRadius: 6,
lineAttrs: { line: {
'solid': { 'solid': {
attrs: Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000', }, PENCIL),
'stroke-width': 1, render: null,
renderRev: null,
}, },
'dash': { 'dash': {
attrs: Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-dasharray': '4, 2', 'stroke-dasharray': '4, 2',
}, PENCIL),
render: null,
renderRev: null,
}, },
'wave': { 'wave': {
attrs: Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-linejoin': 'round', 'stroke-linejoin': 'round',
'stroke-linecap': 'round', 'stroke-linecap': 'round',
'wave-width': 6, 'wave-width': 6,
'wave-height': 0.5, 'wave-height': 0.5,
}, PENCIL),
render: null,
renderRev: null,
}, },
}, },
arrow: { arrow: {
single: { 'single': {
width: 5, width: 5,
height: 10,
attrs: {
'fill': '#000000',
'stroke-width': 0,
'stroke-linejoin': 'miter',
},
},
double: {
width: 4,
height: 6, height: 6,
attrs: { attrs: Object.assign({
'fill': 'none', 'fill': 'rgba(0,0,0,0.9)',
'stroke': '#000000', }, PENCIL),
'stroke-width': 1, render: null,
'stroke-linejoin': 'miter',
}, },
'double': {
width: 4,
height: 8,
attrs: Object.assign({
'fill': 'none',
}, PENCIL),
render: null,
}, },
}, },
label: { label: {
@ -6704,6 +6739,10 @@ define('sequence/themes/Sketch',[
this.agentCap.cross.render = this.renderCross.bind(this); this.agentCap.cross.render = this.renderCross.bind(this);
this.agentCap.bar.render = this.renderBar.bind(this); this.agentCap.bar.render = this.renderBar.bind(this);
this.agentCap.box.boxRenderer = this.renderBox.bind(this); this.agentCap.box.boxRenderer = this.renderBox.bind(this);
this.connect.arrow.single.render = this.renderArrowHead.bind(this);
this.connect.arrow.double.render = this.renderArrowHead.bind(this);
this.connect.line.solid.render = this.renderConnect.bind(this);
this.connect.line.dash.render = this.renderConnect.bind(this);
this.notes.note.boxRenderer = this.renderNote.bind(this); this.notes.note.boxRenderer = this.renderNote.bind(this);
this.notes.state.boxRenderer = this.renderState.bind(this); this.notes.state.boxRenderer = this.renderState.bind(this);
} }
@ -6748,16 +6787,22 @@ define('sequence/themes/Sketch',[
return Math.asin(rand * 2 - 1) * 2 * range / Math.PI; return Math.asin(rand * 2 - 1) * 2 * range / Math.PI;
} }
lineNodes(p1, p2, {var1 = 1, var2 = 1, move = true}) { lineNodes(p1, p2, {
var1 = 1,
var2 = 1,
varX = 1,
varY = 1,
move = true,
}) {
const length = Math.sqrt( const length = Math.sqrt(
(p2.x - p1.x) * (p2.x - p1.x) + (p2.x - p1.x) * (p2.x - p1.x) +
(p2.y - p1.y) * (p2.y - p1.y) (p2.y - p1.y) * (p2.y - p1.y)
); );
const rough = Math.min(Math.sqrt(length) * 0.2, 5); const rough = Math.min(Math.sqrt(length) * 0.2, 5);
const x1 = p1.x + this.vary(var1 * rough); const x1 = p1.x + this.vary(var1 * varX * rough);
const y1 = p1.y + this.vary(var1 * rough); const y1 = p1.y + this.vary(var1 * varY * rough);
const x2 = p2.x + this.vary(var2 * rough); const x2 = p2.x + this.vary(var2 * varX * rough);
const y2 = p2.y + this.vary(var2 * rough); const y2 = p2.y + this.vary(var2 * varY * rough);
// -1 = p1 higher, 1 = p2 higher // -1 = p1 higher, 1 = p2 higher
const upper = Math.max(-1, Math.min(1, const upper = Math.max(-1, Math.min(1,
@ -6784,14 +6829,12 @@ define('sequence/themes/Sketch',[
}; };
} }
renderLine(p1, p2, {var1 = 1, var2 = 1}) { renderLine(p1, p2, lineOptions) {
const line = this.lineNodes(p1, p2, {var1, var2}); const line = this.lineNodes(p1, p2, lineOptions);
const shape = svg.make('path', { const shape = svg.make('path', Object.assign({
'd': line.nodes, 'd': line.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none', 'fill': 'none',
}); }, PENCIL));
return shape; return shape;
} }
@ -6817,12 +6860,10 @@ define('sequence/themes/Sketch',[
{var1: 0, var2: 0.3, move: false} {var1: 0, var2: 0.3, move: false}
); );
const shape = svg.make('path', { const shape = svg.make('path', Object.assign({
'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes, 'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': fill || '#FFFFFF', 'fill': fill || '#FFFFFF',
}); }, PENCIL));
return shape; return shape;
} }
@ -6866,7 +6907,7 @@ define('sequence/themes/Sketch',[
); );
const g = svg.make('g'); const g = svg.make('g');
g.appendChild(svg.make('path', { g.appendChild(svg.make('path', Object.assign({
'd': ( 'd': (
lT.nodes + lT.nodes +
lF.nodes + lF.nodes +
@ -6874,20 +6915,52 @@ define('sequence/themes/Sketch',[
lB.nodes + lB.nodes +
lL.nodes lL.nodes
), ),
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': '#FFFFFF', 'fill': '#FFFFFF',
})); }, PENCIL)));
g.appendChild(svg.make('path', { g.appendChild(svg.make('path', Object.assign({
'd': lF1.nodes + lF2.nodes, 'd': lF1.nodes + lF2.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none', 'fill': 'none',
})); }, PENCIL)));
return g; return g;
} }
renderConnect({x1, dx1, x2, dx2, y}, attrs) {
const ln = this.lineNodes(
{x: x1 + dx1, y},
{x: x2 + dx2, y},
{varX: 0.3}
);
return {
shape: svg.make('path', Object.assign({'d': ln.nodes}, attrs)),
p1: {x: ln.p1.x - dx1, y: ln.p2.y},
p2: {x: ln.p2.x - dx2, y: ln.p2.y},
};
}
renderArrowHead({x, y, dx, dy, attrs}) {
const w = dx * (1 + this.vary(0.2));
const h = dy * (1 + this.vary(0.3));
const l1 = this.lineNodes(
{x: x + w, y: y - h},
{x, y},
{var1: 2.0, var2: 0.2}
);
const l2 = this.lineNodes(
l1.p2,
{x: x + w, y: y + h},
{var1: 0, var2: 2.0, move: false}
);
const l3 = (attrs.fill === 'none') ? {nodes: ''} : this.lineNodes(
l2.p2,
l1.p1,
{var1: 0, var2: 0, move: false}
);
return svg.make('path', Object.assign({
'd': l1.nodes + l2.nodes + l3.nodes,
}, attrs));
}
renderState({x, y, width, height}) { renderState({x, y, width, height}) {
// TODO: rounded corners // TODO: rounded corners
return this.renderBox({x, y, width, height}); return this.renderBox({x, y, width, height});
@ -6911,12 +6984,10 @@ define('sequence/themes/Sketch',[
{} {}
); );
return svg.make('path', { return svg.make('path', Object.assign({
'd': l1.nodes + l2.nodes, 'd': l1.nodes + l2.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none', 'fill': 'none',
}); }, PENCIL));
} }
getNote(type) { getNote(type) {
@ -6937,7 +7008,7 @@ define('sequence/themes/Sketch',[
const shape = this.renderLine( const shape = this.renderLine(
{x, y: y0}, {x, y: y0},
{x, y: y1}, {x, y: y1},
{} {varY: 0.3}
); );
shape.setAttribute('class', className); shape.setAttribute('class', className);
container.appendChild(shape); container.appendChild(shape);

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -9,8 +9,8 @@ define([
) => { ) => {
'use strict'; 'use strict';
function drawHorizontalArrowHead(container, {x, y, dx, dy, attrs}) { function drawHorizontalArrowHead({x, y, dx, dy, attrs}) {
container.appendChild(svg.make( return svg.make(
attrs.fill === 'none' ? 'polyline' : 'polygon', attrs.fill === 'none' ? 'polyline' : 'polygon',
Object.assign({ Object.assign({
'points': ( 'points': (
@ -19,7 +19,7 @@ define([
(x + dx) + ' ' + (y + dy) (x + dx) + ' ' + (y + dy)
), ),
}, attrs) }, attrs)
)); );
} }
class Arrowhead { class Arrowhead {
@ -46,15 +46,16 @@ define([
} }
} }
render(layer, theme, {x, y, dir}) { render(layer, theme, pt, dir) {
const config = this.getConfig(theme); const config = this.getConfig(theme);
drawHorizontalArrowHead(layer, { const func = config.render || drawHorizontalArrowHead;
x: x + this.short(theme) * dir, layer.appendChild(func({
y, x: pt.x + this.short(theme) * dir,
y: pt.y,
dx: config.width * dir, dx: config.width * dir,
dy: config.height / 2, dy: config.height / 2,
attrs: config.attrs, attrs: config.attrs,
}); }));
} }
width(theme) { width(theme) {
@ -103,19 +104,21 @@ define([
]; ];
} }
class ConnectingLine { function renderFlat({x1, dx1, x2, dx2, y}, attrs) {
renderFlat(container, {x1, x2, y}, attrs) {
const ww = attrs['wave-width']; const ww = attrs['wave-width'];
const hh = attrs['wave-height']; const hh = attrs['wave-height'];
if(!ww || !hh) { if(!ww || !hh) {
container.appendChild(svg.make('line', Object.assign({ return {
'x1': x1, shape: svg.make('line', Object.assign({
'x1': x1 + dx1,
'y1': y, 'y1': y,
'x2': x2, 'x2': x2 + dx2,
'y2': y, 'y2': y,
}, attrs))); }, attrs)),
return; p1: {x: x1, y},
p2: {x: x2, y},
};
} }
const heights = makeWavyLineHeights(hh); const heights = makeWavyLineHeights(hh);
@ -123,33 +126,40 @@ define([
let p = 0; let p = 0;
let points = ''; let points = '';
for(let x = x1; x + dw <= x2; x += dw) { const xL = Math.min(x1 + dx1, x2 + dx2);
const xR = Math.max(x1 + dx1, x2 + dx2);
for(let x = xL; x + dw <= xR; x += dw) {
points += ( points += (
x + ' ' + x + ' ' +
(y + heights[(p ++) % heights.length]) + ' ' (y + heights[(p ++) % heights.length]) + ' '
); );
} }
points += x2 + ' ' + y; points += xR + ' ' + y;
container.appendChild(svg.make('polyline', Object.assign({ return {
points, shape: svg.make('polyline', Object.assign({points}, attrs)),
}, attrs))); p1: {x: x1, y},
p2: {x: x2, y},
};
} }
renderRev(container, {xL1, xL2, y1, y2, xR}, attrs) { function renderRev({xL, dx1, dx2, y1, y2, xR}, attrs) {
const r = (y2 - y1) / 2; const r = (y2 - y1) / 2;
const ww = attrs['wave-width']; const ww = attrs['wave-width'];
const hh = attrs['wave-height']; const hh = attrs['wave-height'];
if(!ww || !hh) { if(!ww || !hh) {
container.appendChild(svg.make('path', Object.assign({ return {
shape: svg.make('path', Object.assign({
'd': ( 'd': (
'M' + xL1 + ' ' + y1 + 'M' + (xL + dx1) + ' ' + y1 +
'L' + xR + ' ' + y1 + 'L' + xR + ' ' + y1 +
'A' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 + 'A' + r + ' ' + r + ' 0 0 1 ' + xR + ' ' + y2 +
'L' + xL2 + ' ' + y2 'L' + (xL + dx2) + ' ' + y2
), ),
}, attrs))); }, attrs)),
return; p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
} }
const heights = makeWavyLineHeights(hh); const heights = makeWavyLineHeights(hh);
@ -157,7 +167,7 @@ define([
let p = 0; let p = 0;
let points = ''; let points = '';
for(let x = xL1; x + dw <= xR; x += dw) { for(let x = xL + dx1; x + dw <= xR; x += dw) {
points += ( points += (
x + ' ' + x + ' ' +
(y1 + heights[(p ++) % heights.length]) + ' ' (y1 + heights[(p ++) % heights.length]) + ' '
@ -173,21 +183,20 @@ define([
); );
} }
for(let x = xR; x - dw >= xL2; x -= dw) { for(let x = xR; x - dw >= xL + dx2; x -= dw) {
points += ( points += (
x + ' ' + x + ' ' +
(y2 - heights[(p ++) % heights.length]) + ' ' (y2 - heights[(p ++) % heights.length]) + ' '
); );
} }
points += xL2 + ' ' + y2; points += (xL + dx2) + ' ' + y2;
container.appendChild(svg.make('polyline', Object.assign({ return {
points, shape: svg.make('polyline', Object.assign({points}, attrs)),
}, attrs))); p1: {x: xL, y: y1},
p2: {x: xL, y: y2},
};
} }
}
const CONNECTING_LINE = new ConnectingLine();
class Connect extends BaseComponent { class Connect extends BaseComponent {
separation({label, agentNames, options}, env) { separation({label, agentNames, options}, env) {
@ -277,17 +286,19 @@ define([
const x1 = Math.max(lineX + rArrow.width(env.theme), x0 + labelW); const x1 = Math.max(lineX + rArrow.width(env.theme), x0 + labelW);
const y1 = y0 + r * 2; const y1 = y0 + r * 2;
const lineAttrs = config.lineAttrs[options.line]; const line = config.line[options.line];
CONNECTING_LINE.renderRev(env.shapeLayer, { const rendered = (line.renderRev || renderRev)({
xL1: lineX + lArrow.lineGap(env.theme, lineAttrs), xL: lineX,
xL2: lineX + rArrow.lineGap(env.theme, lineAttrs), dx1: lArrow.lineGap(env.theme, line.attrs),
dx2: rArrow.lineGap(env.theme, line.attrs),
y1: y0, y1: y0,
y2: y1, y2: y1,
xR: x1, xR: x1,
}, lineAttrs); }, line.attrs);
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y0, dir: 1}); lArrow.render(env.shapeLayer, env.theme, rendered.p1, 1);
rArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y1, dir: 1}); rArrow.render(env.shapeLayer, env.theme, rendered.p2, 1);
const raise = Math.max(height, lArrow.height(env.theme) / 2); const raise = Math.max(height, lArrow.height(env.theme) / 2);
const arrowDip = rArrow.height(env.theme) / 2; const arrowDip = rArrow.height(env.theme) / 2;
@ -339,15 +350,18 @@ define([
SVGTextBlockClass: env.SVGTextBlockClass, SVGTextBlockClass: env.SVGTextBlockClass,
}); });
const lineAttrs = config.lineAttrs[options.line]; const line = config.line[options.line];
CONNECTING_LINE.renderFlat(env.shapeLayer, { const rendered = (line.render || renderFlat)({
x1: x0 + lArrow.lineGap(env.theme, lineAttrs) * dir, x1: x0,
x2: x1 - rArrow.lineGap(env.theme, lineAttrs) * dir, dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
x2: x1,
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
y, y,
}, lineAttrs); }, line.attrs);
env.shapeLayer.appendChild(rendered.shape);
lArrow.render(env.shapeLayer, env.theme, {x: x0, y, dir}); lArrow.render(env.shapeLayer, env.theme, rendered.p1, dir);
rArrow.render(env.shapeLayer, env.theme, {x: x1, y, dir: -dir}); rArrow.render(env.shapeLayer, env.theme, rendered.p2, -dir);
const arrowSpread = Math.max( const arrowSpread = Math.max(
lArrow.height(env.theme), lArrow.height(env.theme),

View File

@ -82,19 +82,24 @@ define([
connect: { connect: {
loopbackRadius: 6, loopbackRadius: 6,
lineAttrs: { line: {
'solid': { 'solid': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}, },
},
'dash': { 'dash': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'stroke-dasharray': '4, 2', 'stroke-dasharray': '4, 2',
}, },
},
'wave': { 'wave': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
@ -104,8 +109,9 @@ define([
'wave-height': 0.5, 'wave-height': 0.5,
}, },
}, },
},
arrow: { arrow: {
single: { 'single': {
width: 5, width: 5,
height: 10, height: 10,
attrs: { attrs: {
@ -114,7 +120,7 @@ define([
'stroke-linejoin': 'miter', 'stroke-linejoin': 'miter',
}, },
}, },
double: { 'double': {
width: 4, width: 4,
height: 6, height: 6,
attrs: { attrs: {

View File

@ -89,19 +89,24 @@ define([
connect: { connect: {
loopbackRadius: 8, loopbackRadius: 8,
lineAttrs: { line: {
'solid': { 'solid': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
}, },
},
'dash': { 'dash': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
'stroke-dasharray': '10, 4', 'stroke-dasharray': '10, 4',
}, },
},
'wave': { 'wave': {
attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
@ -111,6 +116,7 @@ define([
'wave-height': 1, 'wave-height': 1,
}, },
}, },
},
arrow: { arrow: {
single: { single: {
width: 10, width: 10,

View File

@ -12,7 +12,6 @@ define([
'use strict'; 'use strict';
// TODO: // TODO:
// * arrows
// * fade starter/terminator sometimes does not fully cover line // * fade starter/terminator sometimes does not fully cover line
// * blocks (if/else/repeat/ref) // * blocks (if/else/repeat/ref)
@ -33,6 +32,13 @@ define([
return r; return r;
} }
const PENCIL = {
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
};
const SETTINGS = { const SETTINGS = {
titleMargin: 10, titleMargin: 10,
outerMargin: 5, outerMargin: 5,
@ -77,47 +83,50 @@ define([
connect: { connect: {
loopbackRadius: 6, loopbackRadius: 6,
lineAttrs: { line: {
'solid': { 'solid': {
attrs: Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000', }, PENCIL),
'stroke-width': 1, render: null,
renderRev: null,
}, },
'dash': { 'dash': {
attrs: Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-dasharray': '4, 2', 'stroke-dasharray': '4, 2',
}, PENCIL),
render: null,
renderRev: null,
}, },
'wave': { 'wave': {
attrs: Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-linejoin': 'round', 'stroke-linejoin': 'round',
'stroke-linecap': 'round', 'stroke-linecap': 'round',
'wave-width': 6, 'wave-width': 6,
'wave-height': 0.5, 'wave-height': 0.5,
}, PENCIL),
render: null,
renderRev: null,
}, },
}, },
arrow: { arrow: {
single: { 'single': {
width: 5, width: 5,
height: 10,
attrs: {
'fill': '#000000',
'stroke-width': 0,
'stroke-linejoin': 'miter',
},
},
double: {
width: 4,
height: 6, height: 6,
attrs: { attrs: Object.assign({
'fill': 'none', 'fill': 'rgba(0,0,0,0.9)',
'stroke': '#000000', }, PENCIL),
'stroke-width': 1, render: null,
'stroke-linejoin': 'miter',
}, },
'double': {
width: 4,
height: 8,
attrs: Object.assign({
'fill': 'none',
}, PENCIL),
render: null,
}, },
}, },
label: { label: {
@ -330,6 +339,10 @@ define([
this.agentCap.cross.render = this.renderCross.bind(this); this.agentCap.cross.render = this.renderCross.bind(this);
this.agentCap.bar.render = this.renderBar.bind(this); this.agentCap.bar.render = this.renderBar.bind(this);
this.agentCap.box.boxRenderer = this.renderBox.bind(this); this.agentCap.box.boxRenderer = this.renderBox.bind(this);
this.connect.arrow.single.render = this.renderArrowHead.bind(this);
this.connect.arrow.double.render = this.renderArrowHead.bind(this);
this.connect.line.solid.render = this.renderConnect.bind(this);
this.connect.line.dash.render = this.renderConnect.bind(this);
this.notes.note.boxRenderer = this.renderNote.bind(this); this.notes.note.boxRenderer = this.renderNote.bind(this);
this.notes.state.boxRenderer = this.renderState.bind(this); this.notes.state.boxRenderer = this.renderState.bind(this);
} }
@ -374,16 +387,22 @@ define([
return Math.asin(rand * 2 - 1) * 2 * range / Math.PI; return Math.asin(rand * 2 - 1) * 2 * range / Math.PI;
} }
lineNodes(p1, p2, {var1 = 1, var2 = 1, move = true}) { lineNodes(p1, p2, {
var1 = 1,
var2 = 1,
varX = 1,
varY = 1,
move = true,
}) {
const length = Math.sqrt( const length = Math.sqrt(
(p2.x - p1.x) * (p2.x - p1.x) + (p2.x - p1.x) * (p2.x - p1.x) +
(p2.y - p1.y) * (p2.y - p1.y) (p2.y - p1.y) * (p2.y - p1.y)
); );
const rough = Math.min(Math.sqrt(length) * 0.2, 5); const rough = Math.min(Math.sqrt(length) * 0.2, 5);
const x1 = p1.x + this.vary(var1 * rough); const x1 = p1.x + this.vary(var1 * varX * rough);
const y1 = p1.y + this.vary(var1 * rough); const y1 = p1.y + this.vary(var1 * varY * rough);
const x2 = p2.x + this.vary(var2 * rough); const x2 = p2.x + this.vary(var2 * varX * rough);
const y2 = p2.y + this.vary(var2 * rough); const y2 = p2.y + this.vary(var2 * varY * rough);
// -1 = p1 higher, 1 = p2 higher // -1 = p1 higher, 1 = p2 higher
const upper = Math.max(-1, Math.min(1, const upper = Math.max(-1, Math.min(1,
@ -410,14 +429,12 @@ define([
}; };
} }
renderLine(p1, p2, {var1 = 1, var2 = 1}) { renderLine(p1, p2, lineOptions) {
const line = this.lineNodes(p1, p2, {var1, var2}); const line = this.lineNodes(p1, p2, lineOptions);
const shape = svg.make('path', { const shape = svg.make('path', Object.assign({
'd': line.nodes, 'd': line.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none', 'fill': 'none',
}); }, PENCIL));
return shape; return shape;
} }
@ -443,12 +460,10 @@ define([
{var1: 0, var2: 0.3, move: false} {var1: 0, var2: 0.3, move: false}
); );
const shape = svg.make('path', { const shape = svg.make('path', Object.assign({
'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes, 'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': fill || '#FFFFFF', 'fill': fill || '#FFFFFF',
}); }, PENCIL));
return shape; return shape;
} }
@ -492,7 +507,7 @@ define([
); );
const g = svg.make('g'); const g = svg.make('g');
g.appendChild(svg.make('path', { g.appendChild(svg.make('path', Object.assign({
'd': ( 'd': (
lT.nodes + lT.nodes +
lF.nodes + lF.nodes +
@ -500,20 +515,52 @@ define([
lB.nodes + lB.nodes +
lL.nodes lL.nodes
), ),
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': '#FFFFFF', 'fill': '#FFFFFF',
})); }, PENCIL)));
g.appendChild(svg.make('path', { g.appendChild(svg.make('path', Object.assign({
'd': lF1.nodes + lF2.nodes, 'd': lF1.nodes + lF2.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none', 'fill': 'none',
})); }, PENCIL)));
return g; return g;
} }
renderConnect({x1, dx1, x2, dx2, y}, attrs) {
const ln = this.lineNodes(
{x: x1 + dx1, y},
{x: x2 + dx2, y},
{varX: 0.3}
);
return {
shape: svg.make('path', Object.assign({'d': ln.nodes}, attrs)),
p1: {x: ln.p1.x - dx1, y: ln.p2.y},
p2: {x: ln.p2.x - dx2, y: ln.p2.y},
};
}
renderArrowHead({x, y, dx, dy, attrs}) {
const w = dx * (1 + this.vary(0.2));
const h = dy * (1 + this.vary(0.3));
const l1 = this.lineNodes(
{x: x + w, y: y - h},
{x, y},
{var1: 2.0, var2: 0.2}
);
const l2 = this.lineNodes(
l1.p2,
{x: x + w, y: y + h},
{var1: 0, var2: 2.0, move: false}
);
const l3 = (attrs.fill === 'none') ? {nodes: ''} : this.lineNodes(
l2.p2,
l1.p1,
{var1: 0, var2: 0, move: false}
);
return svg.make('path', Object.assign({
'd': l1.nodes + l2.nodes + l3.nodes,
}, attrs));
}
renderState({x, y, width, height}) { renderState({x, y, width, height}) {
// TODO: rounded corners // TODO: rounded corners
return this.renderBox({x, y, width, height}); return this.renderBox({x, y, width, height});
@ -537,12 +584,10 @@ define([
{} {}
); );
return svg.make('path', { return svg.make('path', Object.assign({
'd': l1.nodes + l2.nodes, 'd': l1.nodes + l2.nodes,
'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8,
'fill': 'none', 'fill': 'none',
}); }, PENCIL));
} }
getNote(type) { getNote(type) {
@ -563,7 +608,7 @@ define([
const shape = this.renderLine( const shape = this.renderLine(
{x, y: y0}, {x, y: y0},
{x, y: y1}, {x, y: y1},
{} {varY: 0.3}
); );
shape.setAttribute('class', className); shape.setAttribute('class', className);
container.appendChild(shape); container.appendChild(shape);