SequenceDiagram/scripts/sequence/renderer/components/AgentCap.mjs

367 lines
7.5 KiB
JavaScript

import BaseComponent, {register} from './BaseComponent.mjs';
import {mergeSets, removeAll} from '../../../core/ArrayUtilities.mjs';
const OUTLINE_ATTRS = {
'class': 'outline',
'fill': 'transparent',
};
class CapBox {
getConfig(options, env, isBegin) {
let config = null;
if(options.includes('database')) {
config = env.theme.agentCap.database;
} else if(isBegin && options.includes('person')) {
config = env.theme.agentCap.person;
}
return config || env.theme.agentCap.box;
}
prepareMeasurements({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env, isBegin);
env.textSizer.expectMeasure(config.labelAttrs, formattedLabel);
}
separation({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env, isBegin);
const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left +
config.padding.right
);
return {
left: width / 2,
radius: width / 2,
right: width / 2,
};
}
topShift({formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env, isBegin);
const height = (
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
config.padding.top +
config.padding.bottom
);
return Math.max(0, height - config.arrowBottom);
}
render(y, {x, formattedLabel, options}, env, isBegin) {
const config = this.getConfig(options, env, isBegin);
const text = env.svg.boxedText(config, formattedLabel, {x, y});
env.makeRegion().add(
text,
env.svg.box(OUTLINE_ATTRS, {
height: text.height,
width: text.width,
x: x - text.width / 2,
y,
})
);
return {
height: text.height,
lineBottom: text.height,
lineTop: 0,
};
}
}
class CapCross {
prepareMeasurements() {
// No-op
}
separation(agentInfo, env) {
const config = env.theme.agentCap.cross;
return {
left: config.size / 2,
radius: 0,
right: config.size / 2,
};
}
topShift(agentInfo, env) {
const config = env.theme.agentCap.cross;
return config.size / 2;
}
render(y, {x, options}, env) {
const config = env.theme.agentCap.cross;
const d = config.size / 2;
env.makeRegion().add(
config.render({
options,
radius: d,
x,
y: y + d,
}),
env.svg.box(OUTLINE_ATTRS, {
height: d * 2,
width: d * 2,
x: x - d,
y,
})
);
return {
height: d * 2,
lineBottom: d,
lineTop: d,
};
}
}
class CapBar {
prepareMeasurements({formattedLabel}, env) {
const config = env.theme.agentCap.box;
env.textSizer.expectMeasure(config.labelAttrs, formattedLabel);
}
separation({formattedLabel}, env) {
const config = env.theme.agentCap.box;
const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left +
config.padding.right
);
return {
left: width / 2,
radius: width / 2,
right: width / 2,
};
}
topShift(agentInfo, env) {
const config = env.theme.agentCap.bar;
return config.height / 2;
}
render(y, {x, formattedLabel, options}, env) {
const boxCfg = env.theme.agentCap.box;
const barCfg = env.theme.agentCap.bar;
const width = (
env.textSizer.measure(boxCfg.labelAttrs, formattedLabel).width +
boxCfg.padding.left +
boxCfg.padding.right
);
const {height} = barCfg;
env.makeRegion().add(
barCfg.render({
height,
options,
width,
x: x - width / 2,
y,
}),
env.svg.box(OUTLINE_ATTRS, {
height,
width,
x: x - width / 2,
y,
})
);
return {
height,
lineBottom: height,
lineTop: 0,
};
}
}
class CapFade {
prepareMeasurements() {
// No-op
}
separation({currentRad}) {
return {
left: currentRad,
radius: currentRad,
right: currentRad,
};
}
topShift(agentInfo, env, isBegin) {
const config = env.theme.agentCap.fade;
return isBegin ? config.height : 0;
}
render(y, {x}, env, isBegin) {
const config = env.theme.agentCap.fade;
const ratio = config.height / (config.height + config.extend);
const gradID = env.addDef(isBegin ? 'FadeIn' : 'FadeOut', () => (
env.svg.linearGradient({
'x1': '0%',
'x2': '0%',
'y1': isBegin ? '100%' : '0%',
'y2': isBegin ? '0%' : '100%',
}, [
{
'offset': '0%',
'stop-color': '#FFFFFF',
},
{
'offset': (100 * ratio).toFixed(3) + '%',
'stop-color': '#000000',
},
])
));
env.lineMaskLayer.add(env.svg.box({
'fill': 'url(#' + gradID + ')',
}, {
height: config.height + config.extend,
width: config.width,
x: x - config.width / 2,
y: y - (isBegin ? config.extend : 0),
}));
env.makeRegion().add(env.svg.box(OUTLINE_ATTRS, {
height: config.height,
width: config.width,
x: x - config.width / 2,
y,
}));
return {
height: config.height,
lineBottom: 0,
lineTop: config.height,
};
}
}
class CapNone {
prepareMeasurements() {
// No-op
}
separation({currentRad}) {
return {
left: currentRad,
radius: currentRad,
right: currentRad,
};
}
topShift(agentInfo, env) {
const config = env.theme.agentCap.none;
return config.height;
}
render(y, {x}, env) {
const config = env.theme.agentCap.none;
const w = 10;
env.makeRegion().add(env.svg.box(OUTLINE_ATTRS, {
height: config.height,
width: w,
x: x - w / 2,
y,
}));
return {
height: config.height,
lineBottom: 0,
lineTop: config.height,
};
}
}
const AGENT_CAPS = {
'bar': new CapBar(),
'box': new CapBox(),
'cross': new CapCross(),
'fade': new CapFade(),
'none': new CapNone(),
};
export default class AgentCap extends BaseComponent {
constructor(begin) {
super();
this.begin = begin;
}
prepareMeasurements({mode, agentIDs}, env) {
agentIDs.forEach((id) => {
const agentInfo = env.agentInfos.get(id);
const cap = AGENT_CAPS[mode];
cap.prepareMeasurements(agentInfo, env, this.begin);
});
}
separationPre({mode, agentIDs}, env) {
agentIDs.forEach((id) => {
const agentInfo = env.agentInfos.get(id);
const cap = AGENT_CAPS[mode];
const sep = cap.separation(agentInfo, env, this.begin);
env.addSpacing(id, sep);
agentInfo.currentMaxRad = Math.max(
agentInfo.currentMaxRad,
sep.radius
);
});
}
separation({agentIDs}, env) {
if(this.begin) {
mergeSets(env.visibleAgentIDs, agentIDs);
} else {
removeAll(env.visibleAgentIDs, agentIDs);
}
}
renderPre({mode, agentIDs}, env) {
let maxTopShift = 0;
agentIDs.forEach((id) => {
const agentInfo = env.agentInfos.get(id);
const cap = AGENT_CAPS[mode];
const topShift = cap.topShift(agentInfo, env, this.begin);
maxTopShift = Math.max(maxTopShift, topShift);
const r = cap.separation(agentInfo, env, this.begin).radius;
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
});
return {
agentIDs,
topShift: maxTopShift,
};
}
render({mode, agentIDs}, env) {
let maxEnd = 0;
agentIDs.forEach((id) => {
const agentInfo = env.agentInfos.get(id);
const cap = AGENT_CAPS[mode];
const topShift = cap.topShift(agentInfo, env, this.begin);
const y0 = env.primaryY - topShift;
const shifts = cap.render(y0, agentInfo, env, this.begin);
maxEnd = Math.max(maxEnd, y0 + shifts.height);
if(this.begin) {
env.drawAgentLine(id, y0 + shifts.lineBottom);
} else {
env.drawAgentLine(id, y0 + shifts.lineTop, true);
}
});
return maxEnd + env.theme.actionMargin;
}
renderHidden({agentIDs}, env) {
agentIDs.forEach((id) => {
env.drawAgentLine(id, env.topY, !this.begin);
});
}
}
register('agent begin', new AgentCap(true));
register('agent end', new AgentCap(false));