260 lines
5.8 KiB
JavaScript
260 lines
5.8 KiB
JavaScript
import BaseComponent, {register} from './BaseComponent.mjs';
|
|
|
|
const OUTLINE_ATTRS = {
|
|
'class': 'outline',
|
|
'fill': 'transparent',
|
|
};
|
|
|
|
function findExtremes(agentInfos, agentIDs) {
|
|
let min = null;
|
|
let max = null;
|
|
agentIDs.forEach((id) => {
|
|
const info = agentInfos.get(id);
|
|
if(min === null || info.index < min.index) {
|
|
min = info;
|
|
}
|
|
if(max === null || info.index > max.index) {
|
|
max = info;
|
|
}
|
|
});
|
|
return {
|
|
left: min.id,
|
|
right: max.id,
|
|
};
|
|
}
|
|
|
|
function findEdges(fullW, {
|
|
x0 = null,
|
|
x1 = null,
|
|
xMid = null,
|
|
}) {
|
|
let xL = x0;
|
|
let xR = x1;
|
|
if(xL === null && xMid !== null) {
|
|
xL = xMid - fullW / 2;
|
|
}
|
|
if(xR === null && xL !== null) {
|
|
xR = xL + fullW;
|
|
} else if(xL === null) {
|
|
xL = xR - fullW;
|
|
}
|
|
return {xL, xR};
|
|
}
|
|
|
|
function textAnchorX(anchor, {xL, xR}, padding) {
|
|
switch(anchor) {
|
|
case 'middle':
|
|
return (xL + padding.left + xR - padding.right) / 2;
|
|
case 'end':
|
|
return xR - padding.right;
|
|
default:
|
|
return xL + padding.left;
|
|
}
|
|
}
|
|
|
|
class NoteComponent extends BaseComponent {
|
|
prepareMeasurements({mode, label}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
env.textSizer.expectMeasure(config.labelAttrs, label);
|
|
}
|
|
|
|
renderPre({agentIDs}) {
|
|
return {agentIDs};
|
|
}
|
|
|
|
renderNote({
|
|
label,
|
|
mode,
|
|
position,
|
|
}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
const {padding} = config;
|
|
|
|
const y = env.topY + config.margin.top + padding.top;
|
|
const labelNode = env.svg.formattedText(config.labelAttrs, label);
|
|
const size = env.textSizer.measure(labelNode);
|
|
|
|
const fullW = (size.width + padding.left + padding.right);
|
|
const fullH = (size.height + padding.top + padding.bottom);
|
|
const edges = findEdges(fullW, position);
|
|
|
|
labelNode.set({
|
|
x: textAnchorX(config.labelAttrs['text-anchor'], edges, padding),
|
|
y,
|
|
});
|
|
|
|
const boundingBox = {
|
|
height: fullH,
|
|
width: edges.xR - edges.xL,
|
|
x: edges.xL,
|
|
y: env.topY + config.margin.top,
|
|
};
|
|
|
|
env.makeRegion().add(
|
|
config.boxRenderer(boundingBox),
|
|
env.svg.box(OUTLINE_ATTRS, boundingBox),
|
|
labelNode
|
|
);
|
|
|
|
return (
|
|
env.topY +
|
|
config.margin.top +
|
|
fullH +
|
|
config.margin.bottom +
|
|
env.theme.actionMargin
|
|
);
|
|
}
|
|
}
|
|
|
|
export class NoteOver extends NoteComponent {
|
|
separation({agentIDs, mode, label}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
const width = (
|
|
env.textSizer.measure(config.labelAttrs, label).width +
|
|
config.padding.left +
|
|
config.padding.right
|
|
);
|
|
|
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
|
const infoL = env.agentInfos.get(left);
|
|
const infoR = env.agentInfos.get(right);
|
|
if(infoL === infoR) {
|
|
env.addSpacing(left, {
|
|
left: width / 2,
|
|
right: width / 2,
|
|
});
|
|
} else {
|
|
const hangL = infoL.currentMaxRad + config.overlap.left;
|
|
const hangR = infoR.currentMaxRad + config.overlap.right;
|
|
|
|
env.addSeparation(left, right, width - hangL - hangR);
|
|
|
|
env.addSpacing(left, {left: hangL, right: 0});
|
|
env.addSpacing(right, {left: 0, right: hangR});
|
|
}
|
|
}
|
|
|
|
render({agentIDs, mode, label}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
|
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
|
const infoL = env.agentInfos.get(left);
|
|
const infoR = env.agentInfos.get(right);
|
|
if(infoL === infoR) {
|
|
const xMid = infoL.x;
|
|
return this.renderNote({
|
|
label,
|
|
mode,
|
|
position: {xMid},
|
|
}, env);
|
|
} else {
|
|
return this.renderNote({
|
|
label,
|
|
mode,
|
|
position: {
|
|
x0: infoL.x - infoL.currentMaxRad - config.overlap.left,
|
|
x1: infoR.x + infoR.currentMaxRad + config.overlap.right,
|
|
},
|
|
}, env);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class NoteSide extends NoteComponent {
|
|
constructor(isRight) {
|
|
super();
|
|
this.isRight = isRight;
|
|
}
|
|
|
|
separation({agentIDs, mode, label}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
|
const width = (
|
|
env.textSizer.measure(config.labelAttrs, label).width +
|
|
config.padding.left +
|
|
config.padding.right +
|
|
config.margin.left +
|
|
config.margin.right
|
|
);
|
|
|
|
if(this.isRight) {
|
|
const info = env.agentInfos.get(right);
|
|
env.addSpacing(right, {
|
|
left: 0,
|
|
right: width + info.currentMaxRad,
|
|
});
|
|
} else {
|
|
const info = env.agentInfos.get(left);
|
|
env.addSpacing(left, {
|
|
left: width + info.currentMaxRad,
|
|
right: 0,
|
|
});
|
|
}
|
|
}
|
|
|
|
render({agentIDs, mode, label}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
|
if(this.isRight) {
|
|
const info = env.agentInfos.get(right);
|
|
const x0 = info.x + info.currentMaxRad + config.margin.left;
|
|
return this.renderNote({
|
|
label,
|
|
mode,
|
|
position: {x0},
|
|
}, env);
|
|
} else {
|
|
const info = env.agentInfos.get(left);
|
|
const x1 = info.x - info.currentMaxRad - config.margin.right;
|
|
return this.renderNote({
|
|
label,
|
|
mode,
|
|
position: {x1},
|
|
}, env);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class NoteBetween extends NoteComponent {
|
|
separation({agentIDs, mode, label}, env) {
|
|
const config = env.theme.getNote(mode);
|
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
|
const infoL = env.agentInfos.get(left);
|
|
const infoR = env.agentInfos.get(right);
|
|
|
|
env.addSeparation(
|
|
left,
|
|
right,
|
|
|
|
env.textSizer.measure(config.labelAttrs, label).width +
|
|
config.padding.left +
|
|
config.padding.right +
|
|
config.margin.left +
|
|
config.margin.right +
|
|
infoL.currentMaxRad +
|
|
infoR.currentMaxRad
|
|
);
|
|
}
|
|
|
|
render({agentIDs, mode, label}, env) {
|
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
|
const infoL = env.agentInfos.get(left);
|
|
const infoR = env.agentInfos.get(right);
|
|
const xMid = (
|
|
infoL.x + infoL.currentMaxRad +
|
|
infoR.x - infoR.currentMaxRad
|
|
) / 2;
|
|
|
|
return this.renderNote({
|
|
label,
|
|
mode,
|
|
position: {xMid},
|
|
}, env);
|
|
}
|
|
}
|
|
|
|
register('note over', new NoteOver());
|
|
register('note left', new NoteSide(false));
|
|
register('note right', new NoteSide(true));
|
|
register('note between', new NoteBetween());
|