UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

108 lines (107 loc) 4.41 kB
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; } }