Add support for faded connectors
This commit is contained in:
parent
b25c5dafb4
commit
e85890563c
|
@ -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'
|
||||
* ),
|
||||
|
|
|
@ -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
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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}),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -170,6 +170,10 @@ export default class BasicTheme extends BaseTheme {
|
|||
'stroke-linejoin': 'miter',
|
||||
},
|
||||
},
|
||||
'fade': {
|
||||
short: 2,
|
||||
size: 16,
|
||||
},
|
||||
'cross': {
|
||||
short: 7,
|
||||
radius: 3,
|
||||
|
|
|
@ -179,6 +179,10 @@ export default class ChunkyTheme extends BaseTheme {
|
|||
'stroke-linecap': 'round',
|
||||
},
|
||||
},
|
||||
'fade': {
|
||||
short: 3,
|
||||
size: 12,
|
||||
},
|
||||
'cross': {
|
||||
short: 10,
|
||||
radius: 5,
|
||||
|
|
|
@ -170,6 +170,10 @@ export default class MonospaceTheme extends BaseTheme {
|
|||
'stroke-linejoin': 'miter',
|
||||
},
|
||||
},
|
||||
'fade': {
|
||||
short: 2,
|
||||
size: 10,
|
||||
},
|
||||
'cross': {
|
||||
short: 8,
|
||||
radius: 4,
|
||||
|
|
|
@ -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,
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
|
@ -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',
|
||||
];
|
||||
|
|
|
@ -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
|
@ -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: (
|
||||
|
|
Loading…
Reference in New Issue