Support rendering conditional boxes [#1]
This commit is contained in:
parent
5ecce9a6ab
commit
1929e3ffb1
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'},
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue