@recogito/recogito-connections
Version:
A plugin for drawing connections between annotations
155 lines (117 loc) • 3.76 kB
JavaScript
import { getBoxToBoxArrow } from 'perfect-arrows';
import EventEmitter from 'tiny-emitter';
import { ARROW_CONFIG } from './Config';
/**
* A compound SVG shape representing an existing network
* edge between two nodes.
*/
export default class SVGEdge extends EventEmitter {
constructor(edge, svg, config) {
super();
this.edge = edge;
this.config = config;
this.g = svg.group()
.attr('class', 'r6o-connections-edge has-events')
.attr('data-id', edge.id)
.click(() => this.emit('click', this.edge));
// Edge path
const svgEdge = this.g.group()
.attr('class', 'r6o-connections-edge-path');
svgEdge.path()
.attr('class', 'r6o-connections-edge-path-buffer');
svgEdge.path()
.attr('class', 'r6o-connections-edge-path-outer');
svgEdge.path()
.attr('class', 'r6o-connections-edge-path-inner');
// Edge base is a single circles
this.g.circle()
.radius(3)
.attr('class', 'r6o-connections-edge-base');
// Edge head is a triangle
this.g.polygon('0,-6 12,0, 0,6')
.attr('class', 'r6o-connections-edge-head');
if (config.showLabels) {
const label = this.g.group()
.attr('class', 'r6o-connections-edge-label')
.attr('style', 'display: none');
label.rect();
label.text().attr('y', 4.5);
this.updateLabel();
}
// Initial render
this.redraw();
}
updateLabel = () => {
const label = this.g.find('.r6o-connections-edge-label');
// Note that firstTag will be null if this as newly-dragged connection!
const firstTag = this.edge.bodies
.find(b => b.purpose === 'tagging')?.value;
if (label && firstTag) {
const text = label.find('text').text(firstTag);
setTimeout(() => {
const { width, height } = text[0][0].node.getBBox();
label.find('rect')
.attr('x', -5.5)
.attr('y', - Math.round(height / 2) - 1.5)
.attr('rx', 2)
.attr('ry', 2)
.attr('width', Math.round(width) + 10)
.attr('height', Math.round(height) + 4);
}, 1);
label.attr('style', null);
} else {
label.attr('style', 'display: none');
}
}
redraw = () => {
let start = this.edge.start.getAttachableRect();
const end = start ? this.edge.end.getAttachableRect(start) : null;
start = end ? this.edge.start.getAttachableRect(end) : null;
if (!start || !end) {
this.g.addClass('r6o-connections-hidden');
return;
}
this.g.removeClass('r6o-connections-hidden');
const [ sx, sy, cx, cy, ex, ey, ae, ] = getBoxToBoxArrow(
start.x,
start.y,
start.width,
start.height,
end.x,
end.y,
end.width || 0,
end.height || 0,
ARROW_CONFIG
);
const endAngleAsDegrees = ae * (180 / Math.PI);
// Base circle
this.g.find('circle')
.attr('cx', sx)
.attr('cy', sy);
// Inner and outer paths
this.g.find('path')
.attr('d', `M${sx},${sy} Q${cx},${cy} ${ex},${ey}`);
// Arrow head
this.g.find('polygon')
.attr('transform', `translate(${ex},${ey}) rotate(${endAngleAsDegrees})`);
// Label (if any)
const label = this.g.find('.r6o-connections-edge-label');
if (label)
label.attr('transform', `translate(${cx},${cy})`);
// Expose essential anchor points
this.startpoint = { x: sx, y: sy };
this.midpoint = { x: cx, y: cy };
this.endpoint = { x: ex, y: ey };
}
resetAttachment = () => {
this.edge.start.resetAttachment();
this.edge.end.resetAttachment();
}
remove = () =>
this.g.remove();
setData = bodies => {
this.edge.bodies = bodies;
if (this.config.showLabels)
this.updateLabel();
}
}