Render if/else/repeat/ref blocks in sketch style, and improve standard rendering [#18]

This commit is contained in:
David Evans 2018-01-10 22:03:46 +00:00
parent 531b284afa
commit bb61d1faf3
14 changed files with 1150 additions and 965 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -172,6 +172,7 @@ define([
textSizer: this.sizer, textSizer: this.sizer,
addSpacing, addSpacing,
addSeparation: this.addSeparation, addSeparation: this.addSeparation,
state: this.state,
components: this.components, components: this.components,
}; };
const component = this.components.get(stage.type); const component = this.components.get(stage.type);
@ -240,13 +241,13 @@ define([
return; return;
} }
this.theme.drawAgentLine(this.agentLines, { this.agentLines.appendChild(this.theme.renderAgentLine({
x: agentInfo.x, x: agentInfo.x,
y0: agentInfo.latestYStart, y0: agentInfo.latestYStart,
y1: toY, y1: toY,
width: agentInfo.currentRad * 2, width: agentInfo.currentRad * 2,
className: 'agent-' + agentInfo.index + '-line', className: 'agent-' + agentInfo.index + '-line',
}); }));
} }
addHighlightObject(line, o) { addHighlightObject(line, o) {
@ -416,6 +417,13 @@ define([
this.height = (y1 - y0); this.height = (y1 - y0);
} }
_resetState() {
this.components.forEach((component) => {
component.resetState(this.state);
});
this.currentY = 0;
}
_reset() { _reset() {
this.knownDefs.clear(); this.knownDefs.clear();
this.highlights.clear(); this.highlights.clear();
@ -428,9 +436,7 @@ define([
svg.empty(this.actionLabels); svg.empty(this.actionLabels);
this.mask.appendChild(this.maskReveal); this.mask.appendChild(this.maskReveal);
this.defs.appendChild(this.mask); this.defs.appendChild(this.mask);
this.components.forEach((component) => { this._resetState();
component.resetState(this.state);
});
} }
setHighlight(line = null) { setHighlight(line = null) {
@ -475,7 +481,7 @@ define([
this.maxX = 0; this.maxX = 0;
this.buildAgentInfos(sequence.agents, sequence.stages); this.buildAgentInfos(sequence.agents, sequence.stages);
this.currentY = 0; this._resetState();
sequence.stages.forEach(this.renderStage); sequence.stages.forEach(this.renderStage);
const bottomY = this.checkAgentRange(['[', ']'], this.currentY); const bottomY = this.checkAgentRange(['[', ']'], this.currentY);

View File

@ -5,6 +5,7 @@ define([
'./Generator', './Generator',
'./Renderer', './Renderer',
'./Exporter', './Exporter',
'./themes/BaseTheme',
'./themes/Basic', './themes/Basic',
'./themes/Chunky', './themes/Chunky',
'./themes/Sketch', './themes/Sketch',
@ -14,6 +15,7 @@ define([
Generator, Generator,
Renderer, Renderer,
Exporter, Exporter,
BaseTheme,
BasicTheme, BasicTheme,
ChunkyTheme, ChunkyTheme,
SketchTheme SketchTheme
@ -234,6 +236,7 @@ define([
Generator, Generator,
Renderer, Renderer,
Exporter, Exporter,
BaseTheme,
themes, themes,
addTheme, addTheme,
registerCodeMirrorMode, registerCodeMirrorMode,

View File

@ -16,6 +16,7 @@ define(() => {
textSizer, textSizer,
addSpacing, addSpacing,
addSeparation, addSeparation,
state,
components, components,
}*/) { }*/) {
} }
@ -27,6 +28,7 @@ define(() => {
textSizer, textSizer,
addSpacing, addSpacing,
addSeparation, addSeparation,
state,
components, components,
}*/) { }*/) {
} }

View File

@ -13,7 +13,8 @@ define([
class BlockSplit extends BaseComponent { class BlockSplit extends BaseComponent {
separation({left, right, mode, label}, env) { separation({left, right, mode, label}, env) {
const config = env.theme.block.section; const blockInfo = env.state.blocks.get(left);
const config = env.theme.getBlock(blockInfo.mode).section;
const width = ( const width = (
env.textSizer.measure(config.mode.labelAttrs, mode).width + env.textSizer.measure(config.mode.labelAttrs, mode).width +
config.mode.padding.left + config.mode.padding.left +
@ -32,21 +33,15 @@ define([
} }
render({left, right, mode, label}, env, first = false) { render({left, right, mode, label}, env, first = false) {
const config = env.theme.block; const blockInfo = env.state.blocks.get(left);
const config = env.theme.getBlock(blockInfo.mode);
const agentInfoL = env.agentInfos.get(left); const agentInfoL = env.agentInfos.get(left);
const agentInfoR = env.agentInfos.get(right); const agentInfoR = env.agentInfos.get(right);
const {hold} = env.state.blocks.get(left);
let y = env.primaryY; let y = env.primaryY;
if(!first) { if(!first) {
y += config.section.padding.bottom; y += config.section.padding.bottom;
hold.appendChild(svg.make('line', Object.assign({
'x1': agentInfoL.x,
'y1': y,
'x2': agentInfoR.x,
'y2': y,
}, config.separator.attrs)));
} }
const clickable = env.makeRegion(); const clickable = env.makeRegion();
@ -56,8 +51,9 @@ define([
y, y,
padding: config.section.mode.padding, padding: config.section.mode.padding,
boxAttrs: config.section.mode.boxAttrs, boxAttrs: config.section.mode.boxAttrs,
boxRenderer: config.section.mode.boxRenderer,
labelAttrs: config.section.mode.labelAttrs, labelAttrs: config.section.mode.labelAttrs,
boxLayer: hold, boxLayer: blockInfo.hold,
labelLayer: clickable, labelLayer: clickable,
SVGTextBlockClass: env.SVGTextBlockClass, SVGTextBlockClass: env.SVGTextBlockClass,
}); });
@ -83,6 +79,15 @@ define([
'fill': 'transparent', 'fill': 'transparent',
}), clickable.firstChild); }), clickable.firstChild);
if(!first) {
blockInfo.hold.appendChild(config.sepRenderer({
'x1': agentInfoL.x,
'y1': y,
'x2': agentInfoR.x,
'y2': y,
}));
}
return y + labelHeight + config.section.padding.top; return y + labelHeight + config.section.padding.top;
} }
} }
@ -96,15 +101,31 @@ define([
state.blocks.clear(); state.blocks.clear();
} }
storeBlockInfo(stage, env) {
env.state.blocks.set(stage.left, {
mode: stage.mode,
hold: null,
startY: null,
});
}
separationPre(stage, env) {
this.storeBlockInfo(stage, env);
}
separation(stage, env) { separation(stage, env) {
array.mergeSets(env.visibleAgents, [stage.left, stage.right]); array.mergeSets(env.visibleAgents, [stage.left, stage.right]);
super.separation(stage, env); super.separation(stage, env);
} }
renderPre({left, right}, env) { renderPre(stage, env) {
this.storeBlockInfo(stage, env);
const config = env.theme.getBlock(stage.mode);
return { return {
agentNames: [left, right], agentNames: [stage.left, stage.right],
topShift: env.theme.block.margin.top, topShift: config.margin.top,
}; };
} }
@ -112,11 +133,9 @@ define([
const hold = svg.make('g'); const hold = svg.make('g');
env.blockLayer.appendChild(hold); env.blockLayer.appendChild(hold);
env.state.blocks.set(stage.left, { const blockInfo = env.state.blocks.get(stage.left);
hold, blockInfo.hold = hold;
mode: stage.mode, blockInfo.startY = env.primaryY;
startY: env.primaryY,
});
return super.render(stage, env, true); return super.render(stage, env, true);
} }
@ -128,25 +147,27 @@ define([
} }
renderPre({left, right}, env) { renderPre({left, right}, env) {
const blockInfo = env.state.blocks.get(left);
const config = env.theme.getBlock(blockInfo.mode);
return { return {
agentNames: [left, right], agentNames: [left, right],
topShift: env.theme.block.section.padding.bottom, topShift: config.section.padding.bottom,
}; };
} }
render({left, right}, env) { render({left, right}, env) {
const config = env.theme.block; const blockInfo = env.state.blocks.get(left);
const config = env.theme.getBlock(blockInfo.mode);
const agentInfoL = env.agentInfos.get(left); const agentInfoL = env.agentInfos.get(left);
const agentInfoR = env.agentInfos.get(right); const agentInfoR = env.agentInfos.get(right);
const {hold, startY, mode} = env.state.blocks.get(left);
const configMode = config.modes[mode] || config.modes['']; blockInfo.hold.appendChild(config.boxRenderer({
hold.appendChild(svg.make('rect', Object.assign({ x: agentInfoL.x,
'x': agentInfoL.x, y: blockInfo.startY,
'y': startY, width: agentInfoR.x - agentInfoL.x,
'width': agentInfoR.x - agentInfoL.x, height: env.primaryY - blockInfo.startY,
'height': env.primaryY - startY, }));
}, configMode.boxAttrs)));
return env.primaryY + config.margin.bottom + env.theme.actionMargin; return env.primaryY + config.margin.bottom + env.theme.actionMargin;
} }

View File

@ -9,19 +9,6 @@ define([
) => { ) => {
'use strict'; 'use strict';
function drawHorizontalArrowHead({x, y, dx, dy, attrs}) {
return svg.make(
attrs.fill === 'none' ? 'polyline' : 'polygon',
Object.assign({
'points': (
(x + dx) + ' ' + (y - dy) + ' ' +
x + ' ' + y + ' ' +
(x + dx) + ' ' + (y + dy)
),
}, attrs)
);
}
class Arrowhead { class Arrowhead {
constructor(propName) { constructor(propName) {
this.propName = propName; this.propName = propName;
@ -48,13 +35,11 @@ define([
render(layer, theme, pt, dir) { render(layer, theme, pt, dir) {
const config = this.getConfig(theme); const config = this.getConfig(theme);
const func = config.render || drawHorizontalArrowHead; layer.appendChild(config.render(config.attrs, {
layer.appendChild(func({
x: pt.x + this.short(theme) * dir, x: pt.x + this.short(theme) * dir,
y: pt.y, y: pt.y,
dx: config.width * dir, dx: config.width * dir,
dy: config.height / 2, dy: config.height / 2,
attrs: config.attrs,
})); }));
} }

View File

@ -0,0 +1,113 @@
define(['svg/SVGUtilities'], (svg) => {
'use strict';
function deepCopy(o) {
if(typeof o !== 'object' || !o) {
return o;
}
const r = {};
for(let k in o) {
if(o.hasOwnProperty(k)) {
r[k] = deepCopy(o[k]);
}
}
return r;
}
class BaseTheme {
constructor({name, settings, blocks, notes}) {
this.name = name;
this.blocks = deepCopy(blocks);
this.notes = deepCopy(notes);
Object.assign(this, deepCopy(settings));
}
reset() {
}
addDefs() {
}
getBlock(type) {
return this.blocks[type] || this.blocks[''];
}
getNote(type) {
return this.notes[type] || this.notes[''];
}
renderAgentLine({x, y0, y1, width, className}) {
if(width > 0) {
return svg.make('rect', Object.assign({
'x': x - width / 2,
'y': y0,
'width': width,
'height': y1 - y0,
'class': className,
}, this.agentLineAttrs));
} else {
return svg.make('line', Object.assign({
'x1': x,
'y1': y0,
'x2': x,
'y2': y1,
'class': className,
}, this.agentLineAttrs));
}
}
}
BaseTheme.renderHorizArrowHead = (attrs, {x, y, dx, dy}) => {
return svg.make(
attrs.fill === 'none' ? 'polyline' : 'polygon',
Object.assign({
'points': (
(x + dx) + ' ' + (y - dy) + ' ' +
x + ' ' + y + ' ' +
(x + dx) + ' ' + (y + dy)
),
}, attrs)
);
};
BaseTheme.renderTag = (attrs, {x, y, width, height}) => {
const {rx, ry} = attrs;
const x2 = x + width;
const y2 = y + height;
const line = (
'M' + x2 + ' ' + y +
'L' + x2 + ' ' + (y2 - ry) +
'L' + (x2 - rx) + ' ' + y2 +
'L' + x + ' ' + y2
);
const g = svg.make('g');
if(attrs.fill !== 'none') {
g.appendChild(svg.make('path', Object.assign({
'd': line + 'L' + x + ' ' + y,
}, attrs, {'stroke': 'none'})));
}
if(attrs.stroke !== 'none') {
g.appendChild(svg.make('path', Object.assign({
'd': line,
}, attrs, {'fill': 'none'})));
}
return g;
};
BaseTheme.renderCross = (attrs, {x, y, radius}) => {
return svg.make('path', Object.assign({
'd': (
'M' + (x - radius) + ' ' + (y - radius) +
'l' + (radius * 2) + ' ' + (radius * 2) +
'm0 ' + (-radius * 2) +
'l' + (-radius * 2) + ' ' + (radius * 2)
),
}, attrs));
};
return BaseTheme;
});

View File

@ -1,9 +1,9 @@
define([ define([
'core/ArrayUtilities', './BaseTheme',
'svg/SVGUtilities', 'svg/SVGUtilities',
'svg/SVGShapes', 'svg/SVGShapes',
], ( ], (
array, BaseTheme,
svg, svg,
SVGShapes SVGShapes
) => { ) => {
@ -43,33 +43,19 @@ define([
}, },
cross: { cross: {
size: 20, size: 20,
render: ({x, y, radius}) => { render: BaseTheme.renderCross.bind(null, {
return svg.make('path', { 'fill': 'none',
'd': ( 'stroke': '#000000',
'M' + (x - radius) + ' ' + (y - radius) + 'stroke-width': 1,
'l' + (radius * 2) + ' ' + (radius * 2) + }),
'm0 ' + (-radius * 2) +
'l' + (-radius * 2) + ' ' + (radius * 2)
),
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
});
},
}, },
bar: { bar: {
height: 4, height: 4,
render: ({x, y, width, height}) => { render: SVGShapes.renderBox.bind(null, {
return svg.make('rect', { 'fill': '#000000',
'x': x, 'stroke': '#000000',
'y': y, 'stroke-width': 1,
'width': width, }),
'height': height,
'fill': '#000000',
'stroke': '#000000',
'stroke-width': 1,
});
},
}, },
fade: { fade: {
width: 5, width: 5,
@ -114,6 +100,7 @@ define([
'single': { 'single': {
width: 5, width: 5,
height: 10, height: 10,
render: BaseTheme.renderHorizArrowHead,
attrs: { attrs: {
'fill': '#000000', 'fill': '#000000',
'stroke-width': 0, 'stroke-width': 0,
@ -123,6 +110,7 @@ define([
'double': { 'double': {
width: 4, width: 4,
height: 6, height: 6,
render: BaseTheme.renderHorizArrowHead,
attrs: { attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
@ -156,82 +144,6 @@ define([
}, },
}, },
block: {
margin: {
top: 0,
bottom: 0,
},
modes: {
'ref': {
boxAttrs: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
},
},
'': {
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': FONT,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 1,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
},
separator: {
attrs: {
'stroke': '#000000',
'stroke-width': 1.5,
'stroke-dasharray': '4, 2',
},
},
},
titleAttrs: { titleAttrs: {
'font-family': FONT, 'font-family': FONT,
'font-size': 20, 'font-size': 20,
@ -247,6 +159,91 @@ define([
}, },
}; };
const SHARED_BLOCK_SECTION = {
padding: {
top: 3,
bottom: 2,
},
mode: {
padding: {
top: 1,
left: 3,
right: 3,
bottom: 0,
},
boxRenderer: BaseTheme.renderTag.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'rx': 2,
'ry': 2,
}),
labelAttrs: {
'font-family': FONT,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 1,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
};
const BLOCKS = {
'ref': {
margin: {
top: 0,
bottom: 0,
},
boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
}),
section: SHARED_BLOCK_SECTION,
},
'': {
margin: {
top: 0,
bottom: 0,
},
boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
}),
section: SHARED_BLOCK_SECTION,
sepRenderer: SVGShapes.renderLine.bind(null, {
'stroke': '#000000',
'stroke-width': 1.5,
'stroke-dasharray': '4, 2',
}),
},
};
const NOTE_ATTRS = {
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
};
const NOTES = { const NOTES = {
'text': { 'text': {
margin: {top: 0, left: 2, right: 2, bottom: 0}, margin: {top: 0, left: 2, right: 2, bottom: 0},
@ -255,11 +252,7 @@ define([
boxRenderer: SVGShapes.renderBox.bind(null, { boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
'note': { 'note': {
margin: {top: 0, left: 5, right: 5, bottom: 0}, margin: {top: 0, left: 5, right: 5, bottom: 0},
@ -274,11 +267,7 @@ define([
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
'state': { 'state': {
margin: {top: 0, left: 5, right: 5, bottom: 0}, margin: {top: 0, left: 5, right: 5, bottom: 0},
@ -291,48 +280,18 @@ define([
'rx': 10, 'rx': 10,
'ry': 10, 'ry': 10,
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
}; };
return class BasicTheme { return class BasicTheme extends BaseTheme {
constructor() { constructor() {
this.name = 'basic'; super({
Object.assign(this, SETTINGS); name: 'basic',
} settings: SETTINGS,
blocks: BLOCKS,
reset() { notes: NOTES,
} });
addDefs() {
}
getNote(type) {
return NOTES[type];
}
drawAgentLine(container, {x, y0, y1, width, className}) {
if(width > 0) {
container.appendChild(svg.make('rect', Object.assign({
'x': x - width / 2,
'y': y0,
'width': width,
'height': y1 - y0,
'class': className,
}, this.agentLineAttrs)));
} else {
container.appendChild(svg.make('line', Object.assign({
'x1': x,
'y1': y0,
'x2': x,
'y2': y1,
'class': className,
}, this.agentLineAttrs)));
}
} }
}; };
}); });

View File

@ -1,9 +1,9 @@
define([ define([
'core/ArrayUtilities', './BaseTheme',
'svg/SVGUtilities', 'svg/SVGUtilities',
'svg/SVGShapes', 'svg/SVGShapes',
], ( ], (
array, BaseTheme,
svg, svg,
SVGShapes SVGShapes
) => { ) => {
@ -46,37 +46,22 @@ define([
}, },
cross: { cross: {
size: 20, size: 20,
render: ({x, y, radius}) => { render: BaseTheme.renderCross.bind(null, {
return svg.make('path', Object.assign({ 'fill': 'none',
'd': ( 'stroke': '#000000',
'M' + (x - radius) + ' ' + (y - radius) + 'stroke-width': 3,
'l' + (radius * 2) + ' ' + (radius * 2) + 'stroke-linecap': 'round',
'm0 ' + (-radius * 2) + }),
'l' + (-radius * 2) + ' ' + (radius * 2)
),
}, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
'stroke-linecap': 'round',
}));
},
}, },
bar: { bar: {
height: 4, height: 4,
render: ({x, y, width, height}) => { render: SVGShapes.renderBox.bind(null, {
return svg.make('rect', { 'fill': '#000000',
'x': x, 'stroke': '#000000',
'y': y, 'stroke-width': 3,
'width': width, 'rx': 2,
'height': height, 'ry': 2,
'fill': '#000000', }),
'stroke': '#000000',
'stroke-width': 3,
'rx': 2,
'ry': 2,
});
},
}, },
fade: { fade: {
width: 5, width: 5,
@ -121,6 +106,7 @@ define([
single: { single: {
width: 10, width: 10,
height: 12, height: 12,
render: BaseTheme.renderHorizArrowHead,
attrs: { attrs: {
'fill': '#000000', 'fill': '#000000',
'stroke': '#000000', 'stroke': '#000000',
@ -131,6 +117,7 @@ define([
double: { double: {
width: 10, width: 10,
height: 12, height: 12,
render: BaseTheme.renderHorizArrowHead,
attrs: { attrs: {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
@ -165,82 +152,6 @@ define([
}, },
}, },
block: {
margin: {
top: 0,
bottom: 0,
},
modes: {
'ref': {
boxAttrs: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 4,
'rx': 5,
'ry': 5,
},
},
'': {
boxAttrs: {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 4,
'rx': 5,
'ry': 5,
},
},
},
section: {
padding: {
top: 3,
bottom: 4,
},
mode: {
padding: {
top: 2,
left: 5,
right: 5,
bottom: 1,
},
boxAttrs: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 2,
'rx': 3,
'ry': 3,
},
labelAttrs: {
'font-family': FONT,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 2,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
},
separator: {
attrs: {
'stroke': '#000000',
'stroke-width': 2,
'stroke-dasharray': '5, 3',
},
},
},
titleAttrs: { titleAttrs: {
'font-family': FONT, 'font-family': FONT,
'font-weight': 'bolder', 'font-weight': 'bolder',
@ -257,6 +168,91 @@ define([
}, },
}; };
const SHARED_BLOCK_SECTION = {
padding: {
top: 3,
bottom: 4,
},
mode: {
padding: {
top: 2,
left: 5,
right: 5,
bottom: 1,
},
boxRenderer: BaseTheme.renderTag.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 2,
'rx': 3,
'ry': 3,
}),
labelAttrs: {
'font-family': FONT,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 2,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
};
const BLOCKS = {
'ref': {
margin: {
top: 0,
bottom: 0,
},
boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 4,
'rx': 5,
'ry': 5,
}),
section: SHARED_BLOCK_SECTION,
},
'': {
margin: {
top: 0,
bottom: 0,
},
boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 4,
'rx': 5,
'ry': 5,
}),
section: SHARED_BLOCK_SECTION,
sepRenderer: SVGShapes.renderLine.bind(null, {
'stroke': '#000000',
'stroke-width': 2,
'stroke-dasharray': '5, 3',
}),
},
};
const NOTE_ATTRS = {
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
};
const NOTES = { const NOTES = {
'text': { 'text': {
margin: {top: 0, left: 2, right: 2, bottom: 0}, margin: {top: 0, left: 2, right: 2, bottom: 0},
@ -265,11 +261,7 @@ define([
boxRenderer: SVGShapes.renderBox.bind(null, { boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
'note': { 'note': {
margin: {top: 0, left: 5, right: 5, bottom: 0}, margin: {top: 0, left: 5, right: 5, bottom: 0},
@ -285,11 +277,7 @@ define([
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
'state': { 'state': {
margin: {top: 0, left: 5, right: 5, bottom: 0}, margin: {top: 0, left: 5, right: 5, bottom: 0},
@ -302,48 +290,18 @@ define([
'rx': 10, 'rx': 10,
'ry': 10, 'ry': 10,
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
}; };
return class ChunkyTheme { return class ChunkyTheme extends BaseTheme {
constructor() { constructor() {
this.name = 'chunky'; super({
Object.assign(this, SETTINGS); name: 'chunky',
} settings: SETTINGS,
blocks: BLOCKS,
reset() { notes: NOTES,
} });
addDefs() {
}
getNote(type) {
return NOTES[type];
}
drawAgentLine(container, {x, y0, y1, width, className}) {
if(width > 0) {
container.appendChild(svg.make('rect', Object.assign({
'x': x - width / 2,
'y': y0,
'width': width,
'height': y1 - y0,
'class': className,
}, this.agentLineAttrs)));
} else {
container.appendChild(svg.make('line', Object.assign({
'x1': x,
'y1': y0,
'x2': x,
'y2': y1,
'class': className,
}, this.agentLineAttrs)));
}
} }
}; };
}); });

View File

@ -1,10 +1,10 @@
define([ define([
'core/ArrayUtilities', './BaseTheme',
'svg/SVGUtilities', 'svg/SVGUtilities',
'svg/SVGShapes', 'svg/SVGShapes',
'./HandleeFontData', './HandleeFontData',
], ( ], (
array, BaseTheme,
svg, svg,
SVGShapes, SVGShapes,
Handlee Handlee
@ -13,25 +13,11 @@ define([
// TODO: // TODO:
// * fade starter/terminator sometimes does not fully cover line // * fade starter/terminator sometimes does not fully cover line
// * blocks (if/else/repeat/ref)
const FONT = Handlee.name; const FONT = Handlee.name;
const FONT_FAMILY = '"' + FONT + '",cursive'; const FONT_FAMILY = '"' + FONT + '",cursive';
const LINE_HEIGHT = 1.5; const LINE_HEIGHT = 1.5;
function deepCopy(o) {
if(typeof o !== 'object' || !o) {
return o;
}
const r = {};
for(let k in o) {
if(o.hasOwnProperty(k)) {
r[k] = deepCopy(o[k]);
}
}
return r;
}
const PENCIL = { const PENCIL = {
'stroke': 'rgba(0,0,0,0.7)', 'stroke': 'rgba(0,0,0,0.7)',
'stroke-width': 0.8, 'stroke-width': 0.8,
@ -39,6 +25,13 @@ define([
'stroke-linecap': 'round', 'stroke-linecap': 'round',
}; };
const THICK_PENCIL = {
'stroke': 'rgba(0,0,0,0.8)',
'stroke-width': 1.2,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
};
const SETTINGS = { const SETTINGS = {
titleMargin: 10, titleMargin: 10,
outerMargin: 5, outerMargin: 5,
@ -154,82 +147,6 @@ define([
}, },
}, },
block: {
margin: {
top: 0,
bottom: 0,
},
modes: {
'ref': {
boxAttrs: {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1.5,
'rx': 2,
'ry': 2,
},
},
'': {
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': FONT_FAMILY,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 1,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
},
separator: {
attrs: {
'stroke': '#000000',
'stroke-width': 1.5,
'stroke-dasharray': '4, 2',
},
},
},
titleAttrs: { titleAttrs: {
'font-family': FONT_FAMILY, 'font-family': FONT_FAMILY,
'font-size': 20, 'font-size': 20,
@ -245,6 +162,69 @@ define([
}, },
}; };
const SHARED_BLOCK_SECTION = {
padding: {
top: 3,
bottom: 2,
},
mode: {
padding: {
top: 2,
left: 3,
right: 5,
bottom: 0,
},
boxRenderer: null,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-weight': 'bold',
'font-size': 9,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
label: {
padding: {
top: 1,
left: 5,
right: 3,
bottom: 0,
},
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
'text-anchor': 'left',
},
},
};
const BLOCKS = {
'ref': {
margin: {
top: 0,
bottom: 0,
},
boxRenderer: null,
section: SHARED_BLOCK_SECTION,
},
'': {
margin: {
top: 0,
bottom: 0,
},
boxRenderer: null,
section: SHARED_BLOCK_SECTION,
sepRenderer: null,
},
};
const NOTE_ATTRS = {
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
};
const NOTES = { const NOTES = {
'text': { 'text': {
margin: {top: 0, left: 2, right: 2, bottom: 0}, margin: {top: 0, left: 2, right: 2, bottom: 0},
@ -253,33 +233,21 @@ define([
boxRenderer: SVGShapes.renderBox.bind(null, { boxRenderer: SVGShapes.renderBox.bind(null, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
}), }),
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
'note': { 'note': {
margin: {top: 0, left: 5, right: 5, bottom: 0}, margin: {top: 0, left: 5, right: 5, bottom: 0},
padding: {top: 5, left: 5, right: 10, bottom: 5}, padding: {top: 5, left: 5, right: 10, bottom: 5},
overlap: {left: 10, right: 10}, overlap: {left: 10, right: 10},
boxRenderer: null, boxRenderer: null,
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
'state': { 'state': {
margin: {top: 0, left: 5, right: 5, bottom: 0}, margin: {top: 0, left: 5, right: 5, bottom: 0},
padding: {top: 7, left: 7, right: 7, bottom: 7}, padding: {top: 7, left: 7, right: 7, bottom: 7},
overlap: {left: 10, right: 10}, overlap: {left: 10, right: 10},
boxRenderer: null, boxRenderer: null,
labelAttrs: { labelAttrs: NOTE_ATTRS,
'font-family': FONT_FAMILY,
'font-size': 8,
'line-height': LINE_HEIGHT,
},
}, },
}; };
@ -324,8 +292,15 @@ define([
const RIGHT = {}; const RIGHT = {};
const LEFT = {}; const LEFT = {};
class SketchTheme { class SketchTheme extends BaseTheme {
constructor(handedness = RIGHT) { constructor(handedness = RIGHT) {
super({
name: '',
settings: SETTINGS,
blocks: BLOCKS,
notes: NOTES,
});
if(handedness === RIGHT) { if(handedness === RIGHT) {
this.name = 'sketch'; this.name = 'sketch';
this.handedness = 1; this.handedness = 1;
@ -334,17 +309,31 @@ define([
this.handedness = -1; this.handedness = -1;
} }
this.random = new Random(); this.random = new Random();
Object.assign(this, deepCopy(SETTINGS));
this.notes = deepCopy(NOTES); this._assignFunctions();
}
_assignFunctions() {
this.renderBar = this.renderBar.bind(this);
this.renderBox = this.renderBox.bind(this);
this.renderArrowHead = this.renderArrowHead.bind(this);
this.renderConnect = this.renderConnect.bind(this);
this.renderTag = this.renderTag.bind(this);
this.agentCap.cross.render = this.renderCross.bind(this); this.agentCap.cross.render = this.renderCross.bind(this);
this.agentCap.bar.render = this.renderBar.bind(this); this.agentCap.bar.render = this.renderBar;
this.agentCap.box.boxRenderer = this.renderBox.bind(this); this.agentCap.box.boxRenderer = this.renderBox;
this.connect.arrow.single.render = this.renderArrowHead.bind(this); this.connect.arrow.single.render = this.renderArrowHead;
this.connect.arrow.double.render = this.renderArrowHead.bind(this); this.connect.arrow.double.render = this.renderArrowHead;
this.connect.line.solid.render = this.renderConnect.bind(this); this.connect.line.solid.render = this.renderConnect;
this.connect.line.dash.render = this.renderConnect.bind(this); this.connect.line.dash.render = this.renderConnect;
this.notes.note.boxRenderer = this.renderNote.bind(this); this.notes.note.boxRenderer = this.renderNote.bind(this);
this.notes.state.boxRenderer = this.renderState.bind(this); this.notes.state.boxRenderer = this.renderState.bind(this);
this.blocks.ref.boxRenderer = this.renderRefBlock.bind(this);
this.blocks[''].boxRenderer = this.renderBlock.bind(this);
this.blocks.ref.section.mode.boxRenderer = this.renderTag;
this.blocks[''].section.mode.boxRenderer = this.renderTag;
this.blocks[''].sepRenderer = this.renderSeparator.bind(this);
} }
reset() { reset() {
@ -434,11 +423,12 @@ define([
const shape = svg.make('path', Object.assign({ const shape = svg.make('path', Object.assign({
'd': line.nodes, 'd': line.nodes,
'fill': 'none', 'fill': 'none',
}, PENCIL)); 'stroke-dasharray': lineOptions.dash ? '6, 5' : 'none',
}, lineOptions.thick ? THICK_PENCIL : PENCIL));
return shape; return shape;
} }
renderBox({x, y, width, height}, {fill = null} = {}) { renderBox({x, y, width, height}, {fill = null, thick = false} = {}) {
const lT = this.lineNodes( const lT = this.lineNodes(
{x, y}, {x, y},
{x: x + width, y}, {x: x + width, y},
@ -463,7 +453,7 @@ define([
const shape = svg.make('path', Object.assign({ const shape = svg.make('path', Object.assign({
'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes, 'd': lT.nodes + lR.nodes + lB.nodes + lL.nodes,
'fill': fill || '#FFFFFF', 'fill': fill || '#FFFFFF',
}, PENCIL)); }, thick ? THICK_PENCIL : PENCIL));
return shape; return shape;
} }
@ -538,7 +528,7 @@ define([
}; };
} }
renderArrowHead({x, y, dx, dy, attrs}) { renderArrowHead(attrs, {x, y, dx, dy}) {
const w = dx * (1 + this.vary(0.2)); const w = dx * (1 + this.vary(0.2));
const h = dy * (1 + this.vary(0.3)); const h = dy * (1 + this.vary(0.3));
const l1 = this.lineNodes( const l1 = this.lineNodes(
@ -566,6 +556,60 @@ define([
return this.renderBox({x, y, width, height}); return this.renderBox({x, y, width, height});
} }
renderRefBlock({x, y, width, height}) {
return this.renderBox(
{x, y, width, height},
{fill: '#FFFFFF', thick: true}
);
}
renderBlock({x, y, width, height}) {
return this.renderBox(
{x, y, width, height},
{fill: 'none', thick: true}
);
}
renderTag({x, y, width, height}) {
const x2 = x + width;
const y2 = y + height;
const l1 = this.lineNodes(
{x: x2 + 3, y},
{x: x2 - 2, y: y2},
{}
);
const l2 = this.lineNodes(
l1.p2,
{x, y: y2 + 1},
{var1: 0, move: false}
);
const line = l1.nodes + l2.nodes;
const g = svg.make('g');
g.appendChild(svg.make('path', {
'd': line + 'L' + x + ' ' + y,
'fill': '#FFFFFF',
}));
g.appendChild(svg.make('path', Object.assign({
'd': line,
'fill': '#FFFFFF',
}, PENCIL)));
return g;
}
renderSeparator({x1, y1, x2, y2}) {
return this.renderLine(
{x: x1, y: y1},
{x: x2, y: y2},
{thick: true, dash: true}
);
}
renderBar({x, y, width, height}) { renderBar({x, y, width, height}) {
return this.renderBox({x, y, width, height}, {fill: '#000000'}); return this.renderBox({x, y, width, height}, {fill: '#000000'});
} }
@ -590,11 +634,7 @@ define([
}, PENCIL)); }, PENCIL));
} }
getNote(type) { renderAgentLine({x, y0, y1, width, className}) {
return this.notes[type];
}
drawAgentLine(container, {x, y0, y1, width, className}) {
if(width > 0) { if(width > 0) {
const shape = this.renderBox({ const shape = this.renderBox({
x: x - width / 2, x: x - width / 2,
@ -603,7 +643,7 @@ define([
height: y1 - y0, height: y1 - y0,
}, {fill: 'none'}); }, {fill: 'none'});
shape.setAttribute('class', className); shape.setAttribute('class', className);
container.appendChild(shape); return shape;
} else { } else {
const shape = this.renderLine( const shape = this.renderLine(
{x, y: y0}, {x, y: y0},
@ -611,7 +651,7 @@ define([
{varY: 0.3} {varY: 0.3}
); );
shape.setAttribute('class', className); shape.setAttribute('class', className);
container.appendChild(shape); return shape;
} }
} }
} }

View File

@ -5,6 +5,10 @@ define(['./SVGUtilities', './SVGTextBlock'], (svg, SVGTextBlock) => {
return svg.make('rect', Object.assign({}, position, attrs)); return svg.make('rect', Object.assign({}, position, attrs));
} }
function renderLine(attrs, position) {
return svg.make('line', Object.assign({}, position, attrs));
}
function renderNote(attrs, flickAttrs, position) { function renderNote(attrs, flickAttrs, position) {
const g = svg.make('g'); const g = svg.make('g');
const x0 = position.x; const x0 = position.x;
@ -104,6 +108,7 @@ define(['./SVGUtilities', './SVGTextBlock'], (svg, SVGTextBlock) => {
return { return {
renderBox, renderBox,
renderLine,
renderNote, renderNote,
renderBoxedText, renderBoxedText,
TextBlock: SVGTextBlock, TextBlock: SVGTextBlock,