Add support for faded connectors

This commit is contained in:
David Evans 2018-05-12 00:34:11 +01:00
parent b25c5dafb4
commit e85890563c
16 changed files with 479 additions and 71 deletions

View File

@ -81,9 +81,12 @@ function findSamples(content) {
* code: (
* 'theme chunky\n' +
* 'define ABC as A, DEF as B\n' +
* 'A is red\n' +
* 'B is blue\n' +
* 'A -> B\n' +
* 'B -> ]\n' +
* '] -> B\n' +
* 'B -~ ]\n' +
* 'divider space with height 0\n' +
* '] ~-> B\n' +
* 'B -> A\n' +
* 'terminators fade'
* ),

View File

@ -598,6 +598,10 @@
'stroke-linejoin': 'miter',
},
},
'fade': {
short: 2,
size: 16,
},
'cross': {
short: 7,
radius: 3,
@ -985,6 +989,10 @@
'stroke-linecap': 'round',
},
},
'fade': {
short: 3,
size: 12,
},
'cross': {
short: 10,
radius: 5,
@ -2960,6 +2968,10 @@
'stroke-linejoin': 'miter',
},
},
'fade': {
short: 2,
size: 10,
},
'cross': {
short: 8,
radius: 4,
@ -4424,6 +4436,7 @@
{tok: '', type: 0},
{tok: '<', type: 1},
{tok: '<<', type: 2},
{tok: '~', type: 3},
];
const mTypes = [
{tok: '-', type: 'solid'},
@ -4434,19 +4447,27 @@
{tok: '', type: 0},
{tok: '>', type: 1},
{tok: '>>', type: 2},
{tok: 'x', type: 3},
{tok: '~', type: 3},
{tok: 'x', type: 4},
];
const arrows = (combine([lTypes, mTypes, rTypes])
.filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0))
);
const types = new Map();
arrows.forEach((arrow) => {
combine([lTypes, mTypes, rTypes]).forEach((arrow) => {
const [left, line, right] = arrow;
if(left.type === 0 && right.type === 0) {
// A line without arrows cannot be a connector
return;
}
if(left.type === 3 && line.type === 'wave' && right.type === 0) {
// ~~ could be fade-wave-none or none-wave-fade
// We allow only none-wave-fade to resolve this
return;
}
types.set(arrow.map((part) => part.tok).join(''), {
left: arrow[0].type,
line: arrow[1].type,
right: arrow[2].type,
left: left.type,
line: line.type,
right: right.type,
});
});
@ -5776,6 +5797,18 @@
'fill': 'transparent',
};
const MASK_PAD = 5;
function applyMask(shape, maskShapes, env, bounds) {
if(!maskShapes.length) {
return;
}
const mask = env.svg.el('mask')
.attr('maskUnits', 'userSpaceOnUse')
.add(env.svg.box({'fill': '#FFFFFF'}, bounds), ...maskShapes);
shape.attr('mask', 'url(#' + env.addDef(mask) + ')');
}
class Arrowhead {
constructor(propName) {
this.propName = propName;
@ -5800,9 +5833,9 @@
}
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
const short = this.short(theme);
render({layer}, env, pt, dir) {
const config = this.getConfig(env.theme);
const short = this.short(env.theme);
layer.add(config.render(config.attrs, {
dir,
height: config.height,
@ -5834,13 +5867,58 @@
}
}
class Arrowfade {
getConfig(theme) {
return theme.connect.arrow.fade;
}
render({lineMask}, env, pt, dir) {
const config = this.getConfig(env.theme);
const {short, size} = config;
let fadeID = null;
const delta = MASK_PAD / (size + MASK_PAD * 2);
if(dir.dx >= 0) {
fadeID = env.addDef('arrowFadeL', () => env.svg.linearGradient({}, [
{'offset': delta * 100 + '%', 'stop-color': '#000000'},
{'offset': (100 - delta * 100) + '%', 'stop-color': '#FFFFFF'},
]));
} else {
fadeID = env.addDef('arrowFadeR', () => env.svg.linearGradient({}, [
{'offset': delta * 100 + '%', 'stop-color': '#FFFFFF'},
{'offset': (100 - delta * 100) + '%', 'stop-color': '#000000'},
]));
}
const p1 = {x: pt.x + dir.dx * short, y: pt.y + dir.dy * short};
const p2 = {x: p1.x + dir.dx * size, y: p1.y + dir.dy * size};
const box = env.svg.box({'fill': 'url(#' + fadeID + ')'}, {
height: Math.abs(p1.y - p2.y) + MASK_PAD * 2,
width: size + MASK_PAD * 2,
x: Math.min(p1.x, p2.x) - MASK_PAD,
y: Math.min(p1.y, p2.y) - MASK_PAD,
});
lineMask.push(box);
}
width(theme) {
return this.getConfig(theme).short;
}
height() {
return 0;
}
lineGap(theme) {
return this.getConfig(theme).short;
}
}
class Arrowcross {
getConfig(theme) {
return theme.connect.arrow.cross;
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
render({layer}, env, pt, dir) {
const config = this.getConfig(env.theme);
layer.add(config.render({
radius: config.radius,
x: pt.x + config.short * dir.dx,
@ -5871,6 +5949,7 @@
},
new Arrowhead('single'),
new Arrowhead('double'),
new Arrowfade(),
new Arrowcross(),
];
@ -5950,8 +6029,9 @@
const dx1 = lArrow.lineGap(env.theme, line);
const dx2 = rArrow.lineGap(env.theme, line);
const rad = env.theme.connect.loopbackRadius;
const rendered = line.renderRev({
rad: env.theme.connect.loopbackRadius,
rad,
x1: x1 + dx1,
x2: x2 + dx2,
xR,
@ -5960,15 +6040,24 @@
});
clickable.add(rendered.shape);
lArrow.render(clickable, env.theme, {
const lineMask = [];
lArrow.render({layer: clickable, lineMask}, env, {
x: rendered.p1.x - dx1,
y: rendered.p1.y,
}, {dx: 1, dy: 0});
rArrow.render(clickable, env.theme, {
rArrow.render({layer: clickable, lineMask}, env, {
x: rendered.p2.x - dx2,
y: rendered.p2.y,
}, {dx: 1, dy: 0});
applyMask(rendered.shape, lineMask, env, {
height: y2 - y1 + MASK_PAD * 2,
width: xR + rad - Math.min(x1, x2) + MASK_PAD * 2,
x: Math.min(x1, x2) - MASK_PAD,
y: y1 - MASK_PAD,
});
}
renderSelfConnect({label, agentIDs, options}, env, from, yBegin) {
@ -6066,8 +6155,20 @@
const p1 = {x: rendered.p1.x - d1 * dx, y: rendered.p1.y - d1 * dy};
const p2 = {x: rendered.p2.x + d2 * dx, y: rendered.p2.y + d2 * dy};
lArrow.render(clickable, env.theme, p1, {dx, dy});
rArrow.render(clickable, env.theme, p2, {dx: -dx, dy: -dy});
const lineMask = [];
lArrow.render({layer: clickable, lineMask}, env, p1, {dx, dy});
rArrow.render({layer: clickable, lineMask}, env, p2, {
dx: -dx,
dy: -dy,
});
applyMask(rendered.shape, lineMask, env, {
height: Math.abs(y2 - y1) + MASK_PAD * 2,
width: Math.abs(x2 - x1) + MASK_PAD * 2,
x: Math.min(x1, x2) - MASK_PAD,
y: Math.min(y1, y2) - MASK_PAD,
});
return {
lArrow,
@ -9044,6 +9145,10 @@
}, PENCIL.normal),
render: this.renderArrowHead.bind(this),
},
'fade': {
short: 0,
size: 12,
},
'cross': {
short: 5,
radius: 3,

File diff suppressed because one or more lines are too long

View File

@ -598,6 +598,10 @@
'stroke-linejoin': 'miter',
},
},
'fade': {
short: 2,
size: 16,
},
'cross': {
short: 7,
radius: 3,
@ -985,6 +989,10 @@
'stroke-linecap': 'round',
},
},
'fade': {
short: 3,
size: 12,
},
'cross': {
short: 10,
radius: 5,
@ -2960,6 +2968,10 @@
'stroke-linejoin': 'miter',
},
},
'fade': {
short: 2,
size: 10,
},
'cross': {
short: 8,
radius: 4,
@ -4424,6 +4436,7 @@
{tok: '', type: 0},
{tok: '<', type: 1},
{tok: '<<', type: 2},
{tok: '~', type: 3},
];
const mTypes = [
{tok: '-', type: 'solid'},
@ -4434,19 +4447,27 @@
{tok: '', type: 0},
{tok: '>', type: 1},
{tok: '>>', type: 2},
{tok: 'x', type: 3},
{tok: '~', type: 3},
{tok: 'x', type: 4},
];
const arrows = (combine([lTypes, mTypes, rTypes])
.filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0))
);
const types = new Map();
arrows.forEach((arrow) => {
combine([lTypes, mTypes, rTypes]).forEach((arrow) => {
const [left, line, right] = arrow;
if(left.type === 0 && right.type === 0) {
// A line without arrows cannot be a connector
return;
}
if(left.type === 3 && line.type === 'wave' && right.type === 0) {
// ~~ could be fade-wave-none or none-wave-fade
// We allow only none-wave-fade to resolve this
return;
}
types.set(arrow.map((part) => part.tok).join(''), {
left: arrow[0].type,
line: arrow[1].type,
right: arrow[2].type,
left: left.type,
line: line.type,
right: right.type,
});
});
@ -5776,6 +5797,18 @@
'fill': 'transparent',
};
const MASK_PAD = 5;
function applyMask(shape, maskShapes, env, bounds) {
if(!maskShapes.length) {
return;
}
const mask = env.svg.el('mask')
.attr('maskUnits', 'userSpaceOnUse')
.add(env.svg.box({'fill': '#FFFFFF'}, bounds), ...maskShapes);
shape.attr('mask', 'url(#' + env.addDef(mask) + ')');
}
class Arrowhead {
constructor(propName) {
this.propName = propName;
@ -5800,9 +5833,9 @@
}
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
const short = this.short(theme);
render({layer}, env, pt, dir) {
const config = this.getConfig(env.theme);
const short = this.short(env.theme);
layer.add(config.render(config.attrs, {
dir,
height: config.height,
@ -5834,13 +5867,58 @@
}
}
class Arrowfade {
getConfig(theme) {
return theme.connect.arrow.fade;
}
render({lineMask}, env, pt, dir) {
const config = this.getConfig(env.theme);
const {short, size} = config;
let fadeID = null;
const delta = MASK_PAD / (size + MASK_PAD * 2);
if(dir.dx >= 0) {
fadeID = env.addDef('arrowFadeL', () => env.svg.linearGradient({}, [
{'offset': delta * 100 + '%', 'stop-color': '#000000'},
{'offset': (100 - delta * 100) + '%', 'stop-color': '#FFFFFF'},
]));
} else {
fadeID = env.addDef('arrowFadeR', () => env.svg.linearGradient({}, [
{'offset': delta * 100 + '%', 'stop-color': '#FFFFFF'},
{'offset': (100 - delta * 100) + '%', 'stop-color': '#000000'},
]));
}
const p1 = {x: pt.x + dir.dx * short, y: pt.y + dir.dy * short};
const p2 = {x: p1.x + dir.dx * size, y: p1.y + dir.dy * size};
const box = env.svg.box({'fill': 'url(#' + fadeID + ')'}, {
height: Math.abs(p1.y - p2.y) + MASK_PAD * 2,
width: size + MASK_PAD * 2,
x: Math.min(p1.x, p2.x) - MASK_PAD,
y: Math.min(p1.y, p2.y) - MASK_PAD,
});
lineMask.push(box);
}
width(theme) {
return this.getConfig(theme).short;
}
height() {
return 0;
}
lineGap(theme) {
return this.getConfig(theme).short;
}
}
class Arrowcross {
getConfig(theme) {
return theme.connect.arrow.cross;
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
render({layer}, env, pt, dir) {
const config = this.getConfig(env.theme);
layer.add(config.render({
radius: config.radius,
x: pt.x + config.short * dir.dx,
@ -5871,6 +5949,7 @@
},
new Arrowhead('single'),
new Arrowhead('double'),
new Arrowfade(),
new Arrowcross(),
];
@ -5950,8 +6029,9 @@
const dx1 = lArrow.lineGap(env.theme, line);
const dx2 = rArrow.lineGap(env.theme, line);
const rad = env.theme.connect.loopbackRadius;
const rendered = line.renderRev({
rad: env.theme.connect.loopbackRadius,
rad,
x1: x1 + dx1,
x2: x2 + dx2,
xR,
@ -5960,15 +6040,24 @@
});
clickable.add(rendered.shape);
lArrow.render(clickable, env.theme, {
const lineMask = [];
lArrow.render({layer: clickable, lineMask}, env, {
x: rendered.p1.x - dx1,
y: rendered.p1.y,
}, {dx: 1, dy: 0});
rArrow.render(clickable, env.theme, {
rArrow.render({layer: clickable, lineMask}, env, {
x: rendered.p2.x - dx2,
y: rendered.p2.y,
}, {dx: 1, dy: 0});
applyMask(rendered.shape, lineMask, env, {
height: y2 - y1 + MASK_PAD * 2,
width: xR + rad - Math.min(x1, x2) + MASK_PAD * 2,
x: Math.min(x1, x2) - MASK_PAD,
y: y1 - MASK_PAD,
});
}
renderSelfConnect({label, agentIDs, options}, env, from, yBegin) {
@ -6066,8 +6155,20 @@
const p1 = {x: rendered.p1.x - d1 * dx, y: rendered.p1.y - d1 * dy};
const p2 = {x: rendered.p2.x + d2 * dx, y: rendered.p2.y + d2 * dy};
lArrow.render(clickable, env.theme, p1, {dx, dy});
rArrow.render(clickable, env.theme, p2, {dx: -dx, dy: -dy});
const lineMask = [];
lArrow.render({layer: clickable, lineMask}, env, p1, {dx, dy});
rArrow.render({layer: clickable, lineMask}, env, p2, {
dx: -dx,
dy: -dy,
});
applyMask(rendered.shape, lineMask, env, {
height: Math.abs(y2 - y1) + MASK_PAD * 2,
width: Math.abs(x2 - x1) + MASK_PAD * 2,
x: Math.min(x1, x2) - MASK_PAD,
y: Math.min(y1, y2) - MASK_PAD,
});
return {
lArrow,
@ -9044,6 +9145,10 @@
}, PENCIL.normal),
render: this.renderArrowHead.bind(this),
},
'fade': {
short: 0,
size: 12,
},
'cross': {
short: 5,
radius: 3,

View File

@ -42,6 +42,7 @@ const CONNECT = {
{tok: '', type: 0},
{tok: '<', type: 1},
{tok: '<<', type: 2},
{tok: '~', type: 3},
];
const mTypes = [
{tok: '-', type: 'solid'},
@ -52,19 +53,27 @@ const CONNECT = {
{tok: '', type: 0},
{tok: '>', type: 1},
{tok: '>>', type: 2},
{tok: 'x', type: 3},
{tok: '~', type: 3},
{tok: 'x', type: 4},
];
const arrows = (combine([lTypes, mTypes, rTypes])
.filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0))
);
const types = new Map();
arrows.forEach((arrow) => {
combine([lTypes, mTypes, rTypes]).forEach((arrow) => {
const [left, line, right] = arrow;
if(left.type === 0 && right.type === 0) {
// A line without arrows cannot be a connector
return;
}
if(left.type === 3 && line.type === 'wave' && right.type === 0) {
// ~~ could be fade-wave-none or none-wave-fade
// We allow only none-wave-fade to resolve this
return;
}
types.set(arrow.map((part) => part.tok).join(''), {
left: arrow[0].type,
line: arrow[1].type,
right: arrow[2].type,
left: left.type,
line: line.type,
right: right.type,
});
});

View File

@ -453,31 +453,62 @@ describe('Sequence Parser', () => {
const parsed = parser.parse(
'A->B\n' +
'A->>B\n' +
'A-~B\n' +
'A-xB\n' +
'A<-B\n' +
'A<->B\n' +
'A<->>B\n' +
'A<-~B\n' +
'A<-xB\n' +
'A<<-B\n' +
'A<<->B\n' +
'A<<->>B\n' +
'A-xB\n' +
'A<<-~B\n' +
'A<<-xB\n' +
'A~-B\n' +
'A~->B\n' +
'A~->>B\n' +
'A~-~B\n' +
'A~-xB\n' +
'A-->B\n' +
'A-->>B\n' +
'A--~B\n' +
'A--xB\n' +
'A<--B\n' +
'A<-->B\n' +
'A<-->>B\n' +
'A<--~B\n' +
'A<--xB\n' +
'A<<--B\n' +
'A<<-->B\n' +
'A<<-->>B\n' +
'A--xB\n' +
'A<<--~B\n' +
'A<<--xB\n' +
'A~--B\n' +
'A~-->B\n' +
'A~-->>B\n' +
'A~--~B\n' +
'A~--xB\n' +
'A~>B\n' +
'A~>>B\n' +
'A~~B\n' +
'A~xB\n' +
'A<~B\n' +
'A<~>B\n' +
'A<~>>B\n' +
'A<~~B\n' +
'A<~xB\n' +
'A<<~B\n' +
'A<<~>B\n' +
'A<<~>>B\n' +
'A~xB\n'
'A<<~~B\n' +
'A<<~xB\n' +
'A~~>B\n' +
'A~~>>B\n' +
'A~~~B\n' +
'A~~xB\n'
);
expect(parsed.stages).toEqual([
@ -488,31 +519,62 @@ describe('Sequence Parser', () => {
right: 1,
}),
PARSED.connect(['A', 'B'], {left: 0, line: 'solid', right: 2}),
PARSED.connect(['A', 'B'], {left: 0, line: 'solid', right: 3}),
PARSED.connect(['A', 'B'], {left: 0, line: 'solid', right: 4}),
PARSED.connect(['A', 'B'], {left: 1, line: 'solid', right: 0}),
PARSED.connect(['A', 'B'], {left: 1, line: 'solid', right: 1}),
PARSED.connect(['A', 'B'], {left: 1, line: 'solid', right: 2}),
PARSED.connect(['A', 'B'], {left: 1, line: 'solid', right: 3}),
PARSED.connect(['A', 'B'], {left: 1, line: 'solid', right: 4}),
PARSED.connect(['A', 'B'], {left: 2, line: 'solid', right: 0}),
PARSED.connect(['A', 'B'], {left: 2, line: 'solid', right: 1}),
PARSED.connect(['A', 'B'], {left: 2, line: 'solid', right: 2}),
PARSED.connect(['A', 'B'], {left: 0, line: 'solid', right: 3}),
PARSED.connect(['A', 'B'], {left: 2, line: 'solid', right: 3}),
PARSED.connect(['A', 'B'], {left: 2, line: 'solid', right: 4}),
PARSED.connect(['A', 'B'], {left: 3, line: 'solid', right: 0}),
PARSED.connect(['A', 'B'], {left: 3, line: 'solid', right: 1}),
PARSED.connect(['A', 'B'], {left: 3, line: 'solid', right: 2}),
PARSED.connect(['A', 'B'], {left: 3, line: 'solid', right: 3}),
PARSED.connect(['A', 'B'], {left: 3, line: 'solid', right: 4}),
PARSED.connect(['A', 'B'], {left: 0, line: 'dash', right: 1}),
PARSED.connect(['A', 'B'], {left: 0, line: 'dash', right: 2}),
PARSED.connect(['A', 'B'], {left: 0, line: 'dash', right: 3}),
PARSED.connect(['A', 'B'], {left: 0, line: 'dash', right: 4}),
PARSED.connect(['A', 'B'], {left: 1, line: 'dash', right: 0}),
PARSED.connect(['A', 'B'], {left: 1, line: 'dash', right: 1}),
PARSED.connect(['A', 'B'], {left: 1, line: 'dash', right: 2}),
PARSED.connect(['A', 'B'], {left: 1, line: 'dash', right: 3}),
PARSED.connect(['A', 'B'], {left: 1, line: 'dash', right: 4}),
PARSED.connect(['A', 'B'], {left: 2, line: 'dash', right: 0}),
PARSED.connect(['A', 'B'], {left: 2, line: 'dash', right: 1}),
PARSED.connect(['A', 'B'], {left: 2, line: 'dash', right: 2}),
PARSED.connect(['A', 'B'], {left: 0, line: 'dash', right: 3}),
PARSED.connect(['A', 'B'], {left: 2, line: 'dash', right: 3}),
PARSED.connect(['A', 'B'], {left: 2, line: 'dash', right: 4}),
PARSED.connect(['A', 'B'], {left: 3, line: 'dash', right: 0}),
PARSED.connect(['A', 'B'], {left: 3, line: 'dash', right: 1}),
PARSED.connect(['A', 'B'], {left: 3, line: 'dash', right: 2}),
PARSED.connect(['A', 'B'], {left: 3, line: 'dash', right: 3}),
PARSED.connect(['A', 'B'], {left: 3, line: 'dash', right: 4}),
PARSED.connect(['A', 'B'], {left: 0, line: 'wave', right: 1}),
PARSED.connect(['A', 'B'], {left: 0, line: 'wave', right: 2}),
PARSED.connect(['A', 'B'], {left: 0, line: 'wave', right: 3}),
PARSED.connect(['A', 'B'], {left: 0, line: 'wave', right: 4}),
PARSED.connect(['A', 'B'], {left: 1, line: 'wave', right: 0}),
PARSED.connect(['A', 'B'], {left: 1, line: 'wave', right: 1}),
PARSED.connect(['A', 'B'], {left: 1, line: 'wave', right: 2}),
PARSED.connect(['A', 'B'], {left: 1, line: 'wave', right: 3}),
PARSED.connect(['A', 'B'], {left: 1, line: 'wave', right: 4}),
PARSED.connect(['A', 'B'], {left: 2, line: 'wave', right: 0}),
PARSED.connect(['A', 'B'], {left: 2, line: 'wave', right: 1}),
PARSED.connect(['A', 'B'], {left: 2, line: 'wave', right: 2}),
PARSED.connect(['A', 'B'], {left: 0, line: 'wave', right: 3}),
PARSED.connect(['A', 'B'], {left: 2, line: 'wave', right: 3}),
PARSED.connect(['A', 'B'], {left: 2, line: 'wave', right: 4}),
PARSED.connect(['A', 'B'], {left: 3, line: 'wave', right: 1}),
PARSED.connect(['A', 'B'], {left: 3, line: 'wave', right: 2}),
PARSED.connect(['A', 'B'], {left: 3, line: 'wave', right: 3}),
PARSED.connect(['A', 'B'], {left: 3, line: 'wave', right: 4}),
]);
});

View File

@ -6,6 +6,18 @@ const OUTLINE_ATTRS = {
'fill': 'transparent',
};
const MASK_PAD = 5;
function applyMask(shape, maskShapes, env, bounds) {
if(!maskShapes.length) {
return;
}
const mask = env.svg.el('mask')
.attr('maskUnits', 'userSpaceOnUse')
.add(env.svg.box({'fill': '#FFFFFF'}, bounds), ...maskShapes);
shape.attr('mask', 'url(#' + env.addDef(mask) + ')');
}
class Arrowhead {
constructor(propName) {
this.propName = propName;
@ -30,9 +42,9 @@ class Arrowhead {
}
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
const short = this.short(theme);
render({layer}, env, pt, dir) {
const config = this.getConfig(env.theme);
const short = this.short(env.theme);
layer.add(config.render(config.attrs, {
dir,
height: config.height,
@ -64,13 +76,58 @@ class Arrowhead {
}
}
class Arrowfade {
getConfig(theme) {
return theme.connect.arrow.fade;
}
render({lineMask}, env, pt, dir) {
const config = this.getConfig(env.theme);
const {short, size} = config;
let fadeID = null;
const delta = MASK_PAD / (size + MASK_PAD * 2);
if(dir.dx >= 0) {
fadeID = env.addDef('arrowFadeL', () => env.svg.linearGradient({}, [
{'offset': delta * 100 + '%', 'stop-color': '#000000'},
{'offset': (100 - delta * 100) + '%', 'stop-color': '#FFFFFF'},
]));
} else {
fadeID = env.addDef('arrowFadeR', () => env.svg.linearGradient({}, [
{'offset': delta * 100 + '%', 'stop-color': '#FFFFFF'},
{'offset': (100 - delta * 100) + '%', 'stop-color': '#000000'},
]));
}
const p1 = {x: pt.x + dir.dx * short, y: pt.y + dir.dy * short};
const p2 = {x: p1.x + dir.dx * size, y: p1.y + dir.dy * size};
const box = env.svg.box({'fill': 'url(#' + fadeID + ')'}, {
height: Math.abs(p1.y - p2.y) + MASK_PAD * 2,
width: size + MASK_PAD * 2,
x: Math.min(p1.x, p2.x) - MASK_PAD,
y: Math.min(p1.y, p2.y) - MASK_PAD,
});
lineMask.push(box);
}
width(theme) {
return this.getConfig(theme).short;
}
height() {
return 0;
}
lineGap(theme) {
return this.getConfig(theme).short;
}
}
class Arrowcross {
getConfig(theme) {
return theme.connect.arrow.cross;
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
render({layer}, env, pt, dir) {
const config = this.getConfig(env.theme);
layer.add(config.render({
radius: config.radius,
x: pt.x + config.short * dir.dx,
@ -101,6 +158,7 @@ const ARROWHEADS = [
},
new Arrowhead('single'),
new Arrowhead('double'),
new Arrowfade(),
new Arrowcross(),
];
@ -180,8 +238,9 @@ export class Connect extends BaseComponent {
const dx1 = lArrow.lineGap(env.theme, line);
const dx2 = rArrow.lineGap(env.theme, line);
const rad = env.theme.connect.loopbackRadius;
const rendered = line.renderRev({
rad: env.theme.connect.loopbackRadius,
rad,
x1: x1 + dx1,
x2: x2 + dx2,
xR,
@ -190,15 +249,24 @@ export class Connect extends BaseComponent {
});
clickable.add(rendered.shape);
lArrow.render(clickable, env.theme, {
const lineMask = [];
lArrow.render({layer: clickable, lineMask}, env, {
x: rendered.p1.x - dx1,
y: rendered.p1.y,
}, {dx: 1, dy: 0});
rArrow.render(clickable, env.theme, {
rArrow.render({layer: clickable, lineMask}, env, {
x: rendered.p2.x - dx2,
y: rendered.p2.y,
}, {dx: 1, dy: 0});
applyMask(rendered.shape, lineMask, env, {
height: y2 - y1 + MASK_PAD * 2,
width: xR + rad - Math.min(x1, x2) + MASK_PAD * 2,
x: Math.min(x1, x2) - MASK_PAD,
y: y1 - MASK_PAD,
});
}
renderSelfConnect({label, agentIDs, options}, env, from, yBegin) {
@ -296,8 +364,20 @@ export class Connect extends BaseComponent {
const p1 = {x: rendered.p1.x - d1 * dx, y: rendered.p1.y - d1 * dy};
const p2 = {x: rendered.p2.x + d2 * dx, y: rendered.p2.y + d2 * dy};
lArrow.render(clickable, env.theme, p1, {dx, dy});
rArrow.render(clickable, env.theme, p2, {dx: -dx, dy: -dy});
const lineMask = [];
lArrow.render({layer: clickable, lineMask}, env, p1, {dx, dy});
rArrow.render({layer: clickable, lineMask}, env, p2, {
dx: -dx,
dy: -dy,
});
applyMask(rendered.shape, lineMask, env, {
height: Math.abs(y2 - y1) + MASK_PAD * 2,
width: Math.abs(x2 - x1) + MASK_PAD * 2,
x: Math.min(x1, x2) - MASK_PAD,
y: Math.min(y1, y2) - MASK_PAD,
});
return {
lArrow,

View File

@ -170,6 +170,10 @@ export default class BasicTheme extends BaseTheme {
'stroke-linejoin': 'miter',
},
},
'fade': {
short: 2,
size: 16,
},
'cross': {
short: 7,
radius: 3,

View File

@ -179,6 +179,10 @@ export default class ChunkyTheme extends BaseTheme {
'stroke-linecap': 'round',
},
},
'fade': {
short: 3,
size: 12,
},
'cross': {
short: 10,
radius: 5,

View File

@ -170,6 +170,10 @@ export default class MonospaceTheme extends BaseTheme {
'stroke-linejoin': 'miter',
},
},
'fade': {
short: 2,
size: 10,
},
'cross': {
short: 8,
radius: 4,

View File

@ -201,6 +201,10 @@ export default class SketchTheme extends BaseTheme {
}, PENCIL.normal),
render: this.renderArrowHead.bind(this),
},
'fade': {
short: 0,
size: 12,
},
'cross': {
short: 5,
radius: 3,

19
spec/images/Fade.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,14 +1,15 @@
export default [
'Connect.svg',
'SelfConnect.svg',
'Divider.svg',
'DividerMasking.svg',
'AgentOptions.svg',
'Asynchronous.svg',
'Block.svg',
'CollapsedBlocks.svg',
'Connect.svg',
'Divider.svg',
'DividerMasking.svg',
'Fade.svg',
'Markdown.svg',
'Parallel.svg',
'Reference.svg',
'ReferenceLayering.svg',
'Markdown.svg',
'AgentOptions.svg',
'Parallel.svg',
'SelfConnect.svg',
];

View File

@ -200,6 +200,10 @@
code: '[ -> {Agent1}: {Message1}\n{Agent1} -> ]: {Message2}',
title: 'Arrows to/from the sides',
},
{
code: '{Agent1} -~ ]: {Message1}\n{Agent1} <-~ ]: {Message2}',
title: 'Fading arrows',
},
{
code: 'text right: {Message}',
preview: (

File diff suppressed because one or more lines are too long

View File

@ -197,6 +197,10 @@ export default [
code: '[ -> {Agent1}: {Message1}\n{Agent1} -> ]: {Message2}',
title: 'Arrows to/from the sides',
},
{
code: '{Agent1} -~ ]: {Message1}\n{Agent1} <-~ ]: {Message2}',
title: 'Fading arrows',
},
{
code: 'text right: {Message}',
preview: (