Add alternative arrow types [#20]
27
README.md
|
@ -40,25 +40,26 @@ terminators box
|
||||||
```
|
```
|
||||||
title Connection Types
|
title Connection Types
|
||||||
|
|
||||||
|
begin Foo, Bar, Baz
|
||||||
|
|
||||||
Foo -> Bar: Simple arrow
|
Foo -> Bar: Simple arrow
|
||||||
Foo --> Bar: Dashed arrow
|
Bar --> Baz: Dashed arrow
|
||||||
Foo <- Bar: Reversed arrow
|
Foo <- Bar: Reversed arrow
|
||||||
Foo <-- Bar: Reversed dashed arrow
|
Bar <-- Baz: Reversed & dashed
|
||||||
Foo <-> Bar: Double arrow
|
Foo <-> Bar: Double arrow
|
||||||
Foo <--> Bar: Double dashed arrow
|
Bar <--> Baz: Double dashed arrow
|
||||||
|
|
||||||
# An arrow with no label:
|
# An arrow with no label:
|
||||||
Foo -> Bar
|
Foo -> Bar
|
||||||
|
|
||||||
Foo -> Foo: Foo talks to itself
|
Bar ->> Baz: Different arrow
|
||||||
|
Foo <<--> Bar: Mix of arrows
|
||||||
|
|
||||||
|
Bar -> Bar: Bar talks to itself
|
||||||
|
|
||||||
Foo -> +Bar: Foo asks Bar
|
Foo -> +Bar: Foo asks Bar
|
||||||
-Bar --> Foo: and Bar replies
|
-Bar --> Foo: and Bar replies
|
||||||
|
|
||||||
# * and ! cause agents to be created and destroyed inline
|
|
||||||
Bar -> *Baz
|
|
||||||
Bar <- !Baz
|
|
||||||
|
|
||||||
# Arrows leaving on the left and right of the diagram
|
# Arrows leaving on the left and right of the diagram
|
||||||
[ -> Foo: From the left
|
[ -> Foo: From the left
|
||||||
[ <- Foo: To the left
|
[ <- Foo: To the left
|
||||||
|
@ -139,12 +140,18 @@ too!'
|
||||||
```
|
```
|
||||||
title "Baz doesn't live long"
|
title "Baz doesn't live long"
|
||||||
|
|
||||||
Foo -> Bar
|
note over Foo, Bar: Using begin / end
|
||||||
|
|
||||||
begin Baz
|
begin Baz
|
||||||
Bar -> Baz
|
Bar -> Baz
|
||||||
Baz -> Foo
|
Baz -> Foo
|
||||||
end Baz
|
end Baz
|
||||||
Foo -> Bar
|
|
||||||
|
note over Foo, Bar: Using * / !
|
||||||
|
|
||||||
|
# * and ! cause agents to be created and destroyed inline
|
||||||
|
Bar -> *Baz: make Baz
|
||||||
|
Foo <- !Baz: end Baz
|
||||||
|
|
||||||
# Foo and Bar end with black bars
|
# Foo and Bar end with black bars
|
||||||
terminators bar
|
terminators bar
|
||||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
@ -56,6 +56,27 @@ define(() => {
|
||||||
return list[list.length - 1];
|
return list[list.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function combineRecur(parts, position, str, target) {
|
||||||
|
if(position >= parts.length) {
|
||||||
|
target.push(str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const choices = parts[position];
|
||||||
|
if(!Array.isArray(choices)) {
|
||||||
|
combineRecur(parts, position + 1, str + choices, target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(let i = 0; i < choices.length; ++ i) {
|
||||||
|
combineRecur(parts, position + 1, str + choices[i], target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function combine(parts) {
|
||||||
|
const target = [];
|
||||||
|
combineRecur(parts, 0, '', target);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
indexOf,
|
indexOf,
|
||||||
mergeSets,
|
mergeSets,
|
||||||
|
@ -63,5 +84,6 @@ define(() => {
|
||||||
removeAll,
|
removeAll,
|
||||||
remove,
|
remove,
|
||||||
last,
|
last,
|
||||||
|
combine,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -169,4 +169,21 @@ defineDescribe('ArrayUtilities', ['./ArrayUtilities'], (array) => {
|
||||||
expect(array.last([])).toEqual(undefined);
|
expect(array.last([])).toEqual(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('.combine', () => {
|
||||||
|
it('returns all combinations of the given arguments', () => {
|
||||||
|
const list = array.combine([
|
||||||
|
['Aa', 'Bb'],
|
||||||
|
['Cc', 'Dd'],
|
||||||
|
'Ee',
|
||||||
|
['Ff'],
|
||||||
|
]);
|
||||||
|
expect(list).toEqual([
|
||||||
|
'AaCcEeFf',
|
||||||
|
'AaDdEeFf',
|
||||||
|
'BbCcEeFf',
|
||||||
|
'BbDdEeFf',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,11 +7,12 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
const end = {type: '', suggest: '\n', then: {}};
|
const end = {type: '', suggest: '\n', then: {}};
|
||||||
const hiddenEnd = {type: '', then: {}};
|
const hiddenEnd = {type: '', then: {}};
|
||||||
|
|
||||||
const ARROWS = [
|
const ARROWS = array.combine([
|
||||||
'->', '-->',
|
['', '<', '<<'],
|
||||||
'<-', '<--',
|
['-', '--'],
|
||||||
'<->', '<-->',
|
['', '>', '>>'],
|
||||||
];
|
]);
|
||||||
|
array.removeAll(ARROWS, ['-', '--']);
|
||||||
|
|
||||||
const textToEnd = {type: 'string', then: {'': 0, '\n': end}};
|
const textToEnd = {type: 'string', then: {'': 0, '\n': end}};
|
||||||
const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
||||||
|
|
|
@ -52,8 +52,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
connect: (agentNames, {
|
connect: (agentNames, {
|
||||||
label = '',
|
label = '',
|
||||||
line = '',
|
line = '',
|
||||||
left = false,
|
left = 0,
|
||||||
right = false,
|
right = 0,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
return {
|
return {
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
|
@ -288,8 +288,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
PARSED.connect(['A', 'B'], {
|
PARSED.connect(['A', 'B'], {
|
||||||
label: 'foo',
|
label: 'foo',
|
||||||
line: 'bar',
|
line: 'bar',
|
||||||
left: true,
|
left: 1,
|
||||||
right: false,
|
right: 0,
|
||||||
}),
|
}),
|
||||||
]});
|
]});
|
||||||
expect(sequence.stages).toEqual([
|
expect(sequence.stages).toEqual([
|
||||||
|
@ -297,8 +297,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
GENERATED.connect(['A', 'B'], {
|
GENERATED.connect(['A', 'B'], {
|
||||||
label: 'foo',
|
label: 'foo',
|
||||||
line: 'bar',
|
line: 'bar',
|
||||||
left: true,
|
left: 1,
|
||||||
right: false,
|
right: 0,
|
||||||
}),
|
}),
|
||||||
jasmine.anything(),
|
jasmine.anything(),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -12,18 +12,28 @@ define([
|
||||||
const BLOCK_TYPES = {
|
const BLOCK_TYPES = {
|
||||||
'if': {type: 'block begin', mode: 'if', skip: []},
|
'if': {type: 'block begin', mode: 'if', skip: []},
|
||||||
'else': {type: 'block split', mode: 'else', skip: ['if']},
|
'else': {type: 'block split', mode: 'else', skip: ['if']},
|
||||||
'elif': {type: 'block split', mode: 'else', skip: []},
|
|
||||||
'repeat': {type: 'block begin', mode: 'repeat', skip: []},
|
'repeat': {type: 'block begin', mode: 'repeat', skip: []},
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONNECT_TYPES = {
|
const CONNECT_TYPES = ((() => {
|
||||||
'->': {line: 'solid', left: false, right: true},
|
const lTypes = ['', '<', '<<'];
|
||||||
'<-': {line: 'solid', left: true, right: false},
|
const mTypes = ['-', '--'];
|
||||||
'<->': {line: 'solid', left: true, right: true},
|
const rTypes = ['', '>', '>>'];
|
||||||
'-->': {line: 'dash', left: false, right: true},
|
const arrows = array.combine([lTypes, mTypes, rTypes]);
|
||||||
'<--': {line: 'dash', left: true, right: false},
|
array.removeAll(arrows, mTypes);
|
||||||
'<-->': {line: 'dash', left: true, right: true},
|
|
||||||
};
|
const types = new Map();
|
||||||
|
|
||||||
|
arrows.forEach((arrow) => {
|
||||||
|
types.set(arrow, {
|
||||||
|
line: arrow.includes('--') ? 'dash' : 'solid',
|
||||||
|
left: lTypes.indexOf(arrow.substr(0, arrow.indexOf('-'))),
|
||||||
|
right: rTypes.indexOf(arrow.substr(arrow.lastIndexOf('-') + 1)),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return types;
|
||||||
|
})());
|
||||||
|
|
||||||
const CONNECT_AGENT_FLAGS = {
|
const CONNECT_AGENT_FLAGS = {
|
||||||
'*': 'begin',
|
'*': 'begin',
|
||||||
|
@ -323,7 +333,7 @@ define([
|
||||||
let typePos = -1;
|
let typePos = -1;
|
||||||
let options = null;
|
let options = null;
|
||||||
for(let j = 0; j < line.length; ++ j) {
|
for(let j = 0; j < line.length; ++ j) {
|
||||||
const opts = CONNECT_TYPES[tokenKeyword(line[j])];
|
const opts = CONNECT_TYPES.get(tokenKeyword(line[j]));
|
||||||
if(opts) {
|
if(opts) {
|
||||||
typePos = j;
|
typePos = j;
|
||||||
options = opts;
|
options = opts;
|
||||||
|
|
|
@ -200,45 +200,40 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
'A <-> B\n' +
|
'A <-> B\n' +
|
||||||
'A --> B\n' +
|
'A --> B\n' +
|
||||||
'A <-- B\n' +
|
'A <-- B\n' +
|
||||||
'A <--> B\n'
|
'A <--> B\n' +
|
||||||
|
'A ->> B\n' +
|
||||||
|
'A <<- B\n' +
|
||||||
|
'A <<->> B\n' +
|
||||||
|
'A <->> B\n' +
|
||||||
|
'A <<-> B\n' +
|
||||||
|
'A -->> B\n' +
|
||||||
|
'A <<-- B\n' +
|
||||||
|
'A <<-->> B\n' +
|
||||||
|
'A <-->> B\n' +
|
||||||
|
'A <<--> B\n'
|
||||||
);
|
);
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
PARSED.connect(['A', 'B'], {
|
PARSED.connect(['A', 'B'], {
|
||||||
line: 'solid',
|
line: 'solid',
|
||||||
left: false,
|
left: 0,
|
||||||
right: true,
|
right: 1,
|
||||||
label: '',
|
|
||||||
}),
|
|
||||||
PARSED.connect(['A', 'B'], {
|
|
||||||
line: 'solid',
|
|
||||||
left: true,
|
|
||||||
right: false,
|
|
||||||
label: '',
|
|
||||||
}),
|
|
||||||
PARSED.connect(['A', 'B'], {
|
|
||||||
line: 'solid',
|
|
||||||
left: true,
|
|
||||||
right: true,
|
|
||||||
label: '',
|
|
||||||
}),
|
|
||||||
PARSED.connect(['A', 'B'], {
|
|
||||||
line: 'dash',
|
|
||||||
left: false,
|
|
||||||
right: true,
|
|
||||||
label: '',
|
|
||||||
}),
|
|
||||||
PARSED.connect(['A', 'B'], {
|
|
||||||
line: 'dash',
|
|
||||||
left: true,
|
|
||||||
right: false,
|
|
||||||
label: '',
|
|
||||||
}),
|
|
||||||
PARSED.connect(['A', 'B'], {
|
|
||||||
line: 'dash',
|
|
||||||
left: true,
|
|
||||||
right: true,
|
|
||||||
label: '',
|
label: '',
|
||||||
}),
|
}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 0}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 1}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 1}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 0}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 1}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 0, right: 2}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 0}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 2}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 1, right: 2}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 1}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 2}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 0}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 2}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 2}),
|
||||||
|
PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 1}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -250,14 +245,14 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
PARSED.connect(['A', 'B'], {
|
PARSED.connect(['A', 'B'], {
|
||||||
line: 'solid',
|
line: 'solid',
|
||||||
left: true,
|
left: 1,
|
||||||
right: false,
|
right: 0,
|
||||||
label: 'B -> A',
|
label: 'B -> A',
|
||||||
}),
|
}),
|
||||||
PARSED.connect(['A', 'B'], {
|
PARSED.connect(['A', 'B'], {
|
||||||
line: 'solid',
|
line: 'solid',
|
||||||
left: false,
|
left: 0,
|
||||||
right: true,
|
right: 1,
|
||||||
label: 'B <- A',
|
label: 'B <- A',
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -33,8 +33,8 @@ defineDescribe('Sequence Renderer', [
|
||||||
label,
|
label,
|
||||||
options: {
|
options: {
|
||||||
line: 'solid',
|
line: 'solid',
|
||||||
left: false,
|
left: 0,
|
||||||
right: true,
|
right: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,8 +22,17 @@ define([
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArrowShort(theme) {
|
class Arrowhead {
|
||||||
const arrow = theme.connect.arrow;
|
constructor(propName) {
|
||||||
|
this.propName = propName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(theme) {
|
||||||
|
return theme.connect.arrow[this.propName];
|
||||||
|
}
|
||||||
|
|
||||||
|
short(theme) {
|
||||||
|
const arrow = this.getConfig(theme);
|
||||||
const join = arrow.attrs['stroke-linejoin'] || 'miter';
|
const join = arrow.attrs['stroke-linejoin'] || 'miter';
|
||||||
const t = arrow.attrs['stroke-width'] * 0.5;
|
const t = arrow.attrs['stroke-width'] * 0.5;
|
||||||
const lineStroke = theme.agentLineAttrs['stroke-width'] * 0.5;
|
const lineStroke = theme.agentLineAttrs['stroke-width'] * 0.5;
|
||||||
|
@ -37,16 +46,63 @@ define([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render(layer, theme, {x, y, dir}) {
|
||||||
|
const config = this.getConfig(theme);
|
||||||
|
drawHorizontalArrowHead(layer, {
|
||||||
|
x: x + this.short(theme) * dir,
|
||||||
|
y,
|
||||||
|
dx: config.width * dir,
|
||||||
|
dy: config.height / 2,
|
||||||
|
attrs: config.attrs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
width(theme) {
|
||||||
|
return this.short(theme) + this.getConfig(theme).width;
|
||||||
|
}
|
||||||
|
|
||||||
|
height(theme) {
|
||||||
|
return this.getConfig(theme).height;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineGap(theme, lineAttrs) {
|
||||||
|
const arrow = this.getConfig(theme);
|
||||||
|
const short = this.short(theme);
|
||||||
|
if(arrow.attrs.fill === 'none') {
|
||||||
|
const h = arrow.height / 2;
|
||||||
|
const w = arrow.width;
|
||||||
|
const safe = short + (lineAttrs['stroke-width'] / 2) * (w / h);
|
||||||
|
return (short + safe) / 2;
|
||||||
|
} else {
|
||||||
|
return short + arrow.width / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARROWHEADS = [
|
||||||
|
{
|
||||||
|
render: () => {},
|
||||||
|
width: () => 0,
|
||||||
|
height: () => 0,
|
||||||
|
lineGap: () => 0,
|
||||||
|
},
|
||||||
|
new Arrowhead('single'),
|
||||||
|
new Arrowhead('double'),
|
||||||
|
];
|
||||||
|
|
||||||
class Connect extends BaseComponent {
|
class Connect extends BaseComponent {
|
||||||
separation({agentNames, label}, env) {
|
separation({label, agentNames, options}, env) {
|
||||||
const config = env.theme.connect;
|
const config = env.theme.connect;
|
||||||
|
|
||||||
const labelWidth = (
|
const lArrow = ARROWHEADS[options.left];
|
||||||
env.textSizer.measure(config.label.attrs, label).width +
|
const rArrow = ARROWHEADS[options.right];
|
||||||
config.label.padding * 2
|
|
||||||
);
|
|
||||||
|
|
||||||
const short = getArrowShort(env.theme);
|
let labelWidth = (
|
||||||
|
env.textSizer.measure(config.label.attrs, label).width
|
||||||
|
);
|
||||||
|
if(labelWidth > 0) {
|
||||||
|
labelWidth += config.label.padding * 2;
|
||||||
|
}
|
||||||
|
|
||||||
const info1 = env.agentInfos.get(agentNames[0]);
|
const info1 = env.agentInfos.get(agentNames[0]);
|
||||||
if(agentNames[0] === agentNames[1]) {
|
if(agentNames[0] === agentNames[1]) {
|
||||||
|
@ -54,9 +110,10 @@ define([
|
||||||
left: 0,
|
left: 0,
|
||||||
right: (
|
right: (
|
||||||
info1.currentMaxRad +
|
info1.currentMaxRad +
|
||||||
labelWidth +
|
Math.max(
|
||||||
config.arrow.width +
|
labelWidth + lArrow.width(env.theme),
|
||||||
short +
|
rArrow.width(env.theme)
|
||||||
|
) +
|
||||||
config.loopbackRadius
|
config.loopbackRadius
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
@ -69,8 +126,10 @@ define([
|
||||||
info1.currentMaxRad +
|
info1.currentMaxRad +
|
||||||
info2.currentMaxRad +
|
info2.currentMaxRad +
|
||||||
labelWidth +
|
labelWidth +
|
||||||
config.arrow.width * 2 +
|
Math.max(
|
||||||
short * 2
|
lArrow.width(env.theme),
|
||||||
|
rArrow.width(env.theme)
|
||||||
|
) * 2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,9 +138,8 @@ define([
|
||||||
const config = env.theme.connect;
|
const config = env.theme.connect;
|
||||||
const from = env.agentInfos.get(agentNames[0]);
|
const from = env.agentInfos.get(agentNames[0]);
|
||||||
|
|
||||||
const dx = config.arrow.width;
|
const lArrow = ARROWHEADS[options.left];
|
||||||
const dy = config.arrow.height / 2;
|
const rArrow = ARROWHEADS[options.right];
|
||||||
const short = getArrowShort(env.theme);
|
|
||||||
|
|
||||||
const height = (
|
const height = (
|
||||||
env.textSizer.measureHeight(config.label.attrs, label) +
|
env.textSizer.measureHeight(config.label.attrs, label) +
|
||||||
|
@ -93,9 +151,8 @@ define([
|
||||||
const y0 = env.primaryY;
|
const y0 = env.primaryY;
|
||||||
const x0 = (
|
const x0 = (
|
||||||
lineX +
|
lineX +
|
||||||
short +
|
lArrow.width(env.theme) +
|
||||||
dx +
|
(label ? config.label.padding : 0)
|
||||||
config.label.padding
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderedText = SVGShapes.renderBoxedText(label, {
|
const renderedText = SVGShapes.renderBoxedText(label, {
|
||||||
|
@ -108,48 +165,32 @@ define([
|
||||||
labelLayer: env.labelLayer,
|
labelLayer: env.labelLayer,
|
||||||
SVGTextBlockClass: env.SVGTextBlockClass,
|
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||||
});
|
});
|
||||||
const r = config.loopbackRadius;
|
const labelW = (label ? (
|
||||||
const x1 = (
|
|
||||||
x0 +
|
|
||||||
renderedText.width +
|
renderedText.width +
|
||||||
config.label.padding -
|
config.label.padding -
|
||||||
config.mask.padding.left -
|
config.mask.padding.left -
|
||||||
config.mask.padding.right
|
config.mask.padding.right
|
||||||
);
|
) : 0);
|
||||||
|
const r = config.loopbackRadius;
|
||||||
|
const x1 = Math.max(lineX + rArrow.width(env.theme), x0 + labelW);
|
||||||
const y1 = y0 + r * 2;
|
const y1 = y0 + r * 2;
|
||||||
|
|
||||||
const space = short + dx / 2;
|
const lineAttrs = config.lineAttrs[options.line];
|
||||||
|
|
||||||
env.shapeLayer.appendChild(svg.make('path', Object.assign({
|
env.shapeLayer.appendChild(svg.make('path', Object.assign({
|
||||||
'd': (
|
'd': (
|
||||||
'M ' + (lineX + (options.left ? space : 0)) + ' ' + y0 +
|
'M ' + (lineX + lArrow.lineGap(env.theme, lineAttrs)) +
|
||||||
|
' ' + y0 +
|
||||||
' L ' + x1 + ' ' + y0 +
|
' L ' + x1 + ' ' + y0 +
|
||||||
' A ' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + y1 +
|
' A ' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + y1 +
|
||||||
' L ' + (lineX + (options.right ? space : 0)) + ' ' + y1
|
' L ' + (lineX + rArrow.lineGap(env.theme, lineAttrs)) +
|
||||||
|
' ' + y1
|
||||||
),
|
),
|
||||||
}, config.lineAttrs[options.line])));
|
}, lineAttrs)));
|
||||||
|
|
||||||
if(options.left) {
|
lArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y0, dir: 1});
|
||||||
drawHorizontalArrowHead(env.shapeLayer, {
|
rArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y1, dir: 1});
|
||||||
x: lineX + short,
|
|
||||||
y: y0,
|
|
||||||
dx,
|
|
||||||
dy,
|
|
||||||
attrs: config.arrow.attrs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.right) {
|
return y1 + rArrow.height(env.theme) / 2 + env.theme.actionMargin;
|
||||||
drawHorizontalArrowHead(env.shapeLayer, {
|
|
||||||
x: lineX + short,
|
|
||||||
y: y1,
|
|
||||||
dx,
|
|
||||||
dy,
|
|
||||||
attrs: config.arrow.attrs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return y1 + dy + env.theme.actionMargin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSimpleConnect({label, agentNames, options}, env) {
|
renderSimpleConnect({label, agentNames, options}, env) {
|
||||||
|
@ -157,10 +198,10 @@ define([
|
||||||
const from = env.agentInfos.get(agentNames[0]);
|
const from = env.agentInfos.get(agentNames[0]);
|
||||||
const to = env.agentInfos.get(agentNames[1]);
|
const to = env.agentInfos.get(agentNames[1]);
|
||||||
|
|
||||||
const dx = config.arrow.width;
|
const lArrow = ARROWHEADS[options.left];
|
||||||
const dy = config.arrow.height / 2;
|
const rArrow = ARROWHEADS[options.right];
|
||||||
|
|
||||||
const dir = (from.x < to.x) ? 1 : -1;
|
const dir = (from.x < to.x) ? 1 : -1;
|
||||||
const short = getArrowShort(env.theme);
|
|
||||||
|
|
||||||
const height = (
|
const height = (
|
||||||
env.textSizer.measureHeight(config.label.attrs, label) +
|
env.textSizer.measureHeight(config.label.attrs, label) +
|
||||||
|
@ -183,50 +224,47 @@ define([
|
||||||
SVGTextBlockClass: env.SVGTextBlockClass,
|
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||||
});
|
});
|
||||||
|
|
||||||
const space = short + dx / 2;
|
const lineAttrs = config.lineAttrs[options.line];
|
||||||
|
|
||||||
env.shapeLayer.appendChild(svg.make('line', Object.assign({
|
env.shapeLayer.appendChild(svg.make('line', Object.assign({
|
||||||
'x1': x0 + (options.left ? space : 0) * dir,
|
'x1': x0 + lArrow.lineGap(env.theme, lineAttrs) * dir,
|
||||||
'y1': y,
|
'y1': y,
|
||||||
'x2': x1 - (options.right ? space : 0) * dir,
|
'x2': x1 - rArrow.lineGap(env.theme, lineAttrs) * dir,
|
||||||
'y2': y,
|
'y2': y,
|
||||||
}, config.lineAttrs[options.line])));
|
}, lineAttrs)));
|
||||||
|
|
||||||
if(options.left) {
|
lArrow.render(env.shapeLayer, env.theme, {x: x0, y, dir});
|
||||||
drawHorizontalArrowHead(env.shapeLayer, {
|
rArrow.render(env.shapeLayer, env.theme, {x: x1, y, dir: -dir});
|
||||||
x: x0 + short * dir,
|
|
||||||
y,
|
return (
|
||||||
dx: dx * dir,
|
y +
|
||||||
dy,
|
Math.max(
|
||||||
attrs: config.arrow.attrs,
|
lArrow.height(env.theme),
|
||||||
});
|
rArrow.height(env.theme)
|
||||||
|
) / 2 +
|
||||||
|
env.theme.actionMargin
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.right) {
|
renderPre({label, agentNames, options}, env) {
|
||||||
drawHorizontalArrowHead(env.shapeLayer, {
|
|
||||||
x: x1 - short * dir,
|
|
||||||
y,
|
|
||||||
dx: -dx * dir,
|
|
||||||
dy,
|
|
||||||
attrs: config.arrow.attrs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return y + dy + env.theme.actionMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPre({label, agentNames}, env) {
|
|
||||||
const config = env.theme.connect;
|
const config = env.theme.connect;
|
||||||
|
|
||||||
|
const lArrow = ARROWHEADS[options.left];
|
||||||
|
const rArrow = ARROWHEADS[options.right];
|
||||||
|
|
||||||
const height = (
|
const height = (
|
||||||
env.textSizer.measureHeight(config.label.attrs, label) +
|
env.textSizer.measureHeight(config.label.attrs, label) +
|
||||||
config.label.margin.top +
|
config.label.margin.top +
|
||||||
config.label.margin.bottom
|
config.label.margin.bottom
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let arrowH = lArrow.height(env.theme);
|
||||||
|
if(agentNames[0] !== agentNames[1]) {
|
||||||
|
arrowH = Math.max(arrowH, rArrow.height(env.theme));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
agentNames,
|
agentNames,
|
||||||
topShift: Math.max(config.arrow.height / 2, height),
|
topShift: Math.max(arrowH / 2, height),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
arrow: {
|
arrow: {
|
||||||
|
single: {
|
||||||
width: 5,
|
width: 5,
|
||||||
height: 10,
|
height: 10,
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -80,6 +81,17 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
'stroke-linejoin': 'miter',
|
'stroke-linejoin': 'miter',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
double: {
|
||||||
|
width: 4,
|
||||||
|
height: 6,
|
||||||
|
attrs: {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
'stroke-linejoin': 'miter',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
label: {
|
label: {
|
||||||
padding: 6,
|
padding: 6,
|
||||||
margin: {top: 2, bottom: 1},
|
margin: {top: 2, bottom: 1},
|
||||||
|
|
|
@ -78,6 +78,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
arrow: {
|
arrow: {
|
||||||
|
single: {
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 12,
|
height: 12,
|
||||||
attrs: {
|
attrs: {
|
||||||
|
@ -87,8 +88,20 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
'stroke-linejoin': 'round',
|
'stroke-linejoin': 'round',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
double: {
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
attrs: {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 3,
|
||||||
|
'stroke-linejoin': 'round',
|
||||||
|
'stroke-linecap': 'round',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
label: {
|
label: {
|
||||||
padding: 6,
|
padding: 7,
|
||||||
margin: {top: 2, bottom: 3},
|
margin: {top: 2, bottom: 3},
|
||||||
attrs: {
|
attrs: {
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
|
|