@gravity-ui/graph
Version:
Modern graph editor component
108 lines (107 loc) • 4.41 kB
JavaScript
import intersects from "intersects";
import { BlockConnection } from "../../../components/canvas/connections/BlockConnection";
import { isPointInStroke } from "../../../components/canvas/connections/bezierHelpers";
import { curvePolyline } from "../../../utils/shapes/curvePolyline";
import { trangleArrowForVector } from "../../../utils/shapes/triangle";
const DEFAULT_FONT_SIZE = 14;
export class MultipointConnection extends BlockConnection {
constructor() {
super(...arguments);
this.labelsGeometry = [];
}
createPath() {
const points = this.getPoints();
if (!points.length) {
return super.createPath();
}
return curvePolyline(points, 10);
}
createArrowPath() {
const points = this.getPoints();
if (!points.length) {
return undefined;
}
const [start, end] = points.slice(points.length - 2);
return trangleArrowForVector(start, end, 16, 10);
}
styleArrow(ctx) {
ctx.fillStyle = this.state.selected
? this.context.colors.connection.selectedBackground
: this.context.colors.connection.background;
ctx.strokeStyle = ctx.fillStyle;
ctx.lineWidth = this.state.selected || this.state.hovered ? -1 : 1;
return { type: "both" };
}
getPoints() {
return this.connectedState.$state.value.points || [];
}
afterRender(ctx) {
this.renderLabelsText(ctx);
}
updatePoints() {
super.updatePoints();
return;
}
getBBox() {
const points = this.getPoints();
if (!points.length) {
return super.getBBox();
}
const x = [];
const y = [];
points.forEach((point) => {
x.push(point.x);
y.push(point.y);
});
return [Math.min(...x), Math.min(...y), Math.max(...x), Math.max(...y)];
}
onHitBox(shape) {
const THRESHOLD_LINE_HIT = this.context.constants.connection.THRESHOLD_LINE_HIT;
if (isPointInStroke(this.context.ctx, this.path2d, shape.x, shape.y, THRESHOLD_LINE_HIT * 2)) {
return true;
}
if (!this.labelsGeometry.length) {
return false;
}
const x = (shape.minX + shape.maxX) / 2;
const y = (shape.minY + shape.maxY) / 2;
const relativeThreshold = THRESHOLD_LINE_HIT / this.context.camera.getCameraScale();
return this.labelsGeometry.some((labelGeometry) => {
return intersects.boxBox(x - relativeThreshold / 2, y - relativeThreshold / 2, relativeThreshold, relativeThreshold, labelGeometry.x, labelGeometry.y, labelGeometry.width, labelGeometry.height);
});
}
renderLabelsText(ctx) {
const [labelInnerTopPadding, labelInnerRightPadding, labelInnerBottomPadding, labelInnerLeftPadding] = this.context.constants.connection.LABEL.INNER_PADDINGS;
const { labels } = this.connectedState.$state.value;
if (!labels || !labels.length) {
return;
}
this.labelsGeometry = [];
labels.forEach(({ x, y, text, height, width }) => {
if ([x, y, text].some((i) => i === undefined) || (x === 0 && y === 0)) {
return;
}
this.labelsGeometry.push({
x,
y,
width,
height,
});
ctx.fillStyle = this.context.colors.connectionLabel.text;
if (this.state.hovered)
ctx.fillStyle = this.context.colors.connectionLabel.hoverText;
if (this.state.selected)
ctx.fillStyle = this.context.colors.connectionLabel.selectedText;
ctx.textAlign = "left";
ctx.font = `${DEFAULT_FONT_SIZE}px sans-serif`;
ctx.fillText(text, x, y, width);
ctx.fillStyle = this.context.colors.connectionLabel.background;
if (this.state.hovered)
ctx.fillStyle = this.context.colors.connectionLabel.hoverBackground;
if (this.state.selected)
ctx.fillStyle = this.context.colors.connectionLabel.selectedBackground;
ctx.fillRect(x - labelInnerLeftPadding, y - labelInnerTopPadding, width + labelInnerLeftPadding + labelInnerRightPadding, height + labelInnerTopPadding + labelInnerBottomPadding);
});
return;
}
}