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

View File

@ -82,30 +82,36 @@ define([
connect: {
loopbackRadius: 6,
lineAttrs: {
line: {
'solid': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
attrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
},
},
'dash': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-dasharray': '4, 2',
attrs: {
'fill': 'none',
'stroke': '#000000',
'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,
attrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 6,
'wave-height': 0.5,
},
},
},
arrow: {
single: {
'single': {
width: 5,
height: 10,
attrs: {
@ -114,7 +120,7 @@ define([
'stroke-linejoin': 'miter',
},
},
double: {
'double': {
width: 4,
height: 6,
attrs: {

View File

@ -89,26 +89,32 @@ define([
connect: {
loopbackRadius: 8,
lineAttrs: {
line: {
'solid': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
attrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
},
},
'dash': {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
'stroke-dasharray': '10, 4',
attrs: {
'fill': 'none',
'stroke': '#000000',
'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,
attrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
'wave-width': 10,
'wave-height': 1,
},
},
},
arrow: {

View File

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