Add person indicator, add sketch rendering for databases [#36]

This commit is contained in:
David Evans 2018-05-12 15:44:49 +01:00
parent 4551df9395
commit a60d8fb0cb
14 changed files with 742 additions and 74 deletions

View File

@ -207,17 +207,52 @@
return g; return g;
} }
renderDB(attrs, position) { renderPerson({iconHeight, iconWidth}, iconAttrs, boxAttrs, position) {
const z = attrs['db-z']; const cx = position.x + position.width / 2;
const sx = iconWidth / 2;
const sy = iconHeight;
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
'M' + (cx - sx) + ' ' + (position.y + iconHeight) +
'a' + sx + ' ' + (sy * 0.3) + ' 0 0 1' +
' ' + (sx * 2) + ' 0'
))
.attrs(iconAttrs),
this.svg.el('path')
.attr('d', (
'M' + cx + ' ' + position.y +
'c' + (sx * 0.224) + ' 0' +
' ' + (sx * 0.4) + ' ' + (sy * 0.1) +
' ' + (sx * 0.4) + ' ' + (sy * 0.275) +
's' + (-sx * 0.176) + ' ' + (sy * 0.35) +
' ' + (-sx * 0.4) + ' ' + (sy * 0.35) +
's' + (-sx * 0.4) + ' ' + (-sy * 0.175) +
' ' + (-sx * 0.4) + ' ' + (-sy * 0.35) +
's' + (sx * 0.176) + ' ' + (-sy * 0.275) +
' ' + (sx * 0.4) + ' ' + (-sy * 0.275)
))
.attrs(iconAttrs),
this.svg.box(boxAttrs, {
height: position.height - iconHeight,
width: position.width,
x: position.x,
y: position.y + iconHeight,
})
);
}
renderDB({tilt}, attrs, position) {
return this.svg.el('g').add( return this.svg.el('g').add(
this.svg.box({ this.svg.box({
'rx': position.width / 2, 'rx': position.width / 2,
'ry': z, 'ry': tilt,
}, position).attrs(attrs), }, position).attrs(attrs),
this.svg.el('path') this.svg.el('path')
.attr('d', ( .attr('d', (
'M' + position.x + ' ' + (position.y + z) + 'M' + position.x + ' ' + (position.y + tilt) +
'a' + (position.width / 2) + ' ' + z + 'a' + (position.width / 2) + ' ' + tilt +
' 0 0 0 ' + position.width + ' 0' ' 0 0 0 ' + position.width + ' 0'
)) ))
.attrs(attrs) .attrs(attrs)
@ -527,6 +562,31 @@
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 15,
iconWidth: 18,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 12, top: 12,
@ -535,11 +595,10 @@
bottom: 3, bottom: 3,
}, },
arrowBottom: 5 + 12 * 1.3 / 2, arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 5}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'db-z': 5,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT, 'font-family': FONT,
@ -912,6 +971,34 @@
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 16,
left: 3,
right: 3,
bottom: 1,
},
arrowBottom: 2 + 14 * 1.3 / 2,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 15,
iconWidth: 18,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 3,
'rx': 4,
'ry': 4,
}),
labelAttrs: {
'font-family': FONT$1,
'font-weight': 'bold',
'font-size': 14,
'line-height': LINE_HEIGHT$1,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 4, top: 4,
@ -920,11 +1007,10 @@
bottom: 0, bottom: 0,
}, },
arrowBottom: 2 + 14 * 1.3 / 2, arrowBottom: 2 + 14 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 2}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
'db-z': 2,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT$1, 'font-family': FONT$1,
@ -2899,6 +2985,31 @@
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 16,
left: 8,
right: 8,
bottom: 4,
},
arrowBottom: 12,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 12,
iconWidth: 14,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
}),
labelAttrs: {
'font-family': FONT$2,
'font-size': 12,
'line-height': LINE_HEIGHT$2,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 9, top: 9,
@ -2907,11 +3018,10 @@
bottom: 3, bottom: 3,
}, },
arrowBottom: 12, arrowBottom: 12,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 4}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'db-z': 4,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT$2, 'font-family': FONT$2,
@ -3210,6 +3320,7 @@
} }
const AGENT_INFO_TYPES = [ const AGENT_INFO_TYPES = [
'person',
'database', 'database',
'red', 'red',
]; ];
@ -5183,21 +5294,23 @@
}; };
class CapBox { class CapBox {
getConfig(options, env) { getConfig(options, env, isBegin) {
let config = null; let config = null;
if(options.includes('database')) { if(options.includes('database')) {
config = env.theme.agentCap.database; config = env.theme.agentCap.database;
} else if(isBegin && options.includes('person')) {
config = env.theme.agentCap.person;
} }
return config || env.theme.agentCap.box; return config || env.theme.agentCap.box;
} }
prepareMeasurements({formattedLabel, options}, env) { prepareMeasurements({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
env.textSizer.expectMeasure(config.labelAttrs, formattedLabel); env.textSizer.expectMeasure(config.labelAttrs, formattedLabel);
} }
separation({formattedLabel, options}, env) { separation({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const width = ( const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width + env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left + config.padding.left +
@ -5211,8 +5324,8 @@
}; };
} }
topShift({formattedLabel, options}, env) { topShift({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const height = ( const height = (
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) + env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
config.padding.top + config.padding.top +
@ -5221,8 +5334,8 @@
return Math.max(0, height - config.arrowBottom); return Math.max(0, height - config.arrowBottom);
} }
render(y, {x, formattedLabel, options}, env) { render(y, {x, formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const text = env.svg.boxedText(config, formattedLabel, {x, y}); const text = env.svg.boxedText(config, formattedLabel, {x, y});
@ -9091,6 +9204,22 @@
}, },
boxRenderer: this.renderBox.bind(this), boxRenderer: this.renderBox.bind(this),
}, },
person: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 12,
'line-height': LINE_HEIGHT$3,
'text-anchor': 'middle',
},
boxRenderer: this.renderPerson.bind(this),
},
database: { database: {
padding: { padding: {
top: 12, top: 12,
@ -9099,10 +9228,7 @@
bottom: 2, bottom: 2,
}, },
arrowBottom: 5 + 12 * 1.3 / 2, arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, Object.assign({ boxRenderer: this.renderDB.bind(this),
'fill': '#FFFFFF',
'db-z': 5,
}, PENCIL.normal)),
labelAttrs: { labelAttrs: {
'font-family': FONT$3, 'font-family': FONT$3,
'font-size': 12, 'font-size': 12,
@ -9414,6 +9540,99 @@
.attrs(attrs || (thick ? PENCIL.thick : PENCIL.normal)); .attrs(attrs || (thick ? PENCIL.thick : PENCIL.normal));
} }
renderPerson(position) {
const sx = 18 / 2;
const sy = 15;
const iconAttrs = Object.assign({'fill': 'none'}, PENCIL.normal);
const cx = position.x + position.width / 2;
const v = this.vary.bind(this);
const lean = v(0.1, 0.05 * this.handedness);
const skew = v(0.1, 0.05 * this.handedness);
const shoulders = v(0.05, 0.35);
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
'M' + (cx - sx) + ' ' + (position.y + sy) +
'c' + (sx * lean) + ' ' + (-sy * (shoulders - skew)) +
' ' + (sx * (2 + lean)) + ' ' + (-sy * (shoulders + skew)) +
' ' + (sx * 2) + ' 0'
))
.attrs(iconAttrs),
this.svg.el('path')
.attr('d', (
'M' + cx + ' ' + position.y +
'c' + (sx * v(0.05, 0.224)) + ' ' + v(0.02, 0) +
' ' + (sx * v(0.07, 0.4)) + ' ' + (sy * v(0.02, 0.1)) +
' ' + (sx * 0.4) + ' ' + (sy * v(0.05, 0.275)) +
's' + (-sx * v(0.05, 0.176)) + ' ' + (sy * 0.35) +
' ' + (-sx * 0.4) + ' ' + (sy * v(0.05, 0.35)) +
's' + (-sx * v(0.07, 0.4)) + ' ' + (-sy * 0.175) +
' ' + (-sx * 0.4) + ' ' + (-sy * v(0.05, 0.35)) +
's' + (sx * v(0.05, 0.176)) + ' ' + (-sy * 0.275) +
' ' + (sx * v(0.05, 0.4)) + ' ' + (-sy * v(0.02, 0.275))
))
.attrs(iconAttrs),
this.renderBox({
height: position.height - sy,
width: position.width,
x: position.x,
y: position.y + sy,
})
);
}
renderDB(pos) {
const tilt = 5;
const tiltC = tilt * 1.2;
const l1 = this.lineNodes(
{x: pos.x, y: pos.y + tilt},
{x: pos.x, y: pos.y + pos.height - tilt},
{}
);
const l2 = this.lineNodes(
{x: pos.x + pos.width, y: pos.y + pos.height - tilt},
{x: pos.x + pos.width, y: pos.y + tilt},
{move: false}
);
const v = this.vary.bind(this);
const cx = pos.x + pos.width / 2;
const dx = -pos.width * this.handedness / 2;
const topX1 = cx - dx * v(0.03, 1.02);
const topY1 = pos.y + tilt * v(0.15, 1);
const topX2 = cx + dx * v(0.03, 1.02);
const topY2 = pos.y + tilt * v(0.15, 1);
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
l1.nodes +
'C' + l1.p2.x + ' ' + (l1.p2.y + v(0.1, tiltC)) +
' ' + l2.p1.x + ' ' + (l2.p1.y + v(0.1, tiltC)) +
' ' + l2.p1.x + ' ' + l2.p1.y +
l2.nodes
))
.attrs(PENCIL.normal)
.attr('fill', '#FFFFFF'),
this.svg.el('path')
.attr('d', (
'M' + topX1 + ' ' + topY1 +
'C' + (topX1 + dx * 0.2) + ' ' + (topY1 - v(0.2, tiltC)) +
' ' + topX2 + ' ' + (topY2 - v(0.2, tiltC)) +
' ' + topX2 + ' ' + topY2 +
'S' + topX1 + ' ' + (topY1 + v(0.2, tiltC)) +
' ' + v(1, topX1) + ' ' + v(0.5, topY1)
))
.attrs(PENCIL.normal)
.attr('fill', '#FFFFFF')
);
}
renderNote({x, y, width, height}) { renderNote({x, y, width, height}) {
const flickSize = 5; const flickSize = 5;
const lT = this.lineNodes( const lT = this.lineNodes(

File diff suppressed because one or more lines are too long

View File

@ -207,17 +207,52 @@
return g; return g;
} }
renderDB(attrs, position) { renderPerson({iconHeight, iconWidth}, iconAttrs, boxAttrs, position) {
const z = attrs['db-z']; const cx = position.x + position.width / 2;
const sx = iconWidth / 2;
const sy = iconHeight;
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
'M' + (cx - sx) + ' ' + (position.y + iconHeight) +
'a' + sx + ' ' + (sy * 0.3) + ' 0 0 1' +
' ' + (sx * 2) + ' 0'
))
.attrs(iconAttrs),
this.svg.el('path')
.attr('d', (
'M' + cx + ' ' + position.y +
'c' + (sx * 0.224) + ' 0' +
' ' + (sx * 0.4) + ' ' + (sy * 0.1) +
' ' + (sx * 0.4) + ' ' + (sy * 0.275) +
's' + (-sx * 0.176) + ' ' + (sy * 0.35) +
' ' + (-sx * 0.4) + ' ' + (sy * 0.35) +
's' + (-sx * 0.4) + ' ' + (-sy * 0.175) +
' ' + (-sx * 0.4) + ' ' + (-sy * 0.35) +
's' + (sx * 0.176) + ' ' + (-sy * 0.275) +
' ' + (sx * 0.4) + ' ' + (-sy * 0.275)
))
.attrs(iconAttrs),
this.svg.box(boxAttrs, {
height: position.height - iconHeight,
width: position.width,
x: position.x,
y: position.y + iconHeight,
})
);
}
renderDB({tilt}, attrs, position) {
return this.svg.el('g').add( return this.svg.el('g').add(
this.svg.box({ this.svg.box({
'rx': position.width / 2, 'rx': position.width / 2,
'ry': z, 'ry': tilt,
}, position).attrs(attrs), }, position).attrs(attrs),
this.svg.el('path') this.svg.el('path')
.attr('d', ( .attr('d', (
'M' + position.x + ' ' + (position.y + z) + 'M' + position.x + ' ' + (position.y + tilt) +
'a' + (position.width / 2) + ' ' + z + 'a' + (position.width / 2) + ' ' + tilt +
' 0 0 0 ' + position.width + ' 0' ' 0 0 0 ' + position.width + ' 0'
)) ))
.attrs(attrs) .attrs(attrs)
@ -527,6 +562,31 @@
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 15,
iconWidth: 18,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 12, top: 12,
@ -535,11 +595,10 @@
bottom: 3, bottom: 3,
}, },
arrowBottom: 5 + 12 * 1.3 / 2, arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 5}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'db-z': 5,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT, 'font-family': FONT,
@ -912,6 +971,34 @@
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 16,
left: 3,
right: 3,
bottom: 1,
},
arrowBottom: 2 + 14 * 1.3 / 2,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 15,
iconWidth: 18,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 3,
'rx': 4,
'ry': 4,
}),
labelAttrs: {
'font-family': FONT$1,
'font-weight': 'bold',
'font-size': 14,
'line-height': LINE_HEIGHT$1,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 4, top: 4,
@ -920,11 +1007,10 @@
bottom: 0, bottom: 0,
}, },
arrowBottom: 2 + 14 * 1.3 / 2, arrowBottom: 2 + 14 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 2}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
'db-z': 2,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT$1, 'font-family': FONT$1,
@ -2899,6 +2985,31 @@
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 16,
left: 8,
right: 8,
bottom: 4,
},
arrowBottom: 12,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 12,
iconWidth: 14,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
}),
labelAttrs: {
'font-family': FONT$2,
'font-size': 12,
'line-height': LINE_HEIGHT$2,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 9, top: 9,
@ -2907,11 +3018,10 @@
bottom: 3, bottom: 3,
}, },
arrowBottom: 12, arrowBottom: 12,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 4}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'db-z': 4,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT$2, 'font-family': FONT$2,
@ -3210,6 +3320,7 @@
} }
const AGENT_INFO_TYPES = [ const AGENT_INFO_TYPES = [
'person',
'database', 'database',
'red', 'red',
]; ];
@ -5183,21 +5294,23 @@
}; };
class CapBox { class CapBox {
getConfig(options, env) { getConfig(options, env, isBegin) {
let config = null; let config = null;
if(options.includes('database')) { if(options.includes('database')) {
config = env.theme.agentCap.database; config = env.theme.agentCap.database;
} else if(isBegin && options.includes('person')) {
config = env.theme.agentCap.person;
} }
return config || env.theme.agentCap.box; return config || env.theme.agentCap.box;
} }
prepareMeasurements({formattedLabel, options}, env) { prepareMeasurements({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
env.textSizer.expectMeasure(config.labelAttrs, formattedLabel); env.textSizer.expectMeasure(config.labelAttrs, formattedLabel);
} }
separation({formattedLabel, options}, env) { separation({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const width = ( const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width + env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left + config.padding.left +
@ -5211,8 +5324,8 @@
}; };
} }
topShift({formattedLabel, options}, env) { topShift({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const height = ( const height = (
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) + env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
config.padding.top + config.padding.top +
@ -5221,8 +5334,8 @@
return Math.max(0, height - config.arrowBottom); return Math.max(0, height - config.arrowBottom);
} }
render(y, {x, formattedLabel, options}, env) { render(y, {x, formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const text = env.svg.boxedText(config, formattedLabel, {x, y}); const text = env.svg.boxedText(config, formattedLabel, {x, y});
@ -9091,6 +9204,22 @@
}, },
boxRenderer: this.renderBox.bind(this), boxRenderer: this.renderBox.bind(this),
}, },
person: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 12,
'line-height': LINE_HEIGHT$3,
'text-anchor': 'middle',
},
boxRenderer: this.renderPerson.bind(this),
},
database: { database: {
padding: { padding: {
top: 12, top: 12,
@ -9099,10 +9228,7 @@
bottom: 2, bottom: 2,
}, },
arrowBottom: 5 + 12 * 1.3 / 2, arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, Object.assign({ boxRenderer: this.renderDB.bind(this),
'fill': '#FFFFFF',
'db-z': 5,
}, PENCIL.normal)),
labelAttrs: { labelAttrs: {
'font-family': FONT$3, 'font-family': FONT$3,
'font-size': 12, 'font-size': 12,
@ -9414,6 +9540,99 @@
.attrs(attrs || (thick ? PENCIL.thick : PENCIL.normal)); .attrs(attrs || (thick ? PENCIL.thick : PENCIL.normal));
} }
renderPerson(position) {
const sx = 18 / 2;
const sy = 15;
const iconAttrs = Object.assign({'fill': 'none'}, PENCIL.normal);
const cx = position.x + position.width / 2;
const v = this.vary.bind(this);
const lean = v(0.1, 0.05 * this.handedness);
const skew = v(0.1, 0.05 * this.handedness);
const shoulders = v(0.05, 0.35);
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
'M' + (cx - sx) + ' ' + (position.y + sy) +
'c' + (sx * lean) + ' ' + (-sy * (shoulders - skew)) +
' ' + (sx * (2 + lean)) + ' ' + (-sy * (shoulders + skew)) +
' ' + (sx * 2) + ' 0'
))
.attrs(iconAttrs),
this.svg.el('path')
.attr('d', (
'M' + cx + ' ' + position.y +
'c' + (sx * v(0.05, 0.224)) + ' ' + v(0.02, 0) +
' ' + (sx * v(0.07, 0.4)) + ' ' + (sy * v(0.02, 0.1)) +
' ' + (sx * 0.4) + ' ' + (sy * v(0.05, 0.275)) +
's' + (-sx * v(0.05, 0.176)) + ' ' + (sy * 0.35) +
' ' + (-sx * 0.4) + ' ' + (sy * v(0.05, 0.35)) +
's' + (-sx * v(0.07, 0.4)) + ' ' + (-sy * 0.175) +
' ' + (-sx * 0.4) + ' ' + (-sy * v(0.05, 0.35)) +
's' + (sx * v(0.05, 0.176)) + ' ' + (-sy * 0.275) +
' ' + (sx * v(0.05, 0.4)) + ' ' + (-sy * v(0.02, 0.275))
))
.attrs(iconAttrs),
this.renderBox({
height: position.height - sy,
width: position.width,
x: position.x,
y: position.y + sy,
})
);
}
renderDB(pos) {
const tilt = 5;
const tiltC = tilt * 1.2;
const l1 = this.lineNodes(
{x: pos.x, y: pos.y + tilt},
{x: pos.x, y: pos.y + pos.height - tilt},
{}
);
const l2 = this.lineNodes(
{x: pos.x + pos.width, y: pos.y + pos.height - tilt},
{x: pos.x + pos.width, y: pos.y + tilt},
{move: false}
);
const v = this.vary.bind(this);
const cx = pos.x + pos.width / 2;
const dx = -pos.width * this.handedness / 2;
const topX1 = cx - dx * v(0.03, 1.02);
const topY1 = pos.y + tilt * v(0.15, 1);
const topX2 = cx + dx * v(0.03, 1.02);
const topY2 = pos.y + tilt * v(0.15, 1);
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
l1.nodes +
'C' + l1.p2.x + ' ' + (l1.p2.y + v(0.1, tiltC)) +
' ' + l2.p1.x + ' ' + (l2.p1.y + v(0.1, tiltC)) +
' ' + l2.p1.x + ' ' + l2.p1.y +
l2.nodes
))
.attrs(PENCIL.normal)
.attr('fill', '#FFFFFF'),
this.svg.el('path')
.attr('d', (
'M' + topX1 + ' ' + topY1 +
'C' + (topX1 + dx * 0.2) + ' ' + (topY1 - v(0.2, tiltC)) +
' ' + topX2 + ' ' + (topY2 - v(0.2, tiltC)) +
' ' + topX2 + ' ' + topY2 +
'S' + topX1 + ' ' + (topY1 + v(0.2, tiltC)) +
' ' + v(1, topX1) + ' ' + v(0.5, topY1)
))
.attrs(PENCIL.normal)
.attr('fill', '#FFFFFF')
);
}
renderNote({x, y, width, height}) { renderNote({x, y, width, height}) {
const flickSize = 5; const flickSize = 5;
const lT = this.lineNodes( const lT = this.lineNodes(

View File

@ -30,6 +30,7 @@ function suggestionsEqual(a, b) {
} }
const AGENT_INFO_TYPES = [ const AGENT_INFO_TYPES = [
'person',
'database', 'database',
'red', 'red',
]; ];

View File

@ -712,6 +712,7 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('A is a '); cm.getDoc().setValue('A is a ');
const hints = getHintTexts({ch: 7, line: 0}); const hints = getHintTexts({ch: 7, line: 0});
expect(hints).toContain('person ');
expect(hints).toContain('database '); expect(hints).toContain('database ');
expect(hints).toContain('red '); expect(hints).toContain('red ');
expect(hints).not.toContain('\n'); expect(hints).not.toContain('\n');

View File

@ -7,21 +7,23 @@ const OUTLINE_ATTRS = {
}; };
class CapBox { class CapBox {
getConfig(options, env) { getConfig(options, env, isBegin) {
let config = null; let config = null;
if(options.includes('database')) { if(options.includes('database')) {
config = env.theme.agentCap.database; config = env.theme.agentCap.database;
} else if(isBegin && options.includes('person')) {
config = env.theme.agentCap.person;
} }
return config || env.theme.agentCap.box; return config || env.theme.agentCap.box;
} }
prepareMeasurements({formattedLabel, options}, env) { prepareMeasurements({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
env.textSizer.expectMeasure(config.labelAttrs, formattedLabel); env.textSizer.expectMeasure(config.labelAttrs, formattedLabel);
} }
separation({formattedLabel, options}, env) { separation({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const width = ( const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width + env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left + config.padding.left +
@ -35,8 +37,8 @@ class CapBox {
}; };
} }
topShift({formattedLabel, options}, env) { topShift({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const height = ( const height = (
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) + env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
config.padding.top + config.padding.top +
@ -45,8 +47,8 @@ class CapBox {
return Math.max(0, height - config.arrowBottom); return Math.max(0, height - config.arrowBottom);
} }
render(y, {x, formattedLabel, options}, env) { render(y, {x, formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env); const config = this.getConfig(options, env, isBegin);
const text = env.svg.boxedText(config, formattedLabel, {x, y}); const text = env.svg.boxedText(config, formattedLabel, {x, y});

View File

@ -204,17 +204,52 @@ export default class BaseTheme {
return g; return g;
} }
renderDB(attrs, position) { renderPerson({iconHeight, iconWidth}, iconAttrs, boxAttrs, position) {
const z = attrs['db-z']; const cx = position.x + position.width / 2;
const sx = iconWidth / 2;
const sy = iconHeight;
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
'M' + (cx - sx) + ' ' + (position.y + iconHeight) +
'a' + sx + ' ' + (sy * 0.3) + ' 0 0 1' +
' ' + (sx * 2) + ' 0'
))
.attrs(iconAttrs),
this.svg.el('path')
.attr('d', (
'M' + cx + ' ' + position.y +
'c' + (sx * 0.224) + ' 0' +
' ' + (sx * 0.4) + ' ' + (sy * 0.1) +
' ' + (sx * 0.4) + ' ' + (sy * 0.275) +
's' + (-sx * 0.176) + ' ' + (sy * 0.35) +
' ' + (-sx * 0.4) + ' ' + (sy * 0.35) +
's' + (-sx * 0.4) + ' ' + (-sy * 0.175) +
' ' + (-sx * 0.4) + ' ' + (-sy * 0.35) +
's' + (sx * 0.176) + ' ' + (-sy * 0.275) +
' ' + (sx * 0.4) + ' ' + (-sy * 0.275)
))
.attrs(iconAttrs),
this.svg.box(boxAttrs, {
height: position.height - iconHeight,
width: position.width,
x: position.x,
y: position.y + iconHeight,
})
);
}
renderDB({tilt}, attrs, position) {
return this.svg.el('g').add( return this.svg.el('g').add(
this.svg.box({ this.svg.box({
'rx': position.width / 2, 'rx': position.width / 2,
'ry': z, 'ry': tilt,
}, position).attrs(attrs), }, position).attrs(attrs),
this.svg.el('path') this.svg.el('path')
.attr('d', ( .attr('d', (
'M' + position.x + ' ' + (position.y + z) + 'M' + position.x + ' ' + (position.y + tilt) +
'a' + (position.width / 2) + ' ' + z + 'a' + (position.width / 2) + ' ' + tilt +
' 0 0 0 ' + position.width + ' 0' ' 0 0 0 ' + position.width + ' 0'
)) ))
.attrs(attrs) .attrs(attrs)

View File

@ -99,6 +99,31 @@ export default class BasicTheme extends BaseTheme {
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 15,
iconWidth: 18,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 12, top: 12,
@ -107,11 +132,10 @@ export default class BasicTheme extends BaseTheme {
bottom: 3, bottom: 3,
}, },
arrowBottom: 5 + 12 * 1.3 / 2, arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 5}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'db-z': 5,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT, 'font-family': FONT,

View File

@ -102,6 +102,34 @@ export default class ChunkyTheme extends BaseTheme {
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 16,
left: 3,
right: 3,
bottom: 1,
},
arrowBottom: 2 + 14 * 1.3 / 2,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 15,
iconWidth: 18,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 3,
'rx': 4,
'ry': 4,
}),
labelAttrs: {
'font-family': FONT,
'font-weight': 'bold',
'font-size': 14,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 4, top: 4,
@ -110,11 +138,10 @@ export default class ChunkyTheme extends BaseTheme {
bottom: 0, bottom: 0,
}, },
arrowBottom: 2 + 14 * 1.3 / 2, arrowBottom: 2 + 14 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 2}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
'db-z': 2,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT, 'font-family': FONT,

View File

@ -99,6 +99,31 @@ export default class MonospaceTheme extends BaseTheme {
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
person: {
padding: {
top: 16,
left: 8,
right: 8,
bottom: 4,
},
arrowBottom: 12,
boxRenderer: this.renderPerson.bind(this, {
iconHeight: 12,
iconWidth: 14,
}, {
'fill': '#000000',
}, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
database: { database: {
padding: { padding: {
top: 9, top: 9,
@ -107,11 +132,10 @@ export default class MonospaceTheme extends BaseTheme {
bottom: 3, bottom: 3,
}, },
arrowBottom: 12, arrowBottom: 12,
boxRenderer: this.renderDB.bind(this, { boxRenderer: this.renderDB.bind(this, {tilt: 4}, {
'fill': '#FFFFFF', 'fill': '#FFFFFF',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
'db-z': 4,
}), }),
labelAttrs: { labelAttrs: {
'font-family': FONT, 'font-family': FONT,

View File

@ -145,6 +145,22 @@ export default class SketchTheme extends BaseTheme {
}, },
boxRenderer: this.renderBox.bind(this), boxRenderer: this.renderBox.bind(this),
}, },
person: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
labelAttrs: {
'font-family': FONT_FAMILY,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
boxRenderer: this.renderPerson.bind(this),
},
database: { database: {
padding: { padding: {
top: 12, top: 12,
@ -153,10 +169,7 @@ export default class SketchTheme extends BaseTheme {
bottom: 2, bottom: 2,
}, },
arrowBottom: 5 + 12 * 1.3 / 2, arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: this.renderDB.bind(this, Object.assign({ boxRenderer: this.renderDB.bind(this),
'fill': '#FFFFFF',
'db-z': 5,
}, PENCIL.normal)),
labelAttrs: { labelAttrs: {
'font-family': FONT, 'font-family': FONT,
'font-size': 12, 'font-size': 12,
@ -468,6 +481,99 @@ export default class SketchTheme extends BaseTheme {
.attrs(attrs || (thick ? PENCIL.thick : PENCIL.normal)); .attrs(attrs || (thick ? PENCIL.thick : PENCIL.normal));
} }
renderPerson(position) {
const sx = 18 / 2;
const sy = 15;
const iconAttrs = Object.assign({'fill': 'none'}, PENCIL.normal);
const cx = position.x + position.width / 2;
const v = this.vary.bind(this);
const lean = v(0.1, 0.05 * this.handedness);
const skew = v(0.1, 0.05 * this.handedness);
const shoulders = v(0.05, 0.35);
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
'M' + (cx - sx) + ' ' + (position.y + sy) +
'c' + (sx * lean) + ' ' + (-sy * (shoulders - skew)) +
' ' + (sx * (2 + lean)) + ' ' + (-sy * (shoulders + skew)) +
' ' + (sx * 2) + ' 0'
))
.attrs(iconAttrs),
this.svg.el('path')
.attr('d', (
'M' + cx + ' ' + position.y +
'c' + (sx * v(0.05, 0.224)) + ' ' + v(0.02, 0) +
' ' + (sx * v(0.07, 0.4)) + ' ' + (sy * v(0.02, 0.1)) +
' ' + (sx * 0.4) + ' ' + (sy * v(0.05, 0.275)) +
's' + (-sx * v(0.05, 0.176)) + ' ' + (sy * 0.35) +
' ' + (-sx * 0.4) + ' ' + (sy * v(0.05, 0.35)) +
's' + (-sx * v(0.07, 0.4)) + ' ' + (-sy * 0.175) +
' ' + (-sx * 0.4) + ' ' + (-sy * v(0.05, 0.35)) +
's' + (sx * v(0.05, 0.176)) + ' ' + (-sy * 0.275) +
' ' + (sx * v(0.05, 0.4)) + ' ' + (-sy * v(0.02, 0.275))
))
.attrs(iconAttrs),
this.renderBox({
height: position.height - sy,
width: position.width,
x: position.x,
y: position.y + sy,
})
);
}
renderDB(pos) {
const tilt = 5;
const tiltC = tilt * 1.2;
const l1 = this.lineNodes(
{x: pos.x, y: pos.y + tilt},
{x: pos.x, y: pos.y + pos.height - tilt},
{}
);
const l2 = this.lineNodes(
{x: pos.x + pos.width, y: pos.y + pos.height - tilt},
{x: pos.x + pos.width, y: pos.y + tilt},
{move: false}
);
const v = this.vary.bind(this);
const cx = pos.x + pos.width / 2;
const dx = -pos.width * this.handedness / 2;
const topX1 = cx - dx * v(0.03, 1.02);
const topY1 = pos.y + tilt * v(0.15, 1);
const topX2 = cx + dx * v(0.03, 1.02);
const topY2 = pos.y + tilt * v(0.15, 1);
return this.svg.el('g').add(
this.svg.el('path')
.attr('d', (
l1.nodes +
'C' + l1.p2.x + ' ' + (l1.p2.y + v(0.1, tiltC)) +
' ' + l2.p1.x + ' ' + (l2.p1.y + v(0.1, tiltC)) +
' ' + l2.p1.x + ' ' + l2.p1.y +
l2.nodes
))
.attrs(PENCIL.normal)
.attr('fill', '#FFFFFF'),
this.svg.el('path')
.attr('d', (
'M' + topX1 + ' ' + topY1 +
'C' + (topX1 + dx * 0.2) + ' ' + (topY1 - v(0.2, tiltC)) +
' ' + topX2 + ' ' + (topY2 - v(0.2, tiltC)) +
' ' + topX2 + ' ' + topY2 +
'S' + topX1 + ' ' + (topY1 + v(0.2, tiltC)) +
' ' + v(1, topX1) + ' ' + v(0.5, topY1)
))
.attrs(PENCIL.normal)
.attr('fill', '#FFFFFF')
);
}
renderNote({x, y, width, height}) { renderNote({x, y, width, height}) {
const flickSize = 5; const flickSize = 5;
const lT = this.lineNodes( const lT = this.lineNodes(

View File

@ -305,6 +305,11 @@
preview: 'headers box\nA is red\nbegin A', preview: 'headers box\nA is red\nbegin A',
title: 'Red agent line', title: 'Red agent line',
}, },
{
code: '{Agent} is a person',
preview: 'headers box\nA is a person\nbegin A',
title: 'Person indicator',
},
{ {
code: '{Agent} is a database', code: '{Agent} is a database',
preview: 'headers box\nA is a database\nbegin A', preview: 'headers box\nA is a database\nbegin A',

File diff suppressed because one or more lines are too long

View File

@ -302,6 +302,11 @@ export default [
preview: 'headers box\nA is red\nbegin A', preview: 'headers box\nA is red\nbegin A',
title: 'Red agent line', title: 'Red agent line',
}, },
{
code: '{Agent} is a person',
preview: 'headers box\nA is a person\nbegin A',
title: 'Person indicator',
},
{ {
code: '{Agent} is a database', code: '{Agent} is a database',
preview: 'headers box\nA is a database\nbegin A', preview: 'headers box\nA is a database\nbegin A',