Begin separating theme out of renderer
This commit is contained in:
parent
1aac54cefc
commit
b0ba84b4eb
|
@ -3,16 +3,19 @@
|
||||||
|
|
||||||
requirejs.config(window.getRequirejsCDN());
|
requirejs.config(window.getRequirejsCDN());
|
||||||
|
|
||||||
|
/* jshint -W072 */
|
||||||
requirejs([
|
requirejs([
|
||||||
'interface/Interface',
|
'interface/Interface',
|
||||||
'sequence/Parser',
|
'sequence/Parser',
|
||||||
'sequence/Generator',
|
'sequence/Generator',
|
||||||
'sequence/Renderer',
|
'sequence/Renderer',
|
||||||
|
'sequence/themes/Basic',
|
||||||
], (
|
], (
|
||||||
Interface,
|
Interface,
|
||||||
Parser,
|
Parser,
|
||||||
Generator,
|
Generator,
|
||||||
Renderer
|
Renderer,
|
||||||
|
Theme
|
||||||
) => {
|
) => {
|
||||||
const defaultCode = (
|
const defaultCode = (
|
||||||
'title Labyrinth\n' +
|
'title Labyrinth\n' +
|
||||||
|
@ -36,7 +39,7 @@
|
||||||
defaultCode,
|
defaultCode,
|
||||||
parser: new Parser(),
|
parser: new Parser(),
|
||||||
generator: new Generator(),
|
generator: new Generator(),
|
||||||
renderer: new Renderer(),
|
renderer: new Renderer(new Theme()),
|
||||||
});
|
});
|
||||||
ui.build(document.body);
|
ui.build(document.body);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
define(['./ArrayUtilities'], (array) => {
|
define(['core/ArrayUtilities'], (array) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class AgentState {
|
class AgentState {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
define([
|
define([
|
||||||
'./ArrayUtilities',
|
'core/ArrayUtilities',
|
||||||
'./SVGUtilities',
|
'svg/SVGUtilities',
|
||||||
'./SVGTextBlock',
|
'svg/SVGTextBlock',
|
||||||
'./SVGShapes',
|
'svg/SVGShapes',
|
||||||
], (
|
], (
|
||||||
array,
|
array,
|
||||||
svg,
|
svg,
|
||||||
|
@ -13,219 +13,6 @@ define([
|
||||||
|
|
||||||
const SEP_ZERO = {left: 0, right: 0};
|
const SEP_ZERO = {left: 0, right: 0};
|
||||||
|
|
||||||
const LINE_HEIGHT = 1.3;
|
|
||||||
const TITLE_MARGIN = 10;
|
|
||||||
const OUTER_MARGIN = 5;
|
|
||||||
const AGENT_MARGIN = 10;
|
|
||||||
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 = {
|
|
||||||
lineAttrs: {
|
|
||||||
'solid': {
|
|
||||||
'fill': 'none',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
},
|
|
||||||
'dash': {
|
|
||||||
'fill': 'none',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
'stroke-dasharray': '4, 2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
arrow: {
|
|
||||||
width: 4,
|
|
||||||
height: 8,
|
|
||||||
attrs: {
|
|
||||||
'fill': '#000000',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
'stroke-linejoin': 'miter',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
padding: 6,
|
|
||||||
margin: {top: 2, bottom: 1},
|
|
||||||
attrs: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-size': 8,
|
|
||||||
'line-height': LINE_HEIGHT,
|
|
||||||
'text-anchor': 'middle',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mask: {
|
|
||||||
padding: {
|
|
||||||
top: 0,
|
|
||||||
left: 3,
|
|
||||||
right: 3,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
maskAttrs: {
|
|
||||||
'fill': '#FFFFFF',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const BLOCK = {
|
|
||||||
margin: {
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
boxAttrs: {
|
|
||||||
'fill': 'none',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1.5,
|
|
||||||
'rx': 2,
|
|
||||||
'ry': 2,
|
|
||||||
},
|
|
||||||
section: {
|
|
||||||
padding: {
|
|
||||||
top: 3,
|
|
||||||
bottom: 2,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
padding: {
|
|
||||||
top: 1,
|
|
||||||
left: 3,
|
|
||||||
right: 3,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
boxAttrs: {
|
|
||||||
'fill': '#FFFFFF',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
'rx': 2,
|
|
||||||
'ry': 2,
|
|
||||||
},
|
|
||||||
labelAttrs: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-weight': 'bold',
|
|
||||||
'font-size': 9,
|
|
||||||
'line-height': LINE_HEIGHT,
|
|
||||||
'text-anchor': 'left',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
padding: {
|
|
||||||
top: 1,
|
|
||||||
left: 5,
|
|
||||||
right: 3,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
maskAttrs: {
|
|
||||||
'fill': '#FFFFFF',
|
|
||||||
},
|
|
||||||
labelAttrs: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-size': 8,
|
|
||||||
'line-height': LINE_HEIGHT,
|
|
||||||
'text-anchor': 'left',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
attrs: {
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1.5,
|
|
||||||
'stroke-dasharray': '4, 2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const NOTE = {
|
|
||||||
'note': {
|
|
||||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
|
||||||
padding: {top: 5, left: 5, right: 10, bottom: 5},
|
|
||||||
overlap: {left: 10, right: 10},
|
|
||||||
boxRenderer: SVGShapes.renderNote.bind(null, {
|
|
||||||
'fill': '#FFFFFF',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
}, {
|
|
||||||
'fill': 'none',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
}),
|
|
||||||
labelAttrs: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-size': 8,
|
|
||||||
'line-height': LINE_HEIGHT,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'state': {
|
|
||||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
|
||||||
padding: {top: 7, left: 7, right: 7, bottom: 7},
|
|
||||||
overlap: {left: 10, right: 10},
|
|
||||||
boxRenderer: SVGShapes.renderBox.bind(null, {
|
|
||||||
'fill': '#FFFFFF',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
'rx': 10,
|
|
||||||
'ry': 10,
|
|
||||||
}),
|
|
||||||
labelAttrs: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-size': 8,
|
|
||||||
'line-height': LINE_HEIGHT,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ATTRS = {
|
|
||||||
TITLE: {
|
|
||||||
'font-family': 'sans-serif',
|
|
||||||
'font-size': 20,
|
|
||||||
'line-height': LINE_HEIGHT,
|
|
||||||
'text-anchor': 'middle',
|
|
||||||
'class': 'title',
|
|
||||||
},
|
|
||||||
|
|
||||||
AGENT_LINE: {
|
|
||||||
'fill': 'none',
|
|
||||||
'stroke': '#000000',
|
|
||||||
'stroke-width': 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function drawHorizontalArrowHead(container, {x, y, dx, dy, attrs}) {
|
function drawHorizontalArrowHead(container, {x, y, dx, dy, attrs}) {
|
||||||
container.appendChild(svg.make(
|
container.appendChild(svg.make(
|
||||||
attrs.fill === 'none' ? 'polyline' : 'polygon',
|
attrs.fill === 'none' ? 'polyline' : 'polygon',
|
||||||
|
@ -265,7 +52,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
return class Renderer {
|
return class Renderer {
|
||||||
constructor() {
|
constructor(theme) {
|
||||||
this.separationAgentCap = {
|
this.separationAgentCap = {
|
||||||
'box': this.separationAgentCapBox.bind(this),
|
'box': this.separationAgentCapBox.bind(this),
|
||||||
'cross': this.separationAgentCapCross.bind(this),
|
'cross': this.separationAgentCapCross.bind(this),
|
||||||
|
@ -317,6 +104,8 @@ define([
|
||||||
|
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
|
this.theme = theme;
|
||||||
|
this.currentSequence = null;
|
||||||
this.buildStaticElements();
|
this.buildStaticElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +127,7 @@ define([
|
||||||
this.base.appendChild(this.sections);
|
this.base.appendChild(this.sections);
|
||||||
this.base.appendChild(this.actionShapes);
|
this.base.appendChild(this.actionShapes);
|
||||||
this.base.appendChild(this.actionLabels);
|
this.base.appendChild(this.actionLabels);
|
||||||
this.title = new SVGTextBlock(this.base, ATTRS.TITLE);
|
this.title = new SVGTextBlock(this.base);
|
||||||
|
|
||||||
this.sizer = new SVGTextBlock.SizeTester(this.base);
|
this.sizer = new SVGTextBlock.SizeTester(this.base);
|
||||||
}
|
}
|
||||||
|
@ -385,17 +174,18 @@ define([
|
||||||
this.addSeparation(
|
this.addSeparation(
|
||||||
agentR,
|
agentR,
|
||||||
agentL,
|
agentL,
|
||||||
sepR.left + sepL.right + AGENT_MARGIN
|
sepR.left + sepL.right + this.theme.agentMargin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
separationAgentCapBox({label}) {
|
separationAgentCapBox({label}) {
|
||||||
|
const config = this.theme.agentCap.box;
|
||||||
const width = (
|
const width = (
|
||||||
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
AGENT_CAP.box.padding.left +
|
config.padding.left +
|
||||||
AGENT_CAP.box.padding.right
|
config.padding.right
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -405,17 +195,19 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationAgentCapCross() {
|
separationAgentCapCross() {
|
||||||
|
const config = this.theme.agentCap.cross;
|
||||||
return {
|
return {
|
||||||
left: AGENT_CAP.cross.size / 2,
|
left: config.size / 2,
|
||||||
right: AGENT_CAP.cross.size / 2,
|
right: config.size / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
separationAgentCapBar({label}) {
|
separationAgentCapBar({label}) {
|
||||||
|
const config = this.theme.agentCap.box;
|
||||||
const width = (
|
const width = (
|
||||||
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
AGENT_CAP.box.padding.left +
|
config.padding.left +
|
||||||
AGENT_CAP.box.padding.right
|
config.padding.right
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -447,19 +239,21 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationConnection({agents, label}) {
|
separationConnection({agents, label}) {
|
||||||
|
const config = this.theme.connect;
|
||||||
|
|
||||||
this.addSeparation(
|
this.addSeparation(
|
||||||
agents[0],
|
agents[0],
|
||||||
agents[1],
|
agents[1],
|
||||||
|
|
||||||
this.sizer.measure(CONNECT.label.attrs, label).width +
|
this.sizer.measure(config.label.attrs, label).width +
|
||||||
CONNECT.arrow.width * 2 +
|
config.arrow.width * 2 +
|
||||||
CONNECT.label.padding * 2 +
|
config.label.padding * 2 +
|
||||||
ATTRS.AGENT_LINE['stroke-width']
|
this.theme.agentLineAttrs['stroke-width']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
separationNoteOver({agents, mode, label}) {
|
separationNoteOver({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
const width = (
|
const width = (
|
||||||
this.sizer.measure(config.labelAttrs, label).width +
|
this.sizer.measure(config.labelAttrs, label).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
|
@ -491,7 +285,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationNoteLeft({agents, mode, label}) {
|
separationNoteLeft({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
const {left} = this.findExtremes(agents);
|
const {left} = this.findExtremes(agents);
|
||||||
|
|
||||||
const agentSpaces = new Map();
|
const agentSpaces = new Map();
|
||||||
|
@ -509,7 +303,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationNoteRight({agents, mode, label}) {
|
separationNoteRight({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
const {right} = this.findExtremes(agents);
|
const {right} = this.findExtremes(agents);
|
||||||
|
|
||||||
const agentSpaces = new Map();
|
const agentSpaces = new Map();
|
||||||
|
@ -527,7 +321,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationNoteBetween({agents, mode, label}) {
|
separationNoteBetween({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
const {left, right} = this.findExtremes(agents);
|
const {left, right} = this.findExtremes(agents);
|
||||||
|
|
||||||
this.addSeparation(
|
this.addSeparation(
|
||||||
|
@ -548,7 +342,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separationSectionBegin(scope, {left, right}, {mode, label}) {
|
separationSectionBegin(scope, {left, right}, {mode, label}) {
|
||||||
const config = BLOCK.section;
|
const config = this.theme.block.section;
|
||||||
const width = (
|
const width = (
|
||||||
this.sizer.measure(config.mode.labelAttrs, mode).width +
|
this.sizer.measure(config.mode.labelAttrs, mode).width +
|
||||||
config.mode.padding.left +
|
config.mode.padding.left +
|
||||||
|
@ -569,12 +363,13 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapBox({x, label}) {
|
renderAgentCapBox({x, label}) {
|
||||||
|
const config = this.theme.agentCap.box;
|
||||||
const {height} = SVGShapes.renderBoxedText(label, {
|
const {height} = SVGShapes.renderBoxedText(label, {
|
||||||
x,
|
x,
|
||||||
y: this.currentY,
|
y: this.currentY,
|
||||||
padding: AGENT_CAP.box.padding,
|
padding: config.padding,
|
||||||
boxAttrs: AGENT_CAP.box.boxAttrs,
|
boxAttrs: config.boxAttrs,
|
||||||
labelAttrs: AGENT_CAP.box.labelAttrs,
|
labelAttrs: config.labelAttrs,
|
||||||
boxLayer: this.actionShapes,
|
boxLayer: this.actionShapes,
|
||||||
labelLayer: this.actionLabels,
|
labelLayer: this.actionLabels,
|
||||||
});
|
});
|
||||||
|
@ -587,8 +382,9 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapCross({x}) {
|
renderAgentCapCross({x}) {
|
||||||
|
const config = this.theme.agentCap.cross;
|
||||||
const y = this.currentY;
|
const y = this.currentY;
|
||||||
const d = AGENT_CAP.cross.size / 2;
|
const d = config.size / 2;
|
||||||
|
|
||||||
this.actionShapes.appendChild(svg.make('path', Object.assign({
|
this.actionShapes.appendChild(svg.make('path', Object.assign({
|
||||||
'd': (
|
'd': (
|
||||||
|
@ -597,7 +393,7 @@ define([
|
||||||
' M ' + (x + d) + ' ' + y +
|
' M ' + (x + d) + ' ' + y +
|
||||||
' L ' + (x - d) + ' ' + (y + d * 2)
|
' L ' + (x - d) + ' ' + (y + d * 2)
|
||||||
),
|
),
|
||||||
}, AGENT_CAP.cross.attrs)));
|
}, config.attrs)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineTop: d,
|
lineTop: d,
|
||||||
|
@ -607,30 +403,33 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapBar({x, label}) {
|
renderAgentCapBar({x, label}) {
|
||||||
|
const configB = this.theme.agentCap.box;
|
||||||
|
const config = this.theme.agentCap.bar;
|
||||||
const width = (
|
const width = (
|
||||||
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
this.sizer.measure(configB.labelAttrs, label).width +
|
||||||
AGENT_CAP.box.padding.left +
|
configB.padding.left +
|
||||||
AGENT_CAP.box.padding.right
|
configB.padding.right
|
||||||
);
|
);
|
||||||
|
|
||||||
this.actionShapes.appendChild(svg.make('rect', Object.assign({
|
this.actionShapes.appendChild(svg.make('rect', Object.assign({
|
||||||
'x': x - width / 2,
|
'x': x - width / 2,
|
||||||
'y': this.currentY,
|
'y': this.currentY,
|
||||||
'width': width,
|
'width': width,
|
||||||
}, AGENT_CAP.bar.attrs)));
|
}, config.attrs)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineTop: 0,
|
lineTop: 0,
|
||||||
lineBottom: AGENT_CAP.bar.attrs.height,
|
lineBottom: config.attrs.height,
|
||||||
height: AGENT_CAP.bar.attrs.height,
|
height: config.attrs.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentCapNone() {
|
renderAgentCapNone() {
|
||||||
|
const config = this.theme.agentCap.none;
|
||||||
return {
|
return {
|
||||||
lineTop: AGENT_CAP.none.height,
|
lineTop: config.height,
|
||||||
lineBottom: 0,
|
lineBottom: 0,
|
||||||
height: AGENT_CAP.none.height,
|
height: config.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,7 +441,7 @@ define([
|
||||||
maxHeight = Math.max(maxHeight, shifts.height);
|
maxHeight = Math.max(maxHeight, shifts.height);
|
||||||
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
|
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
|
||||||
});
|
});
|
||||||
this.currentY += maxHeight + ACTION_MARGIN;
|
this.currentY += maxHeight + this.theme.actionMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAgentEnd({mode, agents}) {
|
renderAgentEnd({mode, agents}) {
|
||||||
|
@ -658,34 +457,35 @@ define([
|
||||||
'x2': x,
|
'x2': x,
|
||||||
'y2': this.currentY + shifts.lineTop,
|
'y2': this.currentY + shifts.lineTop,
|
||||||
'class': 'agent-' + agentInfo.index + '-line',
|
'class': 'agent-' + agentInfo.index + '-line',
|
||||||
}, ATTRS.AGENT_LINE)));
|
}, this.theme.agentLineAttrs)));
|
||||||
agentInfo.latestYStart = null;
|
agentInfo.latestYStart = null;
|
||||||
});
|
});
|
||||||
this.currentY += maxHeight + ACTION_MARGIN;
|
this.currentY += maxHeight + this.theme.actionMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderConnection({label, agents, line, left, right}) {
|
renderConnection({label, agents, line, left, right}) {
|
||||||
|
const config = this.theme.connect;
|
||||||
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 = config.arrow.height / 2;
|
||||||
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 = this.theme.agentLineAttrs['stroke-width'];
|
||||||
|
|
||||||
const height = (
|
const height = (
|
||||||
this.sizer.measureHeight(CONNECT.label.attrs, label) +
|
this.sizer.measureHeight(config.label.attrs, label) +
|
||||||
CONNECT.label.margin.top +
|
config.label.margin.top +
|
||||||
CONNECT.label.margin.bottom
|
config.label.margin.bottom
|
||||||
);
|
);
|
||||||
|
|
||||||
let y = this.currentY + Math.max(dy, height);
|
let y = this.currentY + Math.max(dy, height);
|
||||||
|
|
||||||
SVGShapes.renderBoxedText(label, {
|
SVGShapes.renderBoxedText(label, {
|
||||||
x: (from.x + to.x) / 2,
|
x: (from.x + to.x) / 2,
|
||||||
y: y - height + CONNECT.label.margin.top,
|
y: y - height + config.label.margin.top,
|
||||||
padding: CONNECT.mask.padding,
|
padding: config.mask.padding,
|
||||||
boxAttrs: CONNECT.mask.maskAttrs,
|
boxAttrs: config.mask.maskAttrs,
|
||||||
labelAttrs: CONNECT.label.attrs,
|
labelAttrs: config.label.attrs,
|
||||||
boxLayer: this.mask,
|
boxLayer: this.mask,
|
||||||
labelLayer: this.actionLabels,
|
labelLayer: this.actionLabels,
|
||||||
});
|
});
|
||||||
|
@ -695,15 +495,15 @@ define([
|
||||||
'y1': y,
|
'y1': y,
|
||||||
'x2': to.x - (right ? short : 0) * dir,
|
'x2': to.x - (right ? short : 0) * dir,
|
||||||
'y2': y,
|
'y2': y,
|
||||||
}, CONNECT.lineAttrs[line])));
|
}, config.lineAttrs[line])));
|
||||||
|
|
||||||
if(left) {
|
if(left) {
|
||||||
drawHorizontalArrowHead(this.actionShapes, {
|
drawHorizontalArrowHead(this.actionShapes, {
|
||||||
x: from.x + short * dir,
|
x: from.x + short * dir,
|
||||||
y,
|
y,
|
||||||
dx: CONNECT.arrow.width * dir,
|
dx: config.arrow.width * dir,
|
||||||
dy,
|
dy,
|
||||||
attrs: CONNECT.arrow.attrs,
|
attrs: config.arrow.attrs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,26 +511,26 @@ define([
|
||||||
drawHorizontalArrowHead(this.actionShapes, {
|
drawHorizontalArrowHead(this.actionShapes, {
|
||||||
x: to.x - short * dir,
|
x: to.x - short * dir,
|
||||||
y,
|
y,
|
||||||
dx: -CONNECT.arrow.width * dir,
|
dx: -config.arrow.width * dir,
|
||||||
dy,
|
dy,
|
||||||
attrs: CONNECT.arrow.attrs,
|
attrs: config.arrow.attrs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentY = y + dy + ACTION_MARGIN;
|
this.currentY = y + dy + this.theme.actionMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = this.theme.note[mode];
|
||||||
|
|
||||||
this.currentY += config.margin.top;
|
this.currentY += config.margin.top;
|
||||||
|
|
||||||
const y = this.currentY + config.padding.top;
|
const y = this.currentY + config.padding.top;
|
||||||
const labelNode = new SVGTextBlock(
|
const labelNode = new SVGTextBlock(this.actionLabels, {
|
||||||
this.actionLabels,
|
attrs: config.labelAttrs,
|
||||||
config.labelAttrs,
|
text: label,
|
||||||
{text: label, y}
|
y,
|
||||||
);
|
});
|
||||||
|
|
||||||
const fullW = (
|
const fullW = (
|
||||||
labelNode.width +
|
labelNode.width +
|
||||||
|
@ -752,16 +552,19 @@ define([
|
||||||
}
|
}
|
||||||
switch(config.labelAttrs['text-anchor']) {
|
switch(config.labelAttrs['text-anchor']) {
|
||||||
case 'middle':
|
case 'middle':
|
||||||
labelNode.reanchor((
|
labelNode.set({
|
||||||
x0 + config.padding.left +
|
x: (
|
||||||
x1 - config.padding.right
|
x0 + config.padding.left +
|
||||||
) / 2, y);
|
x1 - config.padding.right
|
||||||
|
) / 2,
|
||||||
|
y,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
labelNode.reanchor(x1 - config.padding.right, y);
|
labelNode.set({x: x1 - config.padding.right, y});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
labelNode.reanchor(x0 + config.padding.left, y);
|
labelNode.set({x: x0 + config.padding.left, y});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,12 +578,12 @@ define([
|
||||||
this.currentY += (
|
this.currentY += (
|
||||||
fullH +
|
fullH +
|
||||||
config.margin.bottom +
|
config.margin.bottom +
|
||||||
ACTION_MARGIN
|
this.theme.actionMargin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNoteOver({agents, mode, label}) {
|
renderNoteOver({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
|
|
||||||
if(agents.length > 1) {
|
if(agents.length > 1) {
|
||||||
const {left, right} = this.findExtremes(agents);
|
const {left, right} = this.findExtremes(agents);
|
||||||
|
@ -795,7 +598,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNoteLeft({agents, mode, label}) {
|
renderNoteLeft({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
|
|
||||||
const {left} = this.findExtremes(agents);
|
const {left} = this.findExtremes(agents);
|
||||||
const x1 = this.agentInfos.get(left).x - config.margin.right;
|
const x1 = this.agentInfos.get(left).x - config.margin.right;
|
||||||
|
@ -803,7 +606,7 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNoteRight({agents, mode, label}) {
|
renderNoteRight({agents, mode, label}) {
|
||||||
const config = NOTE[mode];
|
const config = this.theme.note[mode];
|
||||||
|
|
||||||
const {right} = this.findExtremes(agents);
|
const {right} = this.findExtremes(agents);
|
||||||
const x0 = this.agentInfos.get(right).x + config.margin.left;
|
const x0 = this.agentInfos.get(right).x + config.margin.left;
|
||||||
|
@ -821,34 +624,35 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBlockBegin(scope) {
|
renderBlockBegin(scope) {
|
||||||
this.currentY += BLOCK.margin.top;
|
this.currentY += this.theme.block.margin.top;
|
||||||
|
|
||||||
scope.y = this.currentY;
|
scope.y = this.currentY;
|
||||||
scope.first = true;
|
scope.first = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
||||||
|
const config = this.theme.block;
|
||||||
const agentInfoL = this.agentInfos.get(left);
|
const agentInfoL = this.agentInfos.get(left);
|
||||||
const agentInfoR = this.agentInfos.get(right);
|
const agentInfoR = this.agentInfos.get(right);
|
||||||
|
|
||||||
if(scope.first) {
|
if(scope.first) {
|
||||||
scope.first = false;
|
scope.first = false;
|
||||||
} else {
|
} else {
|
||||||
this.currentY += BLOCK.section.padding.bottom;
|
this.currentY += config.section.padding.bottom;
|
||||||
this.sections.appendChild(svg.make('line', Object.assign({
|
this.sections.appendChild(svg.make('line', Object.assign({
|
||||||
'x1': agentInfoL.x,
|
'x1': agentInfoL.x,
|
||||||
'y1': this.currentY,
|
'y1': this.currentY,
|
||||||
'x2': agentInfoR.x,
|
'x2': agentInfoR.x,
|
||||||
'y2': this.currentY,
|
'y2': this.currentY,
|
||||||
}, BLOCK.separator.attrs)));
|
}, config.separator.attrs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const modeRender = SVGShapes.renderBoxedText(mode, {
|
const modeRender = SVGShapes.renderBoxedText(mode, {
|
||||||
x: agentInfoL.x,
|
x: agentInfoL.x,
|
||||||
y: this.currentY,
|
y: this.currentY,
|
||||||
padding: BLOCK.section.mode.padding,
|
padding: config.section.mode.padding,
|
||||||
boxAttrs: BLOCK.section.mode.boxAttrs,
|
boxAttrs: config.section.mode.boxAttrs,
|
||||||
labelAttrs: BLOCK.section.mode.labelAttrs,
|
labelAttrs: config.section.mode.labelAttrs,
|
||||||
boxLayer: this.blocks,
|
boxLayer: this.blocks,
|
||||||
labelLayer: this.actionLabels,
|
labelLayer: this.actionLabels,
|
||||||
});
|
});
|
||||||
|
@ -856,16 +660,16 @@ define([
|
||||||
const labelRender = SVGShapes.renderBoxedText(label, {
|
const labelRender = SVGShapes.renderBoxedText(label, {
|
||||||
x: agentInfoL.x + modeRender.width,
|
x: agentInfoL.x + modeRender.width,
|
||||||
y: this.currentY,
|
y: this.currentY,
|
||||||
padding: BLOCK.section.label.padding,
|
padding: config.section.label.padding,
|
||||||
boxAttrs: BLOCK.section.label.maskAttrs,
|
boxAttrs: config.section.label.maskAttrs,
|
||||||
labelAttrs: BLOCK.section.label.labelAttrs,
|
labelAttrs: config.section.label.labelAttrs,
|
||||||
boxLayer: this.mask,
|
boxLayer: this.mask,
|
||||||
labelLayer: this.actionLabels,
|
labelLayer: this.actionLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.currentY += (
|
this.currentY += (
|
||||||
Math.max(modeRender.height, labelRender.height) +
|
Math.max(modeRender.height, labelRender.height) +
|
||||||
BLOCK.section.padding.top
|
config.section.padding.top
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,7 +677,8 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBlockEnd(scope, {left, right}) {
|
renderBlockEnd(scope, {left, right}) {
|
||||||
this.currentY += BLOCK.section.padding.bottom;
|
const config = this.theme.block;
|
||||||
|
this.currentY += config.section.padding.bottom;
|
||||||
|
|
||||||
const agentInfoL = this.agentInfos.get(left);
|
const agentInfoL = this.agentInfos.get(left);
|
||||||
const agentInfoR = this.agentInfos.get(right);
|
const agentInfoR = this.agentInfos.get(right);
|
||||||
|
@ -882,9 +687,9 @@ define([
|
||||||
'y': scope.y,
|
'y': scope.y,
|
||||||
'width': agentInfoR.x - agentInfoL.x,
|
'width': agentInfoR.x - agentInfoL.x,
|
||||||
'height': this.currentY - scope.y,
|
'height': this.currentY - scope.y,
|
||||||
}, BLOCK.boxAttrs)));
|
}, config.boxAttrs)));
|
||||||
|
|
||||||
this.currentY += BLOCK.margin.bottom + ACTION_MARGIN;
|
this.currentY += config.margin.bottom + this.theme.actionMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAction(stage) {
|
addAction(stage) {
|
||||||
|
@ -924,15 +729,16 @@ define([
|
||||||
updateBounds(stagesHeight) {
|
updateBounds(stagesHeight) {
|
||||||
const cx = (this.minX + this.maxX) / 2;
|
const cx = (this.minX + this.maxX) / 2;
|
||||||
const titleY = ((this.title.height > 0) ?
|
const titleY = ((this.title.height > 0) ?
|
||||||
(-TITLE_MARGIN - this.title.height) : 0
|
(-this.theme.titleMargin - this.title.height) : 0
|
||||||
);
|
);
|
||||||
this.title.reanchor(cx, titleY);
|
this.title.set({x: cx, y: titleY});
|
||||||
|
|
||||||
const halfTitleWidth = this.title.width / 2;
|
const halfTitleWidth = this.title.width / 2;
|
||||||
const x0 = Math.min(this.minX, cx - halfTitleWidth) - OUTER_MARGIN;
|
const margin = this.theme.outerMargin;
|
||||||
const x1 = Math.max(this.maxX, cx + halfTitleWidth) + OUTER_MARGIN;
|
const x0 = Math.min(this.minX, cx - halfTitleWidth) - margin;
|
||||||
const y0 = titleY - OUTER_MARGIN;
|
const x1 = Math.max(this.maxX, cx + halfTitleWidth) + margin;
|
||||||
const y1 = stagesHeight + OUTER_MARGIN;
|
const y0 = titleY - margin;
|
||||||
|
const y1 = stagesHeight + margin;
|
||||||
|
|
||||||
this.base.setAttribute('viewBox', (
|
this.base.setAttribute('viewBox', (
|
||||||
x0 + ' ' + y0 + ' ' +
|
x0 + ' ' + y0 + ' ' +
|
||||||
|
@ -942,7 +748,17 @@ define([
|
||||||
this.height = (y1 - y0);
|
this.height = (y1 - y0);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({meta, agents, stages}) {
|
setTheme(theme) {
|
||||||
|
if(this.theme === theme) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.theme = theme;
|
||||||
|
if(this.currentSequence) {
|
||||||
|
this.render(this.currentSequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(sequence) {
|
||||||
svg.empty(this.agentLines);
|
svg.empty(this.agentLines);
|
||||||
svg.empty(this.mask);
|
svg.empty(this.mask);
|
||||||
svg.empty(this.blocks);
|
svg.empty(this.blocks);
|
||||||
|
@ -950,20 +766,27 @@ define([
|
||||||
svg.empty(this.actionShapes);
|
svg.empty(this.actionShapes);
|
||||||
svg.empty(this.actionLabels);
|
svg.empty(this.actionLabels);
|
||||||
|
|
||||||
this.title.setText(meta.title);
|
this.title.set({
|
||||||
|
attrs: this.theme.titleAttrs,
|
||||||
|
text: sequence.meta.title,
|
||||||
|
});
|
||||||
|
|
||||||
this.minX = 0;
|
this.minX = 0;
|
||||||
this.maxX = 0;
|
this.maxX = 0;
|
||||||
this.buildAgentInfos(agents, stages);
|
this.buildAgentInfos(sequence.agents, sequence.stages);
|
||||||
|
|
||||||
this.currentY = 0;
|
this.currentY = 0;
|
||||||
traverse(stages, this.renderTraversalFns);
|
traverse(sequence.stages, this.renderTraversalFns);
|
||||||
|
|
||||||
const stagesHeight = Math.max(this.currentY - ACTION_MARGIN, 0);
|
const stagesHeight = Math.max(
|
||||||
|
this.currentY - this.theme.actionMargin,
|
||||||
|
0
|
||||||
|
);
|
||||||
this.updateBounds(stagesHeight);
|
this.updateBounds(stagesHeight);
|
||||||
|
|
||||||
this.sizer.resetCache();
|
this.sizer.resetCache();
|
||||||
this.sizer.detach();
|
this.sizer.detach();
|
||||||
|
this.currentSequence = sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAgentX(name) {
|
getAgentX(name) {
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
|
defineDescribe('Sequence Renderer', [
|
||||||
|
'./Renderer',
|
||||||
|
'./themes/Basic',
|
||||||
|
], (
|
||||||
|
Renderer,
|
||||||
|
Theme
|
||||||
|
) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let renderer = null;
|
let renderer = null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
renderer = new Renderer();
|
renderer = new Renderer(new Theme());
|
||||||
document.body.appendChild(renderer.svg());
|
document.body.appendChild(renderer.svg());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
define([
|
||||||
|
'core/ArrayUtilities',
|
||||||
|
'svg/SVGUtilities',
|
||||||
|
'svg/SVGTextBlock',
|
||||||
|
'svg/SVGShapes',
|
||||||
|
], (
|
||||||
|
array,
|
||||||
|
svg,
|
||||||
|
SVGTextBlock,
|
||||||
|
SVGShapes
|
||||||
|
) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const LINE_HEIGHT = 1.3;
|
||||||
|
|
||||||
|
const SETTINGS = {
|
||||||
|
titleMargin: 10,
|
||||||
|
outerMargin: 5,
|
||||||
|
agentMargin: 10,
|
||||||
|
actionMargin: 5,
|
||||||
|
|
||||||
|
agentCap: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
connect: {
|
||||||
|
lineAttrs: {
|
||||||
|
'solid': {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
},
|
||||||
|
'dash': {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
'stroke-dasharray': '4, 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
width: 4,
|
||||||
|
height: 8,
|
||||||
|
attrs: {
|
||||||
|
'fill': '#000000',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
'stroke-linejoin': 'miter',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
padding: 6,
|
||||||
|
margin: {top: 2, bottom: 1},
|
||||||
|
attrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
'text-anchor': 'middle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mask: {
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
left: 3,
|
||||||
|
right: 3,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
maskAttrs: {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
block: {
|
||||||
|
margin: {
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
boxAttrs: {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1.5,
|
||||||
|
'rx': 2,
|
||||||
|
'ry': 2,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
padding: {
|
||||||
|
top: 3,
|
||||||
|
bottom: 2,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
padding: {
|
||||||
|
top: 1,
|
||||||
|
left: 3,
|
||||||
|
right: 3,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
boxAttrs: {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
'rx': 2,
|
||||||
|
'ry': 2,
|
||||||
|
},
|
||||||
|
labelAttrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-weight': 'bold',
|
||||||
|
'font-size': 9,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
'text-anchor': 'left',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
padding: {
|
||||||
|
top: 1,
|
||||||
|
left: 5,
|
||||||
|
right: 3,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
maskAttrs: {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
},
|
||||||
|
labelAttrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
'text-anchor': 'left',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
attrs: {
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1.5,
|
||||||
|
'stroke-dasharray': '4, 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
note: {
|
||||||
|
'note': {
|
||||||
|
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||||
|
padding: {top: 5, left: 5, right: 10, bottom: 5},
|
||||||
|
overlap: {left: 10, right: 10},
|
||||||
|
boxRenderer: SVGShapes.renderNote.bind(null, {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
}, {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
}),
|
||||||
|
labelAttrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'state': {
|
||||||
|
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||||
|
padding: {top: 7, left: 7, right: 7, bottom: 7},
|
||||||
|
overlap: {left: 10, right: 10},
|
||||||
|
boxRenderer: SVGShapes.renderBox.bind(null, {
|
||||||
|
'fill': '#FFFFFF',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
'rx': 10,
|
||||||
|
'ry': 10,
|
||||||
|
}),
|
||||||
|
labelAttrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-size': 8,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
titleAttrs: {
|
||||||
|
'font-family': 'sans-serif',
|
||||||
|
'font-size': 20,
|
||||||
|
'line-height': LINE_HEIGHT,
|
||||||
|
'text-anchor': 'middle',
|
||||||
|
'class': 'title',
|
||||||
|
},
|
||||||
|
|
||||||
|
agentLineAttrs: {
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return class Theme {
|
||||||
|
constructor() {
|
||||||
|
Object.assign(this, SETTINGS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
defineDescribe('Basic Theme', ['./Basic'], (Theme) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
it('contains settings for the theme', () => {
|
||||||
|
const theme = new Theme();
|
||||||
|
expect(theme.outerMargin).toEqual(5);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,10 +1,11 @@
|
||||||
define([
|
define([
|
||||||
|
'core/ArrayUtilities_spec',
|
||||||
|
'svg/SVGUtilities_spec',
|
||||||
|
'svg/SVGTextBlock_spec',
|
||||||
|
'svg/SVGShapes_spec',
|
||||||
'interface/Interface_spec',
|
'interface/Interface_spec',
|
||||||
'sequence/Parser_spec',
|
'sequence/Parser_spec',
|
||||||
'sequence/Generator_spec',
|
'sequence/Generator_spec',
|
||||||
'sequence/Renderer_spec',
|
'sequence/Renderer_spec',
|
||||||
'sequence/ArrayUtilities_spec',
|
'sequence/themes/Basic_spec',
|
||||||
'sequence/SVGUtilities_spec',
|
|
||||||
'sequence/SVGTextBlock_spec',
|
|
||||||
'sequence/SVGShapes_spec',
|
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -65,7 +65,8 @@ define(['./SVGUtilities', './SVGTextBlock'], (svg, SVGTextBlock) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = new SVGTextBlock(labelLayer, labelAttrs, {
|
const label = new SVGTextBlock(labelLayer, {
|
||||||
|
attrs: labelAttrs,
|
||||||
text,
|
text,
|
||||||
x: anchorX,
|
x: anchorX,
|
||||||
y: y + padding.top,
|
y: y + padding.top,
|
|
@ -65,9 +65,9 @@ defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
||||||
boxLayer: o,
|
boxLayer: o,
|
||||||
labelLayer: o,
|
labelLayer: o,
|
||||||
});
|
});
|
||||||
expect(rendered.label.text).toEqual('foo');
|
expect(rendered.label.state.text).toEqual('foo');
|
||||||
expect(rendered.label.x).toEqual(5);
|
expect(rendered.label.state.x).toEqual(5);
|
||||||
expect(rendered.label.y).toEqual(10);
|
expect(rendered.label.state.y).toEqual(10);
|
||||||
expect(rendered.label.firstLine().parentNode).toEqual(o);
|
expect(rendered.label.firstLine().parentNode).toEqual(o);
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,37 +10,37 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function merge(state, newState) {
|
||||||
|
for(let k in state) {
|
||||||
|
if(state.hasOwnProperty(k)) {
|
||||||
|
if(newState[k] !== null && newState[k] !== undefined) {
|
||||||
|
state[k] = newState[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SVGTextBlock {
|
class SVGTextBlock {
|
||||||
constructor(
|
constructor(container, initialState = {}) {
|
||||||
container,
|
|
||||||
attrs,
|
|
||||||
{text = '', x = 0, y = 0} = {}
|
|
||||||
) {
|
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.attrs = attrs;
|
this.state = {
|
||||||
this.text = '';
|
attrs: {},
|
||||||
this.x = x;
|
text: '',
|
||||||
this.y = y;
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
this.nodes = [];
|
this.nodes = [];
|
||||||
this.setText(text);
|
this.set(initialState);
|
||||||
}
|
|
||||||
|
|
||||||
_updateY() {
|
|
||||||
const {size, lineHeight} = fontDetails(this.attrs);
|
|
||||||
this.nodes.forEach(({element}, i) => {
|
|
||||||
element.setAttribute('y', this.y + i * lineHeight + size);
|
|
||||||
});
|
|
||||||
this.height = lineHeight * this.nodes.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_rebuildNodes(count) {
|
_rebuildNodes(count) {
|
||||||
if(count === this.nodes.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(count > this.nodes.length) {
|
if(count > this.nodes.length) {
|
||||||
const attrs = Object.assign({'x': this.x}, this.attrs);
|
const attrs = Object.assign({
|
||||||
|
'x': this.state.x,
|
||||||
|
}, this.state.attrs);
|
||||||
|
|
||||||
while(this.nodes.length < count) {
|
while(this.nodes.length < count) {
|
||||||
const element = svg.make('text', attrs);
|
const element = svg.make('text', attrs);
|
||||||
const text = svg.makeText();
|
const text = svg.makeText();
|
||||||
|
@ -54,29 +54,23 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this.container.removeChild(element);
|
this.container.removeChild(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._updateY();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
firstLine() {
|
_reset() {
|
||||||
if(this.nodes.length > 0) {
|
this._rebuildNodes(0);
|
||||||
return this.nodes[0].element;
|
this.width = 0;
|
||||||
} else {
|
this.height = 0;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setText(newText) {
|
_renderText() {
|
||||||
if(newText === this.text) {
|
if(!this.state.text) {
|
||||||
|
this._reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!newText) {
|
|
||||||
this.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.text = newText;
|
|
||||||
const lines = this.text.split('\n');
|
|
||||||
|
|
||||||
|
const lines = this.state.text.split('\n');
|
||||||
this._rebuildNodes(lines.length);
|
this._rebuildNodes(lines.length);
|
||||||
|
|
||||||
let maxWidth = 0;
|
let maxWidth = 0;
|
||||||
this.nodes.forEach(({text, element}, i) => {
|
this.nodes.forEach(({text, element}, i) => {
|
||||||
if(text.nodeValue !== lines[i]) {
|
if(text.nodeValue !== lines[i]) {
|
||||||
|
@ -87,25 +81,50 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this.width = maxWidth;
|
this.width = maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
reanchor(newX, newY) {
|
_updateX() {
|
||||||
if(newX !== this.x) {
|
this.nodes.forEach(({element}) => {
|
||||||
this.x = newX;
|
element.setAttribute('x', this.state.x);
|
||||||
this.nodes.forEach(({element}) => {
|
});
|
||||||
element.setAttribute('x', this.x);
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(newY !== this.y) {
|
_updateY() {
|
||||||
this.y = newY;
|
const {size, lineHeight} = fontDetails(this.state.attrs);
|
||||||
this._updateY();
|
this.nodes.forEach(({element}, i) => {
|
||||||
|
element.setAttribute('y', this.state.y + i * lineHeight + size);
|
||||||
|
});
|
||||||
|
this.height = lineHeight * this.nodes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstLine() {
|
||||||
|
if(this.nodes.length > 0) {
|
||||||
|
return this.nodes[0].element;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
set(newState) {
|
||||||
this._rebuildNodes(0);
|
const oldState = Object.assign({}, this.state);
|
||||||
this.text = '';
|
merge(this.state, newState);
|
||||||
this.width = 0;
|
|
||||||
this.height = 0;
|
if(this.state.attrs !== oldState.attrs) {
|
||||||
|
this._reset();
|
||||||
|
oldState.text = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldNodes = this.nodes.length;
|
||||||
|
|
||||||
|
if(this.state.text !== oldState.text) {
|
||||||
|
this._renderText();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.state.x !== oldState.x) {
|
||||||
|
this._updateX();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.state.y !== oldState.y || this.nodes.length !== oldNodes) {
|
||||||
|
this._updateY();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ defineDescribe('SVGTextBlock', [
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hold = svg.makeContainer();
|
hold = svg.makeContainer();
|
||||||
document.body.appendChild(hold);
|
document.body.appendChild(hold);
|
||||||
block = new SVGTextBlock(hold, attrs);
|
block = new SVGTextBlock(hold, {attrs});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -23,52 +23,60 @@ defineDescribe('SVGTextBlock', [
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('defaults to blank text at 0, 0', () => {
|
it('defaults to blank text at 0, 0', () => {
|
||||||
expect(block.text).toEqual('');
|
expect(block.state.text).toEqual('');
|
||||||
expect(block.x).toEqual(0);
|
expect(block.state.x).toEqual(0);
|
||||||
expect(block.y).toEqual(0);
|
expect(block.state.y).toEqual(0);
|
||||||
|
expect(hold.children.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not explode if given no setup', () => {
|
||||||
|
block = new SVGTextBlock(hold);
|
||||||
|
expect(block.state.text).toEqual('');
|
||||||
|
expect(block.state.x).toEqual(0);
|
||||||
|
expect(block.state.y).toEqual(0);
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds the given text if specified', () => {
|
it('adds the given text if specified', () => {
|
||||||
block = new SVGTextBlock(hold, attrs, {text: 'abc'});
|
block = new SVGTextBlock(hold, {attrs, text: 'abc'});
|
||||||
expect(block.text).toEqual('abc');
|
expect(block.state.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, {x: 5, y: 7});
|
block = new SVGTextBlock(hold, {attrs, x: 5, y: 7});
|
||||||
expect(block.x).toEqual(5);
|
expect(block.state.x).toEqual(5);
|
||||||
expect(block.y).toEqual(7);
|
expect(block.state.y).toEqual(7);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.setText', () => {
|
describe('.set', () => {
|
||||||
it('sets the text to the given content', () => {
|
it('sets the text to the given content', () => {
|
||||||
block.setText('foo');
|
block.set({text: 'foo'});
|
||||||
expect(block.text).toEqual('foo');
|
expect(block.state.text).toEqual('foo');
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
expect(hold.children[0].innerHTML).toEqual('foo');
|
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders multiline text', () => {
|
it('renders multiline text', () => {
|
||||||
block.setText('foo\nbar');
|
block.set({text: 'foo\nbar'});
|
||||||
expect(hold.children.length).toEqual(2);
|
expect(hold.children.length).toEqual(2);
|
||||||
expect(hold.children[0].innerHTML).toEqual('foo');
|
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||||
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', () => {
|
it('populates width and height with the size of the text', () => {
|
||||||
block.setText('foo\nbar');
|
block.set({text: 'foo\nbar'});
|
||||||
expect(block.width).toBeGreaterThan(0);
|
expect(block.width).toBeGreaterThan(0);
|
||||||
expect(block.height).toEqual(30);
|
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.set({text: 'foo\nbar'});
|
||||||
const line0 = hold.children[0];
|
const line0 = hold.children[0];
|
||||||
const line1 = hold.children[1];
|
const line1 = hold.children[1];
|
||||||
|
|
||||||
block.setText('zig\nzag\nbaz');
|
block.set({text: 'zig\nzag\nbaz'});
|
||||||
|
|
||||||
expect(hold.children.length).toEqual(3);
|
expect(hold.children.length).toEqual(3);
|
||||||
expect(hold.children[0]).toEqual(line0);
|
expect(hold.children[0]).toEqual(line0);
|
||||||
|
@ -79,10 +87,10 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
it('re-uses text nodes when possible, removing extra if needed', () => {
|
it('re-uses text nodes when possible, removing extra if needed', () => {
|
||||||
block.setText('foo\nbar');
|
block.set({text: 'foo\nbar'});
|
||||||
const line0 = hold.children[0];
|
const line0 = hold.children[0];
|
||||||
|
|
||||||
block.setText('zig');
|
block.set({text: 'zig'});
|
||||||
|
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
expect(hold.children[0]).toEqual(line0);
|
expect(hold.children[0]).toEqual(line0);
|
||||||
|
@ -90,7 +98,7 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
it('positions text nodes and applies attributes', () => {
|
it('positions text nodes and applies attributes', () => {
|
||||||
block.setText('foo\nbar');
|
block.set({text: 'foo\nbar'});
|
||||||
expect(hold.children.length).toEqual(2);
|
expect(hold.children.length).toEqual(2);
|
||||||
expect(hold.children[0].getAttribute('x')).toEqual('0');
|
expect(hold.children[0].getAttribute('x')).toEqual('0');
|
||||||
expect(hold.children[0].getAttribute('y')).toEqual('10');
|
expect(hold.children[0].getAttribute('y')).toEqual('10');
|
||||||
|
@ -99,25 +107,21 @@ defineDescribe('SVGTextBlock', [
|
||||||
expect(hold.children[1].getAttribute('y')).toEqual('25');
|
expect(hold.children[1].getAttribute('y')).toEqual('25');
|
||||||
expect(hold.children[1].getAttribute('font-size')).toEqual('10');
|
expect(hold.children[1].getAttribute('font-size')).toEqual('10');
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('.reanchor', () => {
|
|
||||||
it('moves all nodes', () => {
|
it('moves all nodes', () => {
|
||||||
block.setText('foo\nbaz');
|
block.set({text: 'foo\nbaz'});
|
||||||
block.reanchor(5, 7);
|
block.set({x: 5, y: 7});
|
||||||
expect(hold.children[0].getAttribute('x')).toEqual('5');
|
expect(hold.children[0].getAttribute('x')).toEqual('5');
|
||||||
expect(hold.children[0].getAttribute('y')).toEqual('17');
|
expect(hold.children[0].getAttribute('y')).toEqual('17');
|
||||||
expect(hold.children[1].getAttribute('x')).toEqual('5');
|
expect(hold.children[1].getAttribute('x')).toEqual('5');
|
||||||
expect(hold.children[1].getAttribute('y')).toEqual('32');
|
expect(hold.children[1].getAttribute('y')).toEqual('32');
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('.clear', () => {
|
it('clears if the text is empty', () => {
|
||||||
it('resets the text empty', () => {
|
block.set({text: 'foo\nbaz'});
|
||||||
block.setText('foo\nbaz');
|
block.set({text: ''});
|
||||||
block.setText('');
|
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
expect(block.text).toEqual('');
|
expect(block.state.text).toEqual('');
|
||||||
expect(block.width).toEqual(0);
|
expect(block.width).toEqual(0);
|
||||||
expect(block.height).toEqual(0);
|
expect(block.height).toEqual(0);
|
||||||
});
|
});
|
Loading…
Reference in New Issue