Multiline text everywhere
28
README.md
|
@ -29,6 +29,8 @@ Gremlin -> Bowie: Do what?
|
||||||
Bowie -> Gremlin: Remind me of the babe!
|
Bowie -> Gremlin: Remind me of the babe!
|
||||||
|
|
||||||
Bowie -> Audience: Sings
|
Bowie -> Audience: Sings
|
||||||
|
|
||||||
|
terminators box
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connection Types
|
### Connection Types
|
||||||
|
@ -62,12 +64,13 @@ Foo <- ]: From the right
|
||||||
<img src="screenshots/NotesAndState.png" alt="Notes and State preview" width="150" align="right" />
|
<img src="screenshots/NotesAndState.png" alt="Notes and State preview" width="150" align="right" />
|
||||||
|
|
||||||
```
|
```
|
||||||
title Note placements
|
title Note Placements
|
||||||
|
|
||||||
note over Foo: Foo says something
|
note over Foo: Foo says something
|
||||||
note left of Foo: Stuff
|
note left of Foo: Stuff
|
||||||
note right of Bar: More stuff
|
note right of Bar: More stuff
|
||||||
note over Foo, Bar: Foo and Bar
|
note over Foo, Bar: "Foo and Bar
|
||||||
|
on multiple lines"
|
||||||
note between Foo, Bar: Link
|
note between Foo, Bar: Link
|
||||||
|
|
||||||
state over Foo: Foo is ponderous
|
state over Foo: Foo is ponderous
|
||||||
|
@ -78,7 +81,7 @@ state over Foo: Foo is ponderous
|
||||||
<img src="screenshots/Logic.png" alt="Logic preview" width="200" align="right" />
|
<img src="screenshots/Logic.png" alt="Logic preview" width="200" align="right" />
|
||||||
|
|
||||||
```
|
```
|
||||||
title At the bank
|
title At the Bank
|
||||||
|
|
||||||
begin Person, ATM, Bank
|
begin Person, ATM, Bank
|
||||||
Person -> ATM: Request money
|
Person -> ATM: Request money
|
||||||
|
@ -97,6 +100,25 @@ else
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Multiline Text
|
||||||
|
|
||||||
|
<img src="screenshots/MultilineText.png" alt="Multiline Text preview" width="150" align="right" />
|
||||||
|
|
||||||
|
```
|
||||||
|
title 'My Multiline
|
||||||
|
Title'
|
||||||
|
|
||||||
|
note over Foo: 'Also possible\nwith escapes'
|
||||||
|
|
||||||
|
Foo -> Bar: 'Lines of text\non this arrow'
|
||||||
|
|
||||||
|
if 'Even multiline\ninside conditions like this'
|
||||||
|
Foo -> 'Multiline\nagent'
|
||||||
|
end
|
||||||
|
|
||||||
|
state over Foo: 'Newlines here,\ntoo!'
|
||||||
|
```
|
||||||
|
|
||||||
### Short-Lived Agents
|
### Short-Lived Agents
|
||||||
|
|
||||||
<img src="screenshots/ShortLivedAgents.png" alt="Short Lived Agents preview" width="200" align="right" />
|
<img src="screenshots/ShortLivedAgents.png" alt="Short Lived Agents preview" width="200" align="right" />
|
||||||
|
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
@ -2,58 +2,62 @@ define([
|
||||||
'./ArrayUtilities',
|
'./ArrayUtilities',
|
||||||
'./SVGUtilities',
|
'./SVGUtilities',
|
||||||
'./SVGTextBlock',
|
'./SVGTextBlock',
|
||||||
|
'./SVGShapes',
|
||||||
], (
|
], (
|
||||||
array,
|
array,
|
||||||
svg,
|
svg,
|
||||||
SVGTextBlock
|
SVGTextBlock,
|
||||||
|
SVGShapes
|
||||||
) => {
|
) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function boxRenderer(attrs, position) {
|
|
||||||
return svg.make('rect', Object.assign({}, position, attrs));
|
|
||||||
}
|
|
||||||
|
|
||||||
function noteRenderer(attrs, flickAttrs, position) {
|
|
||||||
const g = svg.make('g');
|
|
||||||
const x0 = position.x;
|
|
||||||
const x1 = position.x + position.width;
|
|
||||||
const y0 = position.y;
|
|
||||||
const y1 = position.y + position.height;
|
|
||||||
const flick = 7;
|
|
||||||
|
|
||||||
g.appendChild(svg.make('path', Object.assign({
|
|
||||||
'd': (
|
|
||||||
'M ' + x0 + ' ' + y0 +
|
|
||||||
' L ' + (x1 - flick) + ' ' + y0 +
|
|
||||||
' L ' + x1 + ' ' + (y0 + flick) +
|
|
||||||
' L ' + x1 + ' ' + y1 +
|
|
||||||
' L ' + x0 + ' ' + y1 +
|
|
||||||
' Z'
|
|
||||||
),
|
|
||||||
}, attrs)));
|
|
||||||
|
|
||||||
g.appendChild(svg.make('path', Object.assign({
|
|
||||||
'd': (
|
|
||||||
'M ' + (x1 - flick) + ' ' + y0 +
|
|
||||||
' L ' + (x1 - flick) + ' ' + (y0 + flick) +
|
|
||||||
' L ' + x1 + ' ' + (y0 + flick)
|
|
||||||
),
|
|
||||||
}, flickAttrs)));
|
|
||||||
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SEP_ZERO = {left: 0, right: 0};
|
const SEP_ZERO = {left: 0, right: 0};
|
||||||
|
|
||||||
const LINE_HEIGHT = 1.3;
|
const LINE_HEIGHT = 1.3;
|
||||||
const TITLE_MARGIN = 10;
|
const TITLE_MARGIN = 10;
|
||||||
const OUTER_MARGIN = 5;
|
const OUTER_MARGIN = 5;
|
||||||
const AGENT_BOX_PADDING = 10;
|
|
||||||
const AGENT_MARGIN = 10;
|
const AGENT_MARGIN = 10;
|
||||||
const AGENT_CROSS_SIZE = 20;
|
|
||||||
const AGENT_NONE_HEIGHT = 10;
|
|
||||||
const ACTION_MARGIN = 5;
|
const ACTION_MARGIN = 5;
|
||||||
|
|
||||||
|
const AGENT_CAP = {
|
||||||
|
box: {
|
||||||
|
padding: {
|
||||||
|
top: 5,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
bottom: 5,
|
||||||
|
},
|
||||||
|
boxAttrs: {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
},
|
||||||
|
labelAttrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-size': 12,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
'text-anchor': 'middle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cross: {
|
||||||
|
size: 20,
|
||||||
|
attrs: {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
attrs: {
|
||||||
|
'fill': '#000000',
|
||||||
|
'height': 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
none: {
|
||||||
|
height: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const CONNECT = {
|
const CONNECT = {
|
||||||
lineAttrs: {
|
lineAttrs: {
|
||||||
'solid': {
|
'solid': {
|
||||||
|
@ -84,12 +88,18 @@ define([
|
||||||
attrs: {
|
attrs: {
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
'font-size': 8,
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
'text-anchor': 'middle',
|
'text-anchor': 'middle',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mask: {
|
mask: {
|
||||||
padding: 3,
|
padding: {
|
||||||
attrs: {
|
top: 0,
|
||||||
|
left: 3,
|
||||||
|
right: 3,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
maskAttrs: {
|
||||||
'fill': '#FFFFFF',
|
'fill': '#FFFFFF',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -130,24 +140,24 @@ define([
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold',
|
||||||
'font-size': 9,
|
'font-size': 9,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
'text-anchor': 'left',
|
'text-anchor': 'left',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
maskPadding: {
|
padding: {
|
||||||
left: 3,
|
top: 1,
|
||||||
|
left: 5,
|
||||||
right: 3,
|
right: 3,
|
||||||
|
bottom: 0,
|
||||||
},
|
},
|
||||||
maskAttrs: {
|
maskAttrs: {
|
||||||
'fill': '#FFFFFF',
|
'fill': '#FFFFFF',
|
||||||
},
|
},
|
||||||
labelPadding: {
|
|
||||||
left: 5,
|
|
||||||
right: 5,
|
|
||||||
},
|
|
||||||
labelAttrs: {
|
labelAttrs: {
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
'font-size': 8,
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
'text-anchor': 'left',
|
'text-anchor': 'left',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -166,7 +176,7 @@ define([
|
||||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||||
padding: {top: 5, left: 5, right: 10, bottom: 5},
|
padding: {top: 5, left: 5, right: 10, bottom: 5},
|
||||||
overlap: {left: 10, right: 10},
|
overlap: {left: 10, right: 10},
|
||||||
boxRenderer: noteRenderer.bind(null, {
|
boxRenderer: SVGShapes.renderNote.bind(null, {
|
||||||
'fill': '#FFFFFF',
|
'fill': '#FFFFFF',
|
||||||
'stroke': '#000000',
|
'stroke': '#000000',
|
||||||
'stroke-width': 1,
|
'stroke-width': 1,
|
||||||
|
@ -178,13 +188,14 @@ define([
|
||||||
labelAttrs: {
|
labelAttrs: {
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
'font-size': 8,
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'state': {
|
'state': {
|
||||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||||
padding: {top: 7, left: 7, right: 7, bottom: 7},
|
padding: {top: 7, left: 7, right: 7, bottom: 7},
|
||||||
overlap: {left: 10, right: 10},
|
overlap: {left: 10, right: 10},
|
||||||
boxRenderer: boxRenderer.bind(null, {
|
boxRenderer: SVGShapes.renderBox.bind(null, {
|
||||||
'fill': '#FFFFFF',
|
'fill': '#FFFFFF',
|
||||||
'stroke': '#000000',
|
'stroke': '#000000',
|
||||||
'stroke-width': 1,
|
'stroke-width': 1,
|
||||||
|
@ -194,6 +205,7 @@ define([
|
||||||
labelAttrs: {
|
labelAttrs: {
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
'font-size': 8,
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -202,6 +214,7 @@ define([
|
||||||
TITLE: {
|
TITLE: {
|
||||||
'font-family': 'sans-serif',
|
'font-family': 'sans-serif',
|
||||||
'font-size': 20,
|
'font-size': 20,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
'text-anchor': 'middle',
|
'text-anchor': 'middle',
|
||||||
'class': 'title',
|
'class': 'title',
|
||||||
},
|
},
|
||||||
|
@ -211,28 +224,21 @@ define([
|
||||||
'stroke': '#000000',
|
'stroke': '#000000',
|
||||||
'stroke-width': 1,
|
'stroke-width': 1,
|
||||||
},
|
},
|
||||||
AGENT_BOX: {
|
|
||||||
'fill': '#FFFFFF',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
'height': 24,
|
|
||||||
},
|
|
||||||
AGENT_BOX_LABEL: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-size': 12,
|
|
||||||
'text-anchor': 'middle',
|
|
||||||
},
|
|
||||||
AGENT_CROSS: {
|
|
||||||
'fill': 'none',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
},
|
|
||||||
AGENT_BAR: {
|
|
||||||
'fill': '#000000',
|
|
||||||
'height': 5,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function drawHorizontalArrowHead(container, {x, y, dx, dy, attrs}) {
|
||||||
|
container.appendChild(svg.make(
|
||||||
|
attrs.fill === 'none' ? 'polyline' : 'polygon',
|
||||||
|
Object.assign({
|
||||||
|
'points': (
|
||||||
|
(x + dx) + ' ' + (y - dy) + ' ' +
|
||||||
|
x + ' ' + y + ' ' +
|
||||||
|
(x + dx) + ' ' + (y + dy)
|
||||||
|
),
|
||||||
|
}, attrs)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
function traverse(stages, callbacks) {
|
function traverse(stages, callbacks) {
|
||||||
stages.forEach((stage) => {
|
stages.forEach((stage) => {
|
||||||
if(stage.type === 'block') {
|
if(stage.type === 'block') {
|
||||||
|
@ -321,19 +327,20 @@ define([
|
||||||
});
|
});
|
||||||
|
|
||||||
this.agentLines = svg.make('g');
|
this.agentLines = svg.make('g');
|
||||||
|
this.mask = svg.make('g');
|
||||||
this.blocks = svg.make('g');
|
this.blocks = svg.make('g');
|
||||||
this.sections = svg.make('g');
|
this.sections = svg.make('g');
|
||||||
this.agentDecor = svg.make('g');
|
this.actionShapes = svg.make('g');
|
||||||
this.actions = svg.make('g');
|
this.actionLabels = svg.make('g');
|
||||||
this.base.appendChild(this.agentLines);
|
this.base.appendChild(this.agentLines);
|
||||||
|
this.base.appendChild(this.mask);
|
||||||
this.base.appendChild(this.blocks);
|
this.base.appendChild(this.blocks);
|
||||||
this.base.appendChild(this.sections);
|
this.base.appendChild(this.sections);
|
||||||
this.base.appendChild(this.agentDecor);
|
this.base.appendChild(this.actionShapes);
|
||||||
this.base.appendChild(this.actions);
|
this.base.appendChild(this.actionLabels);
|
||||||
this.title = new SVGTextBlock(this.base, ATTRS.TITLE, LINE_HEIGHT);
|
this.title = new SVGTextBlock(this.base, ATTRS.TITLE);
|
||||||
|
|
||||||
this.testers = svg.make('g');
|
this.sizer = new SVGTextBlock.SizeTester(this.base);
|
||||||
this.testersCache = new Map();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findExtremes(agents) {
|
findExtremes(agents) {
|
||||||
|
@ -384,24 +391,36 @@ define([
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
separationAgentCapBox(agentInfo) {
|
separationAgentCapBox({label}) {
|
||||||
|
const width = (
|
||||||
|
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
||||||
|
AGENT_CAP.box.padding.left +
|
||||||
|
AGENT_CAP.box.padding.right
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
left: agentInfo.labelWidth / 2,
|
left: width / 2,
|
||||||
right: agentInfo.labelWidth / 2,
|
right: width / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
separationAgentCapCross() {
|
separationAgentCapCross() {
|
||||||
return {
|
return {
|
||||||
left: AGENT_CROSS_SIZE / 2,
|
left: AGENT_CAP.cross.size / 2,
|
||||||
right: AGENT_CROSS_SIZE / 2,
|
right: AGENT_CAP.cross.size / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
separationAgentCapBar(agentInfo) {
|
separationAgentCapBar({label}) {
|
||||||
|
const width = (
|
||||||
|
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
||||||
|
AGENT_CAP.box.padding.left +
|
||||||
|
AGENT_CAP.box.padding.right
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
left: agentInfo.labelWidth / 2,
|
left: width / 2,
|
||||||
right: agentInfo.labelWidth / 2,
|
right: width / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +451,7 @@ define([
|
||||||
agents[0],
|
agents[0],
|
||||||
agents[1],
|
agents[1],
|
||||||
|
|
||||||
this.testTextWidth(CONNECT.label.attrs, label) +
|
this.sizer.measure(CONNECT.label.attrs, label).width +
|
||||||
CONNECT.arrow.width * 2 +
|
CONNECT.arrow.width * 2 +
|
||||||
CONNECT.label.padding * 2 +
|
CONNECT.label.padding * 2 +
|
||||||
ATTRS.AGENT_LINE['stroke-width']
|
ATTRS.AGENT_LINE['stroke-width']
|
||||||
|
@ -442,7 +461,7 @@ define([
|
||||||
separationNoteOver({agents, mode, label}) {
|
separationNoteOver({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = NOTE[mode];
|
||||||
const width = (
|
const width = (
|
||||||
this.testTextWidth(config.labelAttrs, label) +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
config.padding.right
|
config.padding.right
|
||||||
);
|
);
|
||||||
|
@ -478,7 +497,7 @@ define([
|
||||||
const agentSpaces = new Map();
|
const agentSpaces = new Map();
|
||||||
agentSpaces.set(left, {
|
agentSpaces.set(left, {
|
||||||
left: (
|
left: (
|
||||||
this.testTextWidth(config.labelAttrs, label) +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
config.padding.right +
|
config.padding.right +
|
||||||
config.margin.left +
|
config.margin.left +
|
||||||
|
@ -497,7 +516,7 @@ define([
|
||||||
agentSpaces.set(right, {
|
agentSpaces.set(right, {
|
||||||
left: 0,
|
left: 0,
|
||||||
right: (
|
right: (
|
||||||
this.testTextWidth(config.labelAttrs, label) +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
config.padding.right +
|
config.padding.right +
|
||||||
config.margin.left +
|
config.margin.left +
|
||||||
|
@ -515,7 +534,7 @@ define([
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
|
|
||||||
this.testTextWidth(config.labelAttrs, label) +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
config.padding.right +
|
config.padding.right +
|
||||||
config.margin.left +
|
config.margin.left +
|
||||||
|
@ -529,13 +548,14 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationSectionBegin(scope, {left, right}, {mode, label}) {
|
separationSectionBegin(scope, {left, right}, {mode, label}) {
|
||||||
|
const config = BLOCK.section;
|
||||||
const width = (
|
const width = (
|
||||||
this.testTextWidth(BLOCK.section.mode.labelAttrs, mode) +
|
this.sizer.measure(config.mode.labelAttrs, mode).width +
|
||||||
BLOCK.section.mode.padding.left +
|
config.mode.padding.left +
|
||||||
BLOCK.section.mode.padding.right +
|
config.mode.padding.right +
|
||||||
this.testTextWidth(BLOCK.section.label.labelAttrs, label) +
|
this.sizer.measure(config.label.labelAttrs, label).width +
|
||||||
BLOCK.section.label.labelPadding.left +
|
config.label.padding.left +
|
||||||
BLOCK.section.label.labelPadding.right
|
config.label.padding.right
|
||||||
);
|
);
|
||||||
this.addSeparation(left, right, width);
|
this.addSeparation(left, right, width);
|
||||||
}
|
}
|
||||||
|
@ -548,42 +568,36 @@ define([
|
||||||
this.separationAction[stage.type](stage);
|
this.separationAction[stage.type](stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapBox({x, labelWidth, label}) {
|
renderAgentCapBox({x, label}) {
|
||||||
this.agentDecor.appendChild(svg.make('rect', Object.assign({
|
const {height} = SVGShapes.renderBoxedText(label, {
|
||||||
'x': x - labelWidth / 2,
|
x,
|
||||||
'y': this.currentY,
|
y: this.currentY,
|
||||||
'width': labelWidth,
|
padding: AGENT_CAP.box.padding,
|
||||||
}, ATTRS.AGENT_BOX)));
|
boxAttrs: AGENT_CAP.box.boxAttrs,
|
||||||
|
labelAttrs: AGENT_CAP.box.labelAttrs,
|
||||||
const name = svg.make('text', Object.assign({
|
boxLayer: this.actionShapes,
|
||||||
'x': x,
|
labelLayer: this.actionLabels,
|
||||||
'y': this.currentY + (
|
});
|
||||||
ATTRS.AGENT_BOX.height +
|
|
||||||
ATTRS.AGENT_BOX_LABEL['font-size'] * (2 - LINE_HEIGHT)
|
|
||||||
) / 2,
|
|
||||||
}, ATTRS.AGENT_BOX_LABEL));
|
|
||||||
name.appendChild(svg.makeText(label));
|
|
||||||
this.agentDecor.appendChild(name);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineTop: 0,
|
lineTop: 0,
|
||||||
lineBottom: ATTRS.AGENT_BOX.height,
|
lineBottom: height,
|
||||||
height: ATTRS.AGENT_BOX.height,
|
height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapCross({x}) {
|
renderAgentCapCross({x}) {
|
||||||
const y = this.currentY;
|
const y = this.currentY;
|
||||||
const d = AGENT_CROSS_SIZE / 2;
|
const d = AGENT_CAP.cross.size / 2;
|
||||||
|
|
||||||
this.agentDecor.appendChild(svg.make('path', Object.assign({
|
this.actionShapes.appendChild(svg.make('path', Object.assign({
|
||||||
'd': (
|
'd': (
|
||||||
'M ' + (x - d) + ' ' + y +
|
'M ' + (x - d) + ' ' + y +
|
||||||
' L ' + (x + d) + ' ' + (y + d * 2) +
|
' L ' + (x + d) + ' ' + (y + d * 2) +
|
||||||
' M ' + (x + d) + ' ' + y +
|
' M ' + (x + d) + ' ' + y +
|
||||||
' L ' + (x - d) + ' ' + (y + d * 2)
|
' L ' + (x - d) + ' ' + (y + d * 2)
|
||||||
),
|
),
|
||||||
}, ATTRS.AGENT_CROSS)));
|
}, AGENT_CAP.cross.attrs)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineTop: d,
|
lineTop: d,
|
||||||
|
@ -592,124 +606,115 @@ define([
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapBar({x, labelWidth}) {
|
renderAgentCapBar({x, label}) {
|
||||||
this.agentDecor.appendChild(svg.make('rect', Object.assign({
|
const width = (
|
||||||
'x': x - labelWidth / 2,
|
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
||||||
|
AGENT_CAP.box.padding.left +
|
||||||
|
AGENT_CAP.box.padding.right
|
||||||
|
);
|
||||||
|
|
||||||
|
this.actionShapes.appendChild(svg.make('rect', Object.assign({
|
||||||
|
'x': x - width / 2,
|
||||||
'y': this.currentY,
|
'y': this.currentY,
|
||||||
'width': labelWidth,
|
'width': width,
|
||||||
}, ATTRS.AGENT_BAR)));
|
}, AGENT_CAP.bar.attrs)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineTop: 0,
|
lineTop: 0,
|
||||||
lineBottom: ATTRS.AGENT_BAR.height,
|
lineBottom: AGENT_CAP.bar.attrs.height,
|
||||||
height: ATTRS.AGENT_BAR.height,
|
height: AGENT_CAP.bar.attrs.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapNone() {
|
renderAgentCapNone() {
|
||||||
return {
|
return {
|
||||||
lineTop: AGENT_NONE_HEIGHT,
|
lineTop: AGENT_CAP.none.height,
|
||||||
lineBottom: 0,
|
lineBottom: 0,
|
||||||
height: AGENT_NONE_HEIGHT,
|
height: AGENT_CAP.none.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentBegin({mode, agents}) {
|
renderAgentBegin({mode, agents}) {
|
||||||
let shifts = {height: 0};
|
let maxHeight = 0;
|
||||||
agents.forEach((agent) => {
|
agents.forEach((agent) => {
|
||||||
const agentInfo = this.agentInfos.get(agent);
|
const agentInfo = this.agentInfos.get(agent);
|
||||||
shifts = this.renderAgentCap[mode](agentInfo);
|
const shifts = this.renderAgentCap[mode](agentInfo);
|
||||||
|
maxHeight = Math.max(maxHeight, shifts.height);
|
||||||
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
|
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
|
||||||
});
|
});
|
||||||
this.currentY += shifts.height + ACTION_MARGIN;
|
this.currentY += maxHeight + ACTION_MARGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentEnd({mode, agents}) {
|
renderAgentEnd({mode, agents}) {
|
||||||
let shifts = {height: 0};
|
let maxHeight = 0;
|
||||||
agents.forEach((agent) => {
|
agents.forEach((agent) => {
|
||||||
const agentInfo = this.agentInfos.get(agent);
|
const agentInfo = this.agentInfos.get(agent);
|
||||||
const x = agentInfo.x;
|
const x = agentInfo.x;
|
||||||
shifts = this.renderAgentCap[mode](agentInfo);
|
const shifts = this.renderAgentCap[mode](agentInfo);
|
||||||
this.agentLines.appendChild(svg.make('path', Object.assign({
|
maxHeight = Math.max(maxHeight, shifts.height);
|
||||||
'd': (
|
this.agentLines.appendChild(svg.make('line', Object.assign({
|
||||||
'M ' + x + ' ' + agentInfo.latestYStart +
|
'x1': x,
|
||||||
' L ' + x + ' ' + (this.currentY + shifts.lineTop)
|
'y1': agentInfo.latestYStart,
|
||||||
),
|
'x2': x,
|
||||||
|
'y2': this.currentY + shifts.lineTop,
|
||||||
'class': 'agent-' + agentInfo.index + '-line',
|
'class': 'agent-' + agentInfo.index + '-line',
|
||||||
}, ATTRS.AGENT_LINE)));
|
}, ATTRS.AGENT_LINE)));
|
||||||
agentInfo.latestYStart = null;
|
agentInfo.latestYStart = null;
|
||||||
});
|
});
|
||||||
this.currentY += shifts.height + ACTION_MARGIN;
|
this.currentY += maxHeight + ACTION_MARGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderConnection({label, agents, line, left, right}) {
|
renderConnection({label, agents, line, left, right}) {
|
||||||
/* jshint -W074, -W071 */ // TODO: tidy this up
|
|
||||||
const from = this.agentInfos.get(agents[0]);
|
const from = this.agentInfos.get(agents[0]);
|
||||||
const to = this.agentInfos.get(agents[1]);
|
const to = this.agentInfos.get(agents[1]);
|
||||||
|
|
||||||
const dy = CONNECT.arrow.height / 2;
|
const dy = CONNECT.arrow.height / 2;
|
||||||
const dx = CONNECT.arrow.width;
|
|
||||||
const dir = (from.x < to.x) ? 1 : -1;
|
const dir = (from.x < to.x) ? 1 : -1;
|
||||||
const short = ATTRS.AGENT_LINE['stroke-width'];
|
const short = ATTRS.AGENT_LINE['stroke-width'];
|
||||||
let y = this.currentY;
|
|
||||||
|
|
||||||
if(label) {
|
const height = (
|
||||||
const mask = svg.make('rect', CONNECT.mask.attrs);
|
this.sizer.measureHeight(CONNECT.label.attrs, label) +
|
||||||
const labelNode = svg.make('text', CONNECT.label.attrs);
|
|
||||||
labelNode.appendChild(svg.makeText(label));
|
|
||||||
const sz = CONNECT.label.attrs['font-size'];
|
|
||||||
this.actions.appendChild(mask);
|
|
||||||
this.actions.appendChild(labelNode);
|
|
||||||
y += Math.max(
|
|
||||||
dy,
|
|
||||||
CONNECT.label.margin.top +
|
CONNECT.label.margin.top +
|
||||||
sz * LINE_HEIGHT +
|
|
||||||
CONNECT.label.margin.bottom
|
CONNECT.label.margin.bottom
|
||||||
);
|
);
|
||||||
const w = labelNode.getComputedTextLength();
|
|
||||||
const x = (from.x + to.x) / 2;
|
|
||||||
const yBase = (
|
|
||||||
y -
|
|
||||||
sz * (LINE_HEIGHT - 1) -
|
|
||||||
CONNECT.label.margin.bottom
|
|
||||||
);
|
|
||||||
labelNode.setAttribute('x', x);
|
|
||||||
labelNode.setAttribute('y', yBase);
|
|
||||||
mask.setAttribute('x', x - w / 2 - CONNECT.mask.padding);
|
|
||||||
mask.setAttribute('y', yBase - sz);
|
|
||||||
mask.setAttribute('width', w + CONNECT.mask.padding * 2);
|
|
||||||
mask.setAttribute('height', sz * LINE_HEIGHT);
|
|
||||||
} else {
|
|
||||||
y += dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actions.appendChild(svg.make('path', Object.assign({
|
let y = this.currentY + Math.max(dy, height);
|
||||||
'd': (
|
|
||||||
'M ' + (from.x + (left ? short : 0) * dir) + ' ' + y +
|
SVGShapes.renderBoxedText(label, {
|
||||||
' L ' + (to.x - (right ? short : 0) * dir) + ' ' + y
|
x: (from.x + to.x) / 2,
|
||||||
),
|
y: y - height + CONNECT.label.margin.top,
|
||||||
|
padding: CONNECT.mask.padding,
|
||||||
|
boxAttrs: CONNECT.mask.maskAttrs,
|
||||||
|
labelAttrs: CONNECT.label.attrs,
|
||||||
|
boxLayer: this.mask,
|
||||||
|
labelLayer: this.actionLabels,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.actionShapes.appendChild(svg.make('line', Object.assign({
|
||||||
|
'x1': from.x + (left ? short : 0) * dir,
|
||||||
|
'y1': y,
|
||||||
|
'x2': to.x - (right ? short : 0) * dir,
|
||||||
|
'y2': y,
|
||||||
}, CONNECT.lineAttrs[line])));
|
}, CONNECT.lineAttrs[line])));
|
||||||
|
|
||||||
if(left) {
|
if(left) {
|
||||||
this.actions.appendChild(svg.make('path', Object.assign({
|
drawHorizontalArrowHead(this.actionShapes, {
|
||||||
'd': (
|
x: from.x + short * dir,
|
||||||
'M ' + (from.x + (dx + short) * dir) + ' ' + (y - dy) +
|
y,
|
||||||
' L ' + (from.x + short * dir) + ' ' + y +
|
dx: CONNECT.arrow.width * dir,
|
||||||
' L ' + (from.x + (dx + short) * dir) + ' ' + (y + dy) +
|
dy,
|
||||||
(CONNECT.arrow.attrs.fill === 'none' ? '' : ' Z')
|
attrs: CONNECT.arrow.attrs,
|
||||||
),
|
});
|
||||||
}, CONNECT.arrow.attrs)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(right) {
|
if(right) {
|
||||||
this.actions.appendChild(svg.make('path', Object.assign({
|
drawHorizontalArrowHead(this.actionShapes, {
|
||||||
'd': (
|
x: to.x - short * dir,
|
||||||
'M ' + (to.x - (dx + short) * dir) + ' ' + (y - dy) +
|
y,
|
||||||
' L ' + (to.x - short * dir) + ' ' + y +
|
dx: -CONNECT.arrow.width * dir,
|
||||||
' L ' + (to.x - (dx + short) * dir) + ' ' + (y + dy) +
|
dy,
|
||||||
(CONNECT.arrow.attrs.fill === 'none' ? '' : ' Z')
|
attrs: CONNECT.arrow.attrs,
|
||||||
),
|
});
|
||||||
}, CONNECT.arrow.attrs)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentY = y + dy + ACTION_MARGIN;
|
this.currentY = y + dy + ACTION_MARGIN;
|
||||||
|
@ -718,19 +723,25 @@ define([
|
||||||
renderNote({xMid = null, x0 = null, x1 = null}, anchor, mode, label) {
|
renderNote({xMid = null, x0 = null, x1 = null}, anchor, mode, label) {
|
||||||
const config = NOTE[mode];
|
const config = NOTE[mode];
|
||||||
|
|
||||||
const sz = config.labelAttrs['font-size'];
|
|
||||||
|
|
||||||
this.currentY += config.margin.top;
|
this.currentY += config.margin.top;
|
||||||
|
|
||||||
const labelNode = svg.make('text', Object.assign({
|
const y = this.currentY + config.padding.top;
|
||||||
'y': this.currentY + config.padding.top + sz,
|
const labelNode = new SVGTextBlock(
|
||||||
'text-anchor': anchor,
|
this.actionLabels,
|
||||||
}, config.labelAttrs));
|
config.labelAttrs,
|
||||||
labelNode.appendChild(svg.makeText(label));
|
{text: label, y}
|
||||||
this.actions.appendChild(labelNode);
|
);
|
||||||
|
|
||||||
const w = labelNode.getComputedTextLength();
|
const fullW = (
|
||||||
const fullW = w + config.padding.left + config.padding.right;
|
labelNode.width +
|
||||||
|
config.padding.left +
|
||||||
|
config.padding.right
|
||||||
|
);
|
||||||
|
const fullH = (
|
||||||
|
config.padding.top +
|
||||||
|
labelNode.height +
|
||||||
|
config.padding.bottom
|
||||||
|
);
|
||||||
if(x0 === null && xMid !== null) {
|
if(x0 === null && xMid !== null) {
|
||||||
x0 = xMid - fullW / 2;
|
x0 = xMid - fullW / 2;
|
||||||
}
|
}
|
||||||
|
@ -739,35 +750,30 @@ define([
|
||||||
} else if(x0 === null) {
|
} else if(x0 === null) {
|
||||||
x0 = x1 - fullW;
|
x0 = x1 - fullW;
|
||||||
}
|
}
|
||||||
switch(anchor) {
|
switch(config.labelAttrs['text-anchor']) {
|
||||||
case 'start':
|
case 'middle':
|
||||||
labelNode.setAttribute('x', x0 + config.padding.left);
|
labelNode.reanchor((
|
||||||
break;
|
|
||||||
case 'end':
|
|
||||||
labelNode.setAttribute('x', x1 - config.padding.right);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
labelNode.setAttribute('x', (
|
|
||||||
x0 + config.padding.left +
|
x0 + config.padding.left +
|
||||||
x1 - config.padding.right
|
x1 - config.padding.right
|
||||||
) / 2);
|
) / 2, y);
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
labelNode.reanchor(x1 - config.padding.right, y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
labelNode.reanchor(x0 + config.padding.left, y);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.actions.insertBefore(config.boxRenderer({
|
this.actionShapes.appendChild(config.boxRenderer({
|
||||||
x: x0,
|
x: x0,
|
||||||
y: this.currentY,
|
y: this.currentY,
|
||||||
width: x1 - x0,
|
width: x1 - x0,
|
||||||
height: (
|
height: fullH,
|
||||||
config.padding.top +
|
}));
|
||||||
sz * LINE_HEIGHT +
|
|
||||||
config.padding.bottom
|
|
||||||
),
|
|
||||||
}), labelNode);
|
|
||||||
|
|
||||||
this.currentY += (
|
this.currentY += (
|
||||||
config.padding.top +
|
fullH +
|
||||||
sz * LINE_HEIGHT +
|
|
||||||
config.padding.bottom +
|
|
||||||
config.margin.bottom +
|
config.margin.bottom +
|
||||||
ACTION_MARGIN
|
ACTION_MARGIN
|
||||||
);
|
);
|
||||||
|
@ -822,8 +828,6 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
||||||
/* jshint -W071 */ // TODO: tidy this up (split text rendering)
|
|
||||||
|
|
||||||
const agentInfoL = this.agentInfos.get(left);
|
const agentInfoL = this.agentInfos.get(left);
|
||||||
const agentInfoR = this.agentInfos.get(right);
|
const agentInfoR = this.agentInfos.get(right);
|
||||||
|
|
||||||
|
@ -831,71 +835,38 @@ define([
|
||||||
scope.first = false;
|
scope.first = false;
|
||||||
} else {
|
} else {
|
||||||
this.currentY += BLOCK.section.padding.bottom;
|
this.currentY += BLOCK.section.padding.bottom;
|
||||||
this.sections.appendChild(svg.make('path', Object.assign({
|
this.sections.appendChild(svg.make('line', Object.assign({
|
||||||
'd': (
|
'x1': agentInfoL.x,
|
||||||
'M' + agentInfoL.x + ' ' + this.currentY +
|
'y1': this.currentY,
|
||||||
' L' + agentInfoR.x + ' ' + this.currentY
|
'x2': agentInfoR.x,
|
||||||
),
|
'y2': this.currentY,
|
||||||
}, BLOCK.separator.attrs)));
|
}, BLOCK.separator.attrs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = agentInfoL.x;
|
const modeRender = SVGShapes.renderBoxedText(mode, {
|
||||||
if(mode) {
|
x: agentInfoL.x,
|
||||||
const sz = BLOCK.section.mode.labelAttrs['font-size'];
|
y: this.currentY,
|
||||||
const modeBox = svg.make('rect', Object.assign({
|
padding: BLOCK.section.mode.padding,
|
||||||
'x': x,
|
boxAttrs: BLOCK.section.mode.boxAttrs,
|
||||||
'y': this.currentY,
|
labelAttrs: BLOCK.section.mode.labelAttrs,
|
||||||
'height': (
|
boxLayer: this.blocks,
|
||||||
sz * LINE_HEIGHT +
|
labelLayer: this.actionLabels,
|
||||||
BLOCK.section.mode.padding.top +
|
});
|
||||||
BLOCK.section.mode.padding.bottom
|
|
||||||
),
|
const labelRender = SVGShapes.renderBoxedText(label, {
|
||||||
}, BLOCK.section.mode.boxAttrs));
|
x: agentInfoL.x + modeRender.width,
|
||||||
const modeLabel = svg.make('text', Object.assign({
|
y: this.currentY,
|
||||||
'x': x + BLOCK.section.mode.padding.left,
|
padding: BLOCK.section.label.padding,
|
||||||
'y': (
|
boxAttrs: BLOCK.section.label.maskAttrs,
|
||||||
this.currentY + sz +
|
labelAttrs: BLOCK.section.label.labelAttrs,
|
||||||
BLOCK.section.mode.padding.top
|
boxLayer: this.mask,
|
||||||
),
|
labelLayer: this.actionLabels,
|
||||||
}, BLOCK.section.mode.labelAttrs));
|
});
|
||||||
modeLabel.appendChild(svg.makeText(mode));
|
|
||||||
this.blocks.appendChild(modeBox);
|
this.currentY += (
|
||||||
this.actions.appendChild(modeLabel);
|
Math.max(modeRender.height, labelRender.height) +
|
||||||
const w = (
|
BLOCK.section.padding.top
|
||||||
modeLabel.getComputedTextLength() +
|
|
||||||
BLOCK.section.mode.padding.left +
|
|
||||||
BLOCK.section.mode.padding.right
|
|
||||||
);
|
);
|
||||||
modeBox.setAttribute('width', w);
|
|
||||||
x += w;
|
|
||||||
|
|
||||||
this.currentY += sz * LINE_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(label) {
|
|
||||||
x += BLOCK.section.label.labelPadding.left;
|
|
||||||
const sz = BLOCK.section.label.labelAttrs['font-size'];
|
|
||||||
const mask = svg.make('rect', Object.assign({
|
|
||||||
'x': x - BLOCK.section.label.maskPadding.left,
|
|
||||||
'y': this.currentY - sz * LINE_HEIGHT,
|
|
||||||
'height': sz * LINE_HEIGHT,
|
|
||||||
}, BLOCK.section.label.maskAttrs));
|
|
||||||
const labelLabel = svg.make('text', Object.assign({
|
|
||||||
'x': x,
|
|
||||||
'y': this.currentY - sz * (LINE_HEIGHT - 1),
|
|
||||||
}, BLOCK.section.label.labelAttrs));
|
|
||||||
labelLabel.appendChild(svg.makeText(label));
|
|
||||||
this.actions.appendChild(mask);
|
|
||||||
this.actions.appendChild(labelLabel);
|
|
||||||
const w = (
|
|
||||||
labelLabel.getComputedTextLength() +
|
|
||||||
BLOCK.section.label.maskPadding.left +
|
|
||||||
BLOCK.section.label.maskPadding.right
|
|
||||||
);
|
|
||||||
mask.setAttribute('width', w);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentY += BLOCK.section.padding.top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSectionEnd(/*scope, block, section*/) {
|
renderSectionEnd(/*scope, block, section*/) {
|
||||||
|
@ -920,46 +891,20 @@ define([
|
||||||
this.renderAction[stage.type](stage);
|
this.renderAction[stage.type](stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
testTextWidth(attrs, content) {
|
|
||||||
let tester = this.testersCache.get(attrs);
|
|
||||||
if(!tester) {
|
|
||||||
const text = svg.makeText();
|
|
||||||
const node = svg.make('text', attrs);
|
|
||||||
node.appendChild(text);
|
|
||||||
this.testers.appendChild(node);
|
|
||||||
tester = {text, node};
|
|
||||||
this.testersCache.set(attrs, tester);
|
|
||||||
}
|
|
||||||
|
|
||||||
tester.text.nodeValue = content;
|
|
||||||
return tester.node.getComputedTextLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
buildAgentInfos(agents, stages) {
|
buildAgentInfos(agents, stages) {
|
||||||
svg.empty(this.testers);
|
|
||||||
this.testersCache.clear();
|
|
||||||
this.base.appendChild(this.testers);
|
|
||||||
|
|
||||||
this.agentInfos = new Map();
|
this.agentInfos = new Map();
|
||||||
agents.forEach((agent, index) => {
|
agents.forEach((agent, index) => {
|
||||||
this.agentInfos.set(agent, {
|
this.agentInfos.set(agent, {
|
||||||
label: agent,
|
label: agent,
|
||||||
labelWidth: (
|
|
||||||
this.testTextWidth(ATTRS.AGENT_BOX_LABEL, agent) +
|
|
||||||
AGENT_BOX_PADDING * 2
|
|
||||||
),
|
|
||||||
index,
|
index,
|
||||||
x: null,
|
x: null,
|
||||||
latestYStart: null,
|
latestYStart: null,
|
||||||
separations: new Map(),
|
separations: new Map(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.agentInfos.get('[').labelWidth = 0;
|
|
||||||
this.agentInfos.get(']').labelWidth = 0;
|
|
||||||
|
|
||||||
this.visibleAgents = ['[', ']'];
|
this.visibleAgents = ['[', ']'];
|
||||||
traverse(stages, this.separationTraversalFns);
|
traverse(stages, this.separationTraversalFns);
|
||||||
this.base.removeChild(this.testers);
|
|
||||||
|
|
||||||
agents.forEach((agent) => {
|
agents.forEach((agent) => {
|
||||||
const agentInfo = this.agentInfos.get(agent);
|
const agentInfo = this.agentInfos.get(agent);
|
||||||
|
@ -999,10 +944,11 @@ define([
|
||||||
|
|
||||||
render({meta, agents, stages}) {
|
render({meta, agents, stages}) {
|
||||||
svg.empty(this.agentLines);
|
svg.empty(this.agentLines);
|
||||||
|
svg.empty(this.mask);
|
||||||
svg.empty(this.blocks);
|
svg.empty(this.blocks);
|
||||||
svg.empty(this.sections);
|
svg.empty(this.sections);
|
||||||
svg.empty(this.agentDecor);
|
svg.empty(this.actionShapes);
|
||||||
svg.empty(this.actions);
|
svg.empty(this.actionLabels);
|
||||||
|
|
||||||
this.title.setText(meta.title);
|
this.title.setText(meta.title);
|
||||||
|
|
||||||
|
@ -1015,6 +961,9 @@ define([
|
||||||
|
|
||||||
const stagesHeight = Math.max(this.currentY - ACTION_MARGIN, 0);
|
const stagesHeight = Math.max(this.currentY - ACTION_MARGIN, 0);
|
||||||
this.updateBounds(stagesHeight);
|
this.updateBounds(stagesHeight);
|
||||||
|
|
||||||
|
this.sizer.resetCache();
|
||||||
|
this.sizer.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAgentX(name) {
|
getAgentX(name) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
|
||||||
|
|
||||||
const element = renderer.svg();
|
const element = renderer.svg();
|
||||||
const line = element.getElementsByClassName('agent-1-line')[0];
|
const line = element.getElementsByClassName('agent-1-line')[0];
|
||||||
const drawnX = Number(line.getAttribute('d').split(' ')[1]);
|
const drawnX = Number(line.getAttribute('x1'));
|
||||||
|
|
||||||
expect(drawnX).toEqual(renderer.getAgentX('A'));
|
expect(drawnX).toEqual(renderer.getAgentX('A'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
define(['./SVGUtilities', './SVGTextBlock'], (svg, SVGTextBlock) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function renderBox(attrs, position) {
|
||||||
|
return svg.make('rect', Object.assign({}, position, attrs));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNote(attrs, flickAttrs, position) {
|
||||||
|
const g = svg.make('g');
|
||||||
|
const x0 = position.x;
|
||||||
|
const x1 = position.x + position.width;
|
||||||
|
const y0 = position.y;
|
||||||
|
const y1 = position.y + position.height;
|
||||||
|
const flick = 7;
|
||||||
|
|
||||||
|
g.appendChild(svg.make('polygon', Object.assign({
|
||||||
|
'points': (
|
||||||
|
x0 + ' ' + y0 + ' ' +
|
||||||
|
(x1 - flick) + ' ' + y0 + ' ' +
|
||||||
|
x1 + ' ' + (y0 + flick) + ' ' +
|
||||||
|
x1 + ' ' + y1 + ' ' +
|
||||||
|
x0 + ' ' + y1
|
||||||
|
),
|
||||||
|
}, attrs)));
|
||||||
|
|
||||||
|
g.appendChild(svg.make('polyline', Object.assign({
|
||||||
|
'points': (
|
||||||
|
(x1 - flick) + ' ' + y0 + ' ' +
|
||||||
|
(x1 - flick) + ' ' + (y0 + flick) + ' ' +
|
||||||
|
x1 + ' ' + (y0 + flick)
|
||||||
|
),
|
||||||
|
}, flickAttrs)));
|
||||||
|
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoxedText(text, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
padding,
|
||||||
|
boxAttrs,
|
||||||
|
labelAttrs,
|
||||||
|
boxLayer,
|
||||||
|
labelLayer,
|
||||||
|
boxRenderer = null,
|
||||||
|
}) {
|
||||||
|
if(!text) {
|
||||||
|
return {width: 0, height: 0, label: null, box: null};
|
||||||
|
}
|
||||||
|
|
||||||
|
let shift = 0;
|
||||||
|
let anchorX = x;
|
||||||
|
switch(labelAttrs['text-anchor']) {
|
||||||
|
case 'middle':
|
||||||
|
shift = 0.5;
|
||||||
|
anchorX += (padding.left - padding.right) / 2;
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
shift = 1;
|
||||||
|
anchorX -= padding.right;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shift = 0;
|
||||||
|
anchorX += padding.left;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = new SVGTextBlock(labelLayer, labelAttrs, {
|
||||||
|
text,
|
||||||
|
x: anchorX,
|
||||||
|
y: y + padding.top,
|
||||||
|
});
|
||||||
|
|
||||||
|
const width = (label.width + padding.left + padding.right);
|
||||||
|
const height = (label.height + padding.top + padding.bottom);
|
||||||
|
|
||||||
|
let box = null;
|
||||||
|
if(boxRenderer) {
|
||||||
|
box = boxRenderer({
|
||||||
|
'x': anchorX - label.width * shift - padding.left,
|
||||||
|
'y': y,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
box = renderBox(boxAttrs, {
|
||||||
|
'x': anchorX - label.width * shift - padding.left,
|
||||||
|
'y': y,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(boxLayer === labelLayer) {
|
||||||
|
boxLayer.insertBefore(box, label.firstLine());
|
||||||
|
} else {
|
||||||
|
boxLayer.appendChild(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {width, height, label, box};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderBox,
|
||||||
|
renderNote,
|
||||||
|
renderBoxedText,
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,107 @@
|
||||||
|
defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('renderBox', () => {
|
||||||
|
it('returns a simple rect SVG element', () => {
|
||||||
|
const node = SVGShapes.renderBox({
|
||||||
|
'foo': 'bar',
|
||||||
|
}, {
|
||||||
|
'x': 10,
|
||||||
|
'y': 20,
|
||||||
|
'width': 30,
|
||||||
|
'height': 40,
|
||||||
|
});
|
||||||
|
expect(node.tagName).toEqual('rect');
|
||||||
|
expect(node.getAttribute('foo')).toEqual('bar');
|
||||||
|
expect(node.getAttribute('x')).toEqual('10');
|
||||||
|
expect(node.getAttribute('y')).toEqual('20');
|
||||||
|
expect(node.getAttribute('width')).toEqual('30');
|
||||||
|
expect(node.getAttribute('height')).toEqual('40');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderNote', () => {
|
||||||
|
it('returns a group containing a rectangle with a page flick', () => {
|
||||||
|
const node = SVGShapes.renderNote({
|
||||||
|
'foo': 'bar',
|
||||||
|
}, {
|
||||||
|
'zig': 'zag',
|
||||||
|
}, {
|
||||||
|
'x': 10,
|
||||||
|
'y': 20,
|
||||||
|
'width': 30,
|
||||||
|
'height': 40,
|
||||||
|
});
|
||||||
|
expect(node.tagName).toEqual('g');
|
||||||
|
expect(node.children.length).toEqual(2);
|
||||||
|
const back = node.children[0];
|
||||||
|
expect(back.getAttribute('foo')).toEqual('bar');
|
||||||
|
expect(back.getAttribute('points')).toEqual(
|
||||||
|
'10 20 ' +
|
||||||
|
'33 20 ' +
|
||||||
|
'40 27 ' +
|
||||||
|
'40 60 ' +
|
||||||
|
'10 60'
|
||||||
|
);
|
||||||
|
const flick = node.children[1];
|
||||||
|
expect(flick.getAttribute('zig')).toEqual('zag');
|
||||||
|
expect(flick.getAttribute('points')).toEqual(
|
||||||
|
'33 20 ' +
|
||||||
|
'33 27 ' +
|
||||||
|
'40 27'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderBoxedText', () => {
|
||||||
|
it('renders a label', () => {
|
||||||
|
const o = document.createElement('p');
|
||||||
|
const rendered = SVGShapes.renderBoxedText('foo', {
|
||||||
|
x: 1,
|
||||||
|
y: 2,
|
||||||
|
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
||||||
|
boxAttrs: {},
|
||||||
|
labelAttrs: {'font-size': 10, 'line-height': 1.5, 'foo': 'bar'},
|
||||||
|
boxLayer: o,
|
||||||
|
labelLayer: o,
|
||||||
|
});
|
||||||
|
expect(rendered.label.text).toEqual('foo');
|
||||||
|
expect(rendered.label.x).toEqual(5);
|
||||||
|
expect(rendered.label.y).toEqual(10);
|
||||||
|
expect(rendered.label.firstLine().parentNode).toEqual(o);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('positions a box beneath the rendered label', () => {
|
||||||
|
const o = document.createElement('p');
|
||||||
|
const rendered = SVGShapes.renderBoxedText('foo', {
|
||||||
|
x: 1,
|
||||||
|
y: 2,
|
||||||
|
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
||||||
|
boxAttrs: {'foo': 'bar'},
|
||||||
|
labelAttrs: {'font-size': 10, 'line-height': 1.5},
|
||||||
|
boxLayer: o,
|
||||||
|
labelLayer: o,
|
||||||
|
});
|
||||||
|
expect(rendered.box.getAttribute('x')).toEqual('1');
|
||||||
|
expect(rendered.box.getAttribute('y')).toEqual('2');
|
||||||
|
expect(rendered.box.getAttribute('height')).toEqual('55');
|
||||||
|
expect(rendered.box.getAttribute('foo')).toEqual('bar');
|
||||||
|
expect(rendered.box.parentNode).toEqual(o);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the size of the rendered box', () => {
|
||||||
|
const o = document.createElement('p');
|
||||||
|
const rendered = SVGShapes.renderBoxedText('foo', {
|
||||||
|
x: 1,
|
||||||
|
y: 2,
|
||||||
|
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
||||||
|
boxAttrs: {},
|
||||||
|
labelAttrs: {'font-size': 10, 'line-height': 1.5},
|
||||||
|
boxLayer: o,
|
||||||
|
labelLayer: o,
|
||||||
|
});
|
||||||
|
expect(rendered.width).toBeGreaterThan(20 - 1);
|
||||||
|
expect(rendered.height).toEqual(55);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,16 +1,23 @@
|
||||||
define(['./SVGUtilities'], (svg) => {
|
define(['./SVGUtilities'], (svg) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return class SVGTextBlock {
|
function fontDetails(attrs) {
|
||||||
|
const size = Number(attrs['font-size']);
|
||||||
|
const lineHeight = size * (Number(attrs['line-height']) || 1);
|
||||||
|
return {
|
||||||
|
size,
|
||||||
|
lineHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SVGTextBlock {
|
||||||
constructor(
|
constructor(
|
||||||
container,
|
container,
|
||||||
attrs,
|
attrs,
|
||||||
lineHeight,
|
|
||||||
{text = '', x = 0, y = 0} = {}
|
{text = '', x = 0, y = 0} = {}
|
||||||
) {
|
) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.attrs = attrs;
|
this.attrs = attrs;
|
||||||
this.lineHeight = lineHeight;
|
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
@ -21,12 +28,11 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateY() {
|
_updateY() {
|
||||||
const sz = Number(this.attrs['font-size']);
|
const {size, lineHeight} = fontDetails(this.attrs);
|
||||||
const space = sz * this.lineHeight;
|
|
||||||
this.nodes.forEach(({element}, i) => {
|
this.nodes.forEach(({element}, i) => {
|
||||||
element.setAttribute('y', this.y + i * space + sz);
|
element.setAttribute('y', this.y + i * lineHeight + size);
|
||||||
});
|
});
|
||||||
this.height = space * this.nodes.length;
|
this.height = lineHeight * this.nodes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
_rebuildNodes(count) {
|
_rebuildNodes(count) {
|
||||||
|
@ -51,6 +57,14 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this._updateY();
|
this._updateY();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firstLine() {
|
||||||
|
if(this.nodes.length > 0) {
|
||||||
|
return this.nodes[0].element;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setText(newText) {
|
setText(newText) {
|
||||||
if(newText === this.text) {
|
if(newText === this.text) {
|
||||||
return;
|
return;
|
||||||
|
@ -93,5 +107,69 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
class SizeTester {
|
||||||
|
constructor(container) {
|
||||||
|
this.testers = svg.make('g', {'display': 'none'});
|
||||||
|
this.container = container;
|
||||||
|
this.cache = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
measure(attrs, content) {
|
||||||
|
if(!content) {
|
||||||
|
return {width: 0, height: 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
let tester = this.cache.get(attrs);
|
||||||
|
if(!tester) {
|
||||||
|
const text = svg.makeText();
|
||||||
|
const node = svg.make('text', attrs);
|
||||||
|
node.appendChild(text);
|
||||||
|
this.testers.appendChild(node);
|
||||||
|
tester = {text, node};
|
||||||
|
this.cache.set(attrs, tester);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.testers.parentNode) {
|
||||||
|
this.container.appendChild(this.testers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = content.split('\n');
|
||||||
|
let width = 0;
|
||||||
|
lines.forEach((line) => {
|
||||||
|
tester.text.nodeValue = line;
|
||||||
|
width = Math.max(width, tester.node.getComputedTextLength());
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height: lines.length * fontDetails(attrs).lineHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
measureHeight(attrs, content) {
|
||||||
|
if(!content) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = content.split('\n');
|
||||||
|
return lines.length * fontDetails(attrs).lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCache() {
|
||||||
|
svg.empty(this.testers);
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
detach() {
|
||||||
|
if(this.testers.parentNode) {
|
||||||
|
this.container.removeChild(this.testers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SVGTextBlock.SizeTester = SizeTester;
|
||||||
|
|
||||||
|
return SVGTextBlock;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
defineDescribe('SVGTextBlock', [
|
||||||
|
'./SVGTextBlock',
|
||||||
|
'./SVGUtilities',
|
||||||
|
], (
|
||||||
|
SVGTextBlock,
|
||||||
|
svg
|
||||||
|
) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const attrs = {'font-size': 10};
|
const attrs = {'font-size': 10, 'line-height': 1.5};
|
||||||
let hold = null;
|
let hold = null;
|
||||||
let block = null;
|
let block = null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hold = document.createElement('p');
|
hold = svg.makeContainer();
|
||||||
document.body.appendChild(hold);
|
document.body.appendChild(hold);
|
||||||
block = new SVGTextBlock(hold, attrs, 1.5);
|
block = new SVGTextBlock(hold, attrs);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -24,19 +30,19 @@ defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds the given text if specified', () => {
|
it('adds the given text if specified', () => {
|
||||||
block = new SVGTextBlock(hold, attrs, 1.5, {text: 'abc'});
|
block = new SVGTextBlock(hold, attrs, {text: 'abc'});
|
||||||
expect(block.text).toEqual('abc');
|
expect(block.text).toEqual('abc');
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the given coordinates if specified', () => {
|
it('uses the given coordinates if specified', () => {
|
||||||
block = new SVGTextBlock(hold, attrs, 1.5, {x: 5, y: 7});
|
block = new SVGTextBlock(hold, attrs, {x: 5, y: 7});
|
||||||
expect(block.x).toEqual(5);
|
expect(block.x).toEqual(5);
|
||||||
expect(block.y).toEqual(7);
|
expect(block.y).toEqual(7);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setText', () => {
|
describe('.setText', () => {
|
||||||
it('sets the text to the given content', () => {
|
it('sets the text to the given content', () => {
|
||||||
block.setText('foo');
|
block.setText('foo');
|
||||||
expect(block.text).toEqual('foo');
|
expect(block.text).toEqual('foo');
|
||||||
|
@ -51,6 +57,12 @@ defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
||||||
expect(hold.children[1].innerHTML).toEqual('bar');
|
expect(hold.children[1].innerHTML).toEqual('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('populates width and height with the size of the text', () => {
|
||||||
|
block.setText('foo\nbar');
|
||||||
|
expect(block.width).toBeGreaterThan(0);
|
||||||
|
expect(block.height).toEqual(30);
|
||||||
|
});
|
||||||
|
|
||||||
it('re-uses text nodes when possible, adding more if needed', () => {
|
it('re-uses text nodes when possible, adding more if needed', () => {
|
||||||
block.setText('foo\nbar');
|
block.setText('foo\nbar');
|
||||||
const line0 = hold.children[0];
|
const line0 = hold.children[0];
|
||||||
|
@ -89,7 +101,7 @@ defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('reanchor', () => {
|
describe('.reanchor', () => {
|
||||||
it('moves all nodes', () => {
|
it('moves all nodes', () => {
|
||||||
block.setText('foo\nbaz');
|
block.setText('foo\nbaz');
|
||||||
block.reanchor(5, 7);
|
block.reanchor(5, 7);
|
||||||
|
@ -100,7 +112,7 @@ defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('clear', () => {
|
describe('.clear', () => {
|
||||||
it('resets the text empty', () => {
|
it('resets the text empty', () => {
|
||||||
block.setText('foo\nbaz');
|
block.setText('foo\nbaz');
|
||||||
block.setText('');
|
block.setText('');
|
||||||
|
@ -110,4 +122,80 @@ defineDescribe('SVGTextBlock', ['./SVGTextBlock'], (SVGTextBlock) => {
|
||||||
expect(block.height).toEqual(0);
|
expect(block.height).toEqual(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('SizeTester', () => {
|
||||||
|
let tester = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tester = new SVGTextBlock.SizeTester(hold);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.measure', () => {
|
||||||
|
it('calculates the size of the rendered text', () => {
|
||||||
|
const size = tester.measure(attrs, 'foo');
|
||||||
|
expect(size.width).toBeGreaterThan(0);
|
||||||
|
expect(size.height).toEqual(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('measures multiline text', () => {
|
||||||
|
const size = tester.measure(attrs, 'foo\nbar');
|
||||||
|
expect(size.width).toBeGreaterThan(0);
|
||||||
|
expect(size.height).toEqual(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 0, 0 for empty content', () => {
|
||||||
|
const size = tester.measure(attrs, '');
|
||||||
|
expect(size.width).toEqual(0);
|
||||||
|
expect(size.height).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the maximum width for multiline text', () => {
|
||||||
|
const size0 = tester.measure(attrs, 'foo');
|
||||||
|
const size1 = tester.measure(attrs, 'longline');
|
||||||
|
const size = tester.measure(attrs, 'foo\nlongline\nfoo');
|
||||||
|
expect(size1.width).toBeGreaterThan(size0.width);
|
||||||
|
expect(size.width).toEqual(size1.width);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.measureHeight', () => {
|
||||||
|
it('calculates the height of the rendered text', () => {
|
||||||
|
const height = tester.measureHeight(attrs, 'foo');
|
||||||
|
expect(height).toEqual(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('measures multiline text', () => {
|
||||||
|
const height = tester.measureHeight(attrs, 'foo\nbar');
|
||||||
|
expect(height).toEqual(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 0 for empty content', () => {
|
||||||
|
const height = tester.measureHeight(attrs, '');
|
||||||
|
expect(height).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not require the container', () => {
|
||||||
|
tester.measureHeight(attrs, 'foo');
|
||||||
|
expect(hold.children.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.detach', () => {
|
||||||
|
it('removes the test node from the DOM', () => {
|
||||||
|
tester.measure(attrs, 'foo');
|
||||||
|
expect(hold.children.length).toEqual(1);
|
||||||
|
tester.detach();
|
||||||
|
expect(hold.children.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not prevent using the tester again later', () => {
|
||||||
|
tester.measure(attrs, 'foo');
|
||||||
|
tester.detach();
|
||||||
|
|
||||||
|
const size = tester.measure(attrs, 'foo');
|
||||||
|
expect(hold.children.length).toEqual(1);
|
||||||
|
expect(size.width).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,4 +6,5 @@ define([
|
||||||
'sequence/ArrayUtilities_spec',
|
'sequence/ArrayUtilities_spec',
|
||||||
'sequence/SVGUtilities_spec',
|
'sequence/SVGUtilities_spec',
|
||||||
'sequence/SVGTextBlock_spec',
|
'sequence/SVGTextBlock_spec',
|
||||||
|
'sequence/SVGShapes_spec',
|
||||||
]);
|
]);
|
||||||
|
|