Support rendering conditional boxes [#1]

This commit is contained in:
David Evans 2017-10-25 00:04:53 +01:00
parent 5ecce9a6ab
commit 1929e3ffb1
4 changed files with 347 additions and 108 deletions

View File

@ -36,6 +36,7 @@ define(() => {
return class Generator { return class Generator {
constructor() { constructor() {
this.agentStates = new Map(); this.agentStates = new Map();
this.agents = [];
this.blockCount = 0; this.blockCount = 0;
this.nesting = []; this.nesting = [];
this.currentSection = null; this.currentSection = null;
@ -52,18 +53,13 @@ define(() => {
this.handleStage = this.handleStage.bind(this); this.handleStage = this.handleStage.bind(this);
} }
addStage(stage) { addBounds(target, agentL, agentR, involvedAgents = null) {
this.currentSection.stages.push(stage);
mergeSets(this.currentNest.agents, stage.agents);
}
addColumnBounds(target, agentL, agentR, involvedAgents = []) {
removeElement(target, agentL); removeElement(target, agentL);
removeElement(target, agentR); removeElement(target, agentR);
let indexL = 0; let indexL = 0;
let indexR = target.length; let indexR = target.length;
if(involvedAgents.length > 0) { if(involvedAgents) {
const found = (involvedAgents const found = (involvedAgents
.map((agent) => target.indexOf(agent)) .map((agent) => target.indexOf(agent))
.filter((p) => (p !== -1)) .filter((p) => (p !== -1))
@ -103,18 +99,21 @@ define(() => {
const existing = lastElement(this.currentSection.stages) || {}; const existing = lastElement(this.currentSection.stages) || {};
if(existing.type === type && existing.mode === mode) { if(existing.type === type && existing.mode === mode) {
mergeSets(existing.agents, filteredAgents); mergeSets(existing.agents, filteredAgents);
mergeSets(this.currentNest.agents, filteredAgents);
} else { } else {
this.addStage({ this.currentSection.stages.push({
type, type,
agents: filteredAgents, agents: filteredAgents,
mode, mode,
}); });
} }
mergeSets(this.currentNest.agents, filteredAgents);
mergeSets(this.agents, filteredAgents);
} }
beginNested(mode, label, name) { beginNested(mode, label, name) {
const agents = []; const nameL = name + '[';
const nameR = name + ']';
const agents = [nameL, nameR];
const stages = []; const stages = [];
this.currentSection = { this.currentSection = {
mode, mode,
@ -122,20 +121,24 @@ define(() => {
stages, stages,
}; };
this.currentNest = { this.currentNest = {
type: 'block',
agents, agents,
stage: {
type: 'block',
sections: [this.currentSection], sections: [this.currentSection],
leftColumn: name + '[', left: nameL,
rightColumn: name + ']', right: nameR,
},
}; };
this.agentStates.set(name + '[', LOCKED_AGENT); this.agentStates.set(nameL, LOCKED_AGENT);
this.agentStates.set(name + ']', LOCKED_AGENT); this.agentStates.set(nameR, LOCKED_AGENT);
this.nesting.push(this.currentNest); this.nesting.push(this.currentNest);
return {agents, stages}; return {agents, stages};
} }
handleAgentDefine() { handleAgentDefine({agents}) {
mergeSets(this.currentNest.agents, agents);
mergeSets(this.agents, agents);
} }
handleAgentBegin({agents, mode}) { handleAgentBegin({agents, mode}) {
@ -153,7 +156,7 @@ define(() => {
} }
handleBlockSplit({mode, label}) { handleBlockSplit({mode, label}) {
if(this.currentNest.sections[0].mode !== 'if') { if(this.currentNest.stage.sections[0].mode !== 'if') {
throw new Error('Invalid block nesting'); throw new Error('Invalid block nesting');
} }
this.currentSection = { this.currentSection = {
@ -161,35 +164,34 @@ define(() => {
label, label,
stages: [], stages: [],
}; };
this.currentNest.sections.push(this.currentSection); this.currentNest.stage.sections.push(this.currentSection);
} }
handleBlockEnd() { handleBlockEnd() {
if(this.nesting.length <= 1) { if(this.nesting.length <= 1) {
throw new Error('Invalid block nesting'); throw new Error('Invalid block nesting');
} }
const subNest = this.nesting.pop(); const {stage, agents} = this.nesting.pop();
this.currentNest = lastElement(this.nesting); this.currentNest = lastElement(this.nesting);
this.currentSection = lastElement(this.currentNest.sections); this.currentSection = lastElement(this.currentNest.stage.sections);
if(subNest.agents.length > 0) { if(stage.sections.some((section) => section.stages.length > 0)) {
this.addStage(subNest); mergeSets(this.currentNest.agents, agents);
this.addColumnBounds( mergeSets(this.agents, agents);
this.currentNest.agents, this.addBounds(
subNest.leftColumn, this.agents,
subNest.rightColumn, stage.left,
subNest.agents stage.right,
); agents
this.addColumnBounds(
subNest.agents,
subNest.leftColumn,
subNest.rightColumn
); );
this.currentSection.stages.push(stage);
} }
} }
handleUnknownStage(stage) { handleUnknownStage(stage) {
this.setAgentVis(stage.agents, true, 'box'); this.setAgentVis(stage.agents, true, 'box');
this.addStage(stage); this.currentSection.stages.push(stage);
mergeSets(this.currentNest.agents, stage.agents);
mergeSets(this.agents, stage.agents);
} }
handleStage(stage) { handleStage(stage) {
@ -203,6 +205,7 @@ define(() => {
generate({stages, meta = {}}) { generate({stages, meta = {}}) {
this.agentStates.clear(); this.agentStates.clear();
this.agents.length = 0;
this.blockCount = 0; this.blockCount = 0;
this.nesting.length = 0; this.nesting.length = 0;
const globals = this.beginNested('global', '', ''); const globals = this.beginNested('global', '', '');
@ -213,19 +216,19 @@ define(() => {
throw new Error('Invalid block nesting'); throw new Error('Invalid block nesting');
} }
this.setAgentVis(globals.agents, false, meta.terminators || 'none'); this.setAgentVis(this.agents, false, meta.terminators || 'none');
this.addColumnBounds( this.addBounds(
globals.agents, this.agents,
this.currentNest.leftColumn, this.currentNest.stage.left,
this.currentNest.rightColumn this.currentNest.stage.right
); );
return { return {
meta: { meta: {
title: meta.title, title: meta.title,
}, },
agents: globals.agents, agents: this.agents,
stages: globals.stages, stages: globals.stages,
}; };
} }

View File

@ -51,6 +51,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
expect(sequence.agents).toEqual(['[', 'B', ']']); expect(sequence.agents).toEqual(['[', 'B', ']']);
}); });
it('accounts for define calls when ordering agents', () => {
const sequence = generator.generate({stages: [
{type: AGENT_DEFINE, agents: ['B']},
{type: '->', agents: ['A', 'B']},
]});
expect(sequence.agents).toEqual(['[', 'B', 'A', ']']);
});
it('creates implicit begin stages for agents when used', () => { it('creates implicit begin stages for agents when used', () => {
const sequence = generator.generate({stages: [ const sequence = generator.generate({stages: [
{type: '->', agents: ['A', 'B']}, {type: '->', agents: ['A', 'B']},
@ -216,7 +224,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]); ]);
}); });
it('records virtual block column names in blocks', () => { it('records virtual block agent names in blocks', () => {
const sequence = generator.generate({stages: [ const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'}, {type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: ['A', 'B']}, {type: '->', agents: ['A', 'B']},
@ -225,23 +233,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const block0 = sequence.stages[0]; const block0 = sequence.stages[0];
expect(block0.type).toEqual('block'); expect(block0.type).toEqual('block');
expect(block0.leftColumn).toEqual('__BLOCK0['); expect(block0.left).toEqual('__BLOCK0[');
expect(block0.rightColumn).toEqual('__BLOCK0]'); expect(block0.right).toEqual('__BLOCK0]');
});
it('records all involved agents in blocks', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: '->', agents: ['A', 'B']},
{type: BLOCK_SPLIT, mode: 'else', label: 'xyz'},
{type: '->', agents: ['A', 'C']},
{type: BLOCK_END},
]});
const block0 = sequence.stages[0];
expect(block0.agents).toEqual(
['__BLOCK0[', 'A', 'B', 'C', '__BLOCK0]']
);
}); });
it('records all sections within blocks', () => { it('records all sections within blocks', () => {
@ -277,9 +270,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
{type: BLOCK_END}, {type: BLOCK_END},
]}); ]});
const block0 = sequence.stages[0]; expect(sequence.agents).toEqual([
expect(block0.type).toEqual('block'); '[',
expect(block0.agents).toEqual([
'__BLOCK0[', '__BLOCK0[',
'__BLOCK1[', '__BLOCK1[',
'A', 'A',
@ -287,20 +279,48 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
'C', 'C',
'__BLOCK1]', '__BLOCK1]',
'__BLOCK0]', '__BLOCK0]',
']',
]); ]);
expect(block0.leftColumn).toEqual('__BLOCK0['); const block0 = sequence.stages[0];
expect(block0.rightColumn).toEqual('__BLOCK0]'); expect(block0.type).toEqual('block');
expect(block0.left).toEqual('__BLOCK0[');
expect(block0.right).toEqual('__BLOCK0]');
const block1 = block0.sections[1].stages[0]; const block1 = block0.sections[1].stages[0];
expect(block1.type).toEqual('block'); expect(block1.type).toEqual('block');
expect(block1.agents).toEqual([ expect(block1.left).toEqual('__BLOCK1[');
expect(block1.right).toEqual('__BLOCK1]');
});
it('preserves block boundaries when agents exist outside', () => {
const sequence = generator.generate({stages: [
{type: '->', agents: ['A', 'B']},
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: BLOCK_BEGIN, mode: 'if', label: 'def'},
{type: '->', agents: ['A', 'B']},
{type: BLOCK_END},
{type: BLOCK_END},
]});
expect(sequence.agents).toEqual([
'[',
'__BLOCK0[',
'__BLOCK1[', '__BLOCK1[',
'C',
'A', 'A',
'B',
'__BLOCK1]', '__BLOCK1]',
'__BLOCK0]',
']',
]); ]);
expect(block1.leftColumn).toEqual('__BLOCK1['); const block0 = sequence.stages[2];
expect(block1.rightColumn).toEqual('__BLOCK1]'); expect(block0.type).toEqual('block');
expect(block0.left).toEqual('__BLOCK0[');
expect(block0.right).toEqual('__BLOCK0]');
const block1 = block0.sections[0].stages[0];
expect(block1.type).toEqual('block');
expect(block1.left).toEqual('__BLOCK1[');
expect(block1.right).toEqual('__BLOCK1]');
}); });
it('allows empty block parts after split', () => { it('allows empty block parts after split', () => {
@ -351,6 +371,16 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
expect(sequence.stages).toEqual([]); expect(sequence.stages).toEqual([]);
}); });
it('removes blocks which only contain define statements', () => {
const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
{type: AGENT_DEFINE, agents: ['A']},
{type: BLOCK_END},
]});
expect(sequence.stages).toEqual([]);
});
it('does not create virtual agents for empty blocks', () => { it('does not create virtual agents for empty blocks', () => {
const sequence = generator.generate({stages: [ const sequence = generator.generate({stages: [
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'}, {type: BLOCK_BEGIN, mode: 'if', label: 'abc'},

View File

@ -48,8 +48,32 @@ define(() => {
const CONNECT_POINT = 4; const CONNECT_POINT = 4;
const CONNECT_LABEL_PADDING = 6; const CONNECT_LABEL_PADDING = 6;
const CONNECT_LABEL_MASK_PADDING = 3; const CONNECT_LABEL_MASK_PADDING = 3;
const CONNECT_LABEL_MARGIN_TOP = 2; const CONNECT_LABEL_MARGIN = {
const CONNECT_LABEL_MARGIN_BOTTOM = 1; top: 2,
bottom: 1,
};
const BLOCK_MARGIN = {
top: 5,
bottom: 5,
};
const BLOCK_SECTION_PADDING = {
top: 1,
bottom: 5,
};
const BLOCK_MODE_PADDING = {
top: 1,
left: 3,
right: 3,
bottom: 0,
};
const BLOCK_LABEL_PADDING = {
left: 5,
right: 5,
};
const BLOCK_LABEL_MASK_PADDING = {
left: 3,
right: 3,
};
const ATTRS = { const ATTRS = {
TITLE: { TITLE: {
@ -85,6 +109,40 @@ define(() => {
'height': 5, 'height': 5,
}, },
BLOCK_BOX: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
},
BLOCK_SEPARATOR: {
'stroke': '#000000',
'stroke-width': 1.5,
'stroke-dasharray': '4, 2',
},
BLOCK_MODE: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'rx': 2,
'ry': 2,
},
BLOCK_MODE_LABEL: {
'font-family': 'sans-serif',
'font-weight': 'bold',
'font-size': 9,
'text-anchor': 'left',
},
BLOCK_LABEL: {
'font-family': 'sans-serif',
'font-size': 8,
'text-anchor': 'left',
},
BLOCK_LABEL_MASK: {
'fill': '#FFFFFF',
},
CONNECT_LINE_SOLID: { CONNECT_LINE_SOLID: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
@ -94,7 +152,7 @@ define(() => {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'stroke-dasharray': '2, 2', 'stroke-dasharray': '4, 2',
}, },
CONNECT_LABEL: { CONNECT_LABEL: {
'font-family': 'sans-serif', 'font-family': 'sans-serif',
@ -126,13 +184,27 @@ define(() => {
return o; return o;
} }
function traverse(stages, fn) { function traverse(stages, callbacks) {
stages.forEach((stage) => { stages.forEach((stage) => {
fn(stage);
if(stage.type === 'block') { if(stage.type === 'block') {
const scope = {};
if(callbacks.blockBeginFn) {
callbacks.blockBeginFn(scope, stage);
}
stage.sections.forEach((section) => { stage.sections.forEach((section) => {
traverse(section.stages, fn); if(callbacks.sectionBeginFn) {
callbacks.sectionBeginFn(scope, stage, section);
}
traverse(section.stages, callbacks);
if(callbacks.sectionEndFn) {
callbacks.sectionEndFn(scope, stage, section);
}
}); });
if(callbacks.blockEndFn) {
callbacks.blockEndFn(scope, stage);
}
} else if(callbacks.stageFn) {
callbacks.stageFn(stage);
} }
}); });
} }
@ -155,9 +227,13 @@ define(() => {
this.diagram = makeSVGNode('g'); this.diagram = makeSVGNode('g');
this.agentLines = makeSVGNode('g'); this.agentLines = makeSVGNode('g');
this.blocks = makeSVGNode('g');
this.sections = makeSVGNode('g');
this.agentDecor = makeSVGNode('g'); this.agentDecor = makeSVGNode('g');
this.actions = makeSVGNode('g'); this.actions = makeSVGNode('g');
this.diagram.appendChild(this.agentLines); this.diagram.appendChild(this.agentLines);
this.diagram.appendChild(this.blocks);
this.diagram.appendChild(this.sections);
this.diagram.appendChild(this.agentDecor); this.diagram.appendChild(this.agentDecor);
this.diagram.appendChild(this.actions); this.diagram.appendChild(this.actions);
this.base.appendChild(this.diagram); this.base.appendChild(this.diagram);
@ -173,7 +249,6 @@ define(() => {
'agent begin': this.separationAgent.bind(this), 'agent begin': this.separationAgent.bind(this),
'agent end': this.separationAgent.bind(this), 'agent end': this.separationAgent.bind(this),
'connection': this.separationConnection.bind(this), 'connection': this.separationConnection.bind(this),
'block': this.separationBlock.bind(this),
'note over': this.separationNoteOver.bind(this), 'note over': this.separationNoteOver.bind(this),
'note left': this.separationNoteLeft.bind(this), 'note left': this.separationNoteLeft.bind(this),
'note right': this.separationNoteRight.bind(this), 'note right': this.separationNoteRight.bind(this),
@ -191,13 +266,27 @@ define(() => {
'agent begin': this.renderAgentBegin.bind(this), 'agent begin': this.renderAgentBegin.bind(this),
'agent end': this.renderAgentEnd.bind(this), 'agent end': this.renderAgentEnd.bind(this),
'connection': this.renderConnection.bind(this), 'connection': this.renderConnection.bind(this),
'block': this.renderBlock.bind(this),
'note over': this.renderNoteOver.bind(this), 'note over': this.renderNoteOver.bind(this),
'note left': this.renderNoteLeft.bind(this), 'note left': this.renderNoteLeft.bind(this),
'note right': this.renderNoteRight.bind(this), 'note right': this.renderNoteRight.bind(this),
'note between': this.renderNoteBetween.bind(this), 'note between': this.renderNoteBetween.bind(this),
}; };
this.separationTraversalFns = {
stageFn: this.checkSeparation.bind(this),
blockBeginFn: this.separationBlockBegin.bind(this),
sectionBeginFn: this.separationSectionBegin.bind(this),
blockEndFn: this.separationBlockEnd.bind(this),
};
this.renderTraversalFns = {
stageFn: this.addAction.bind(this),
blockBeginFn: this.renderBlockBegin.bind(this),
sectionBeginFn: this.renderSectionBegin.bind(this),
sectionEndFn: this.renderSectionEnd.bind(this),
blockEndFn: this.renderBlockEnd.bind(this),
};
this.width = 0; this.width = 0;
this.height = 0; this.height = 0;
} }
@ -280,17 +369,13 @@ define(() => {
agents[0], agents[0],
agents[1], agents[1],
this.testTextWidth(this.testConnectWidth, label) + this.testTextWidth(this.testConnect, label) +
CONNECT_POINT * 2 + CONNECT_POINT * 2 +
CONNECT_LABEL_PADDING * 2 + CONNECT_LABEL_PADDING * 2 +
ATTRS.AGENT_LINE['stroke-width'] ATTRS.AGENT_LINE['stroke-width']
); );
} }
separationBlock(/*stage*/) {
// TODO
}
separationNoteOver(/*stage*/) { separationNoteOver(/*stage*/) {
// TODO // TODO
} }
@ -307,6 +392,25 @@ define(() => {
// TODO // TODO
} }
separationBlockBegin(scope, {left, right}) {
mergeSets(this.visibleAgents, [left, right]);
this.addSeparations(this.visibleAgents, new Map());
}
separationSectionBegin(scope, {left, right}, {mode, label}) {
const width = (
this.testTextWidth(this.testBlockMode, mode) +
BLOCK_MODE_PADDING.left + BLOCK_MODE_PADDING.right +
this.testTextWidth(this.testBlockLabel, label) +
BLOCK_LABEL_PADDING.left + BLOCK_LABEL_PADDING.right
);
this.addSeparation(left, right, width);
}
separationBlockEnd(scope, {left, right}) {
removeAll(this.visibleAgents, [left, right]);
}
checkSeparation(stage) { checkSeparation(stage) {
this.separationAction[stage.type](stage); this.separationAction[stage.type](stage);
} }
@ -430,16 +534,16 @@ define(() => {
this.actions.appendChild(labelNode); this.actions.appendChild(labelNode);
y += Math.max( y += Math.max(
dy, dy,
CONNECT_LABEL_MARGIN_TOP + CONNECT_LABEL_MARGIN.top +
sz * LINE_HEIGHT + sz * LINE_HEIGHT +
CONNECT_LABEL_MARGIN_BOTTOM CONNECT_LABEL_MARGIN.bottom
); );
const w = labelNode.getComputedTextLength(); const w = labelNode.getComputedTextLength();
const x = (from.x + to.x) / 2; const x = (from.x + to.x) / 2;
const yBase = ( const yBase = (
y - y -
sz * (LINE_HEIGHT - 1) - sz * (LINE_HEIGHT - 1) -
CONNECT_LABEL_MARGIN_BOTTOM CONNECT_LABEL_MARGIN.bottom
); );
labelNode.setAttribute('x', x); labelNode.setAttribute('x', x);
labelNode.setAttribute('y', yBase); labelNode.setAttribute('y', yBase);
@ -483,10 +587,6 @@ define(() => {
this.currentY = y + dy + ACTION_MARGIN; this.currentY = y + dy + ACTION_MARGIN;
} }
renderBlock(/*stage*/) {
// TODO
}
renderNoteOver(/*stage*/) { renderNoteOver(/*stage*/) {
// TODO // TODO
} }
@ -503,6 +603,106 @@ define(() => {
// TODO // TODO
} }
renderBlockBegin(scope) {
this.currentY += BLOCK_MARGIN.top;
scope.y = this.currentY;
scope.first = true;
}
renderSectionBegin(scope, {left, right}, {mode, label}) {
const agentInfoL = this.agentInfos.get(left);
const agentInfoR = this.agentInfos.get(right);
if(scope.first) {
scope.first = false;
} else {
this.currentY += BLOCK_SECTION_PADDING.bottom;
this.sections.appendChild(makeSVGNode('path', Object.assign({
'd': (
'M' + agentInfoL.x + ' ' + this.currentY +
' L' + agentInfoR.x + ' ' + this.currentY
),
}, ATTRS.BLOCK_SEPARATOR)));
}
let x = agentInfoL.x;
if(mode) {
const sz = ATTRS.BLOCK_MODE_LABEL['font-size'];
const modeBox = makeSVGNode('rect', Object.assign({
'x': x,
'y': this.currentY,
'height': (
sz * LINE_HEIGHT +
BLOCK_MODE_PADDING.top +
BLOCK_MODE_PADDING.bottom
),
}, ATTRS.BLOCK_MODE));
const modeLabel = makeSVGNode('text', Object.assign({
'x': x + BLOCK_MODE_PADDING.left,
'y': (
this.currentY + sz +
BLOCK_MODE_PADDING.top
),
}, ATTRS.BLOCK_MODE_LABEL));
modeLabel.appendChild(makeText(mode));
this.blocks.appendChild(modeBox);
this.actions.appendChild(modeLabel);
const w = (
modeLabel.getComputedTextLength() +
BLOCK_MODE_PADDING.left +
BLOCK_MODE_PADDING.right
);
modeBox.setAttribute('width', w);
x += w;
this.currentY += sz * LINE_HEIGHT;
}
if(label) {
x += BLOCK_LABEL_PADDING.left;
const sz = ATTRS.BLOCK_LABEL['font-size'];
const mask = makeSVGNode('rect', Object.assign({
'x': x - BLOCK_LABEL_MASK_PADDING.left,
'y': this.currentY - sz * LINE_HEIGHT,
'height': sz * LINE_HEIGHT,
}, ATTRS.BLOCK_LABEL_MASK));
const labelLabel = makeSVGNode('text', Object.assign({
'x': x,
'y': this.currentY - sz * (LINE_HEIGHT - 1),
}, ATTRS.BLOCK_LABEL));
labelLabel.appendChild(makeText(label));
this.actions.appendChild(mask);
this.actions.appendChild(labelLabel);
const w = (
labelLabel.getComputedTextLength() +
BLOCK_LABEL_MASK_PADDING.left +
BLOCK_LABEL_MASK_PADDING.right
);
mask.setAttribute('width', w);
}
this.currentY += BLOCK_SECTION_PADDING.top;
}
renderSectionEnd(/*scope, block, section*/) {
}
renderBlockEnd(scope, {left, right}) {
this.currentY += BLOCK_SECTION_PADDING.bottom;
const agentInfoL = this.agentInfos.get(left);
const agentInfoR = this.agentInfos.get(right);
this.blocks.appendChild(makeSVGNode('rect', Object.assign({
'x': agentInfoL.x,
'y': scope.y,
'width': agentInfoR.x - agentInfoL.x,
'height': this.currentY - scope.y,
}, ATTRS.BLOCK_BOX)));
this.currentY += BLOCK_MARGIN.bottom;
}
addAction(stage) { addAction(stage) {
this.renderAction[stage.type](stage); this.renderAction[stage.type](stage);
} }
@ -525,14 +725,14 @@ define(() => {
} }
buildAgentInfos(agents, stages) { buildAgentInfos(agents, stages) {
const testNameWidth = this.makeTextTester(ATTRS.AGENT_BOX_LABEL); const testName = this.makeTextTester(ATTRS.AGENT_BOX_LABEL);
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: ( labelWidth: (
this.testTextWidth(testNameWidth, agent) + this.testTextWidth(testName, agent) +
BOX_PADDING * 2 BOX_PADDING * 2
), ),
index, index,
@ -544,12 +744,16 @@ define(() => {
this.agentInfos.get('[').labelWidth = 0; this.agentInfos.get('[').labelWidth = 0;
this.agentInfos.get(']').labelWidth = 0; this.agentInfos.get(']').labelWidth = 0;
this.removeTextTester(testNameWidth); this.removeTextTester(testName);
this.testConnectWidth = this.makeTextTester(ATTRS.CONNECT_LABEL); this.testConnect = this.makeTextTester(ATTRS.CONNECT_LABEL);
this.testBlockMode = this.makeTextTester(ATTRS.BLOCK_MODE_LABEL);
this.testBlockLabel = this.makeTextTester(ATTRS.BLOCK_LABEL);
this.visibleAgents = ['[', ']']; this.visibleAgents = ['[', ']'];
traverse(stages, this.checkSeparation.bind(this)); traverse(stages, this.separationTraversalFns);
this.removeTextTester(this.testConnectWidth); this.removeTextTester(this.testConnect);
this.removeTextTester(this.testBlockMode);
this.removeTextTester(this.testBlockLabel);
agents.forEach((agent) => { agents.forEach((agent) => {
const agentInfo = this.agentInfos.get(agent); const agentInfo = this.agentInfos.get(agent);
@ -597,6 +801,8 @@ define(() => {
render({meta, agents, stages}) { render({meta, agents, stages}) {
empty(this.agentLines); empty(this.agentLines);
empty(this.blocks);
empty(this.sections);
empty(this.agentDecor); empty(this.agentDecor);
empty(this.actions); empty(this.actions);
@ -607,12 +813,12 @@ define(() => {
this.buildAgentInfos(agents, stages); this.buildAgentInfos(agents, stages);
this.currentY = 0; this.currentY = 0;
traverse(stages, this.addAction.bind(this)); traverse(stages, this.renderTraversalFns);
this.updateBounds(Math.max(this.currentY - ACTION_MARGIN, 0)); this.updateBounds(Math.max(this.currentY - ACTION_MARGIN, 0));
} }
getColumnX(name) { getAgentX(name) {
return this.agentInfos.get(name).x; return this.agentInfos.get(name).x;
} }

View File

@ -42,7 +42,7 @@ defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
expect(title.innerHTML).toEqual('Title'); expect(title.innerHTML).toEqual('Title');
}); });
it('positions column lines', () => { it('positions agent lines', () => {
/* /*
A -> B A -> B
*/ */
@ -61,10 +61,10 @@ defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
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('d').split(' ')[1]);
expect(drawnX).toEqual(renderer.getColumnX('A')); expect(drawnX).toEqual(renderer.getAgentX('A'));
}); });
it('arranges columns left-to-right', () => { it('arranges agents left-to-right', () => {
/* /*
[ -> A [ -> A
A -> B A -> B
@ -85,11 +85,11 @@ defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
], ],
}); });
const xL = renderer.getColumnX('['); const xL = renderer.getAgentX('[');
const xA = renderer.getColumnX('A'); const xA = renderer.getAgentX('A');
const xB = renderer.getColumnX('B'); const xB = renderer.getAgentX('B');
const xC = renderer.getColumnX('C'); const xC = renderer.getAgentX('C');
const xR = renderer.getColumnX(']'); const xR = renderer.getAgentX(']');
expect(xA).toBeGreaterThan(xL); expect(xA).toBeGreaterThan(xL);
expect(xB).toBeGreaterThan(xA); expect(xB).toBeGreaterThan(xA);
@ -97,7 +97,7 @@ defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
expect(xR).toBeGreaterThan(xC); expect(xR).toBeGreaterThan(xC);
}); });
it('allows column reordering for mutually-exclusive columns', () => { it('allows agent reordering for mutually-exclusive agents', () => {
/* /*
A -> B: short A -> B: short
end B end B
@ -123,10 +123,10 @@ defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
], ],
}); });
const xA = renderer.getColumnX('A'); const xA = renderer.getAgentX('A');
const xB = renderer.getColumnX('B'); const xB = renderer.getAgentX('B');
const xC = renderer.getColumnX('C'); const xC = renderer.getAgentX('C');
const xD = renderer.getColumnX('D'); const xD = renderer.getAgentX('D');
expect(xB).toBeGreaterThan(xA); expect(xB).toBeGreaterThan(xA);
expect(xC).toBeGreaterThan(xA); expect(xC).toBeGreaterThan(xA);