UNPKG

@maxgraph/core

Version:

maxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

314 lines (310 loc) 12.9 kB
/* Copyright 2021-present The maxGraph project Contributors Copyright (c) 2006-2015, JGraph Ltd Copyright (c) 2006-2015, Gaudenz Alder Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import Point from '../geometry/Point.js'; import Rectangle from '../geometry/Rectangle.js'; import { contains } from '../../util/mathUtils.js'; import { setOpacity } from '../../util/styleUtils.js'; import ElbowEdgeHandler from './ElbowEdgeHandler.js'; import { EdgeHandlerConfig } from './config.js'; class EdgeSegmentHandler extends ElbowEdgeHandler { constructor(state) { super(state); this.points = []; } /** * Returns the current absolute points. */ getCurrentPoints() { let pts = this.state.absolutePoints; // Special case for straight edges where we add a virtual middle handle for moving the edge const tol = Math.max(1, this.graph.view.scale); if ((pts.length === 2 && pts[0] && pts[1]) || (pts.length === 3 && pts[0] && pts[1] && pts[2] && ((Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol) || (Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol)))) { const cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; const cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; pts = [pts[0], new Point(cx, cy), new Point(cx, cy), pts[pts.length - 1]]; } return pts; } /** * Updates the given preview state taking into account the state of the constraint handler. */ getPreviewPoints(point) { if (this.isSource || this.isTarget) { return super.getPreviewPoints(point); } const pts = this.getCurrentPoints(); let last = this.convertPoint(pts[0].clone(), false); point = this.convertPoint(point.clone(), false); let result = []; for (let i = 1; i < pts.length; i += 1) { const pt = this.convertPoint(pts[i].clone(), false); if (i === this.index) { if (Math.round(last.x - pt.x) === 0) { last.x = point.x; pt.x = point.x; } if (Math.round(last.y - pt.y) === 0) { last.y = point.y; pt.y = point.y; } } if (i < pts.length - 1) { result.push(pt); } last = pt; } // Replaces single point that intersects with source or target if (result.length === 1) { const source = this.state.getVisibleTerminalState(true); const target = this.state.getVisibleTerminalState(false); const scale = this.state.view.getScale(); const tr = this.state.view.getTranslate(); const x = result[0].x * scale + tr.x; const y = result[0].y * scale + tr.y; if ((source != null && contains(source, x, y)) || (target != null && contains(target, x, y))) { result = [point, point]; } } return result; } /** * Overridden to perform optimization of the edge style result. */ updatePreviewState(edge, point, terminalState, me) { super.updatePreviewState(edge, point, terminalState, me); // Checks and corrects preview by running edge style again if (!this.isSource && !this.isTarget) { point = this.convertPoint(point.clone(), false); const pts = edge.absolutePoints; let pt0 = pts[0]; let pt1 = pts[1]; let result = []; for (let i = 2; i < pts.length; i += 1) { const pt2 = pts[i]; // Merges adjacent segments only if more than 2 to allow for straight edges if ((Math.round(pt0.x - pt1.x) !== 0 || Math.round(pt1.x - pt2.x) !== 0) && (Math.round(pt0.y - pt1.y) !== 0 || Math.round(pt1.y - pt2.y) !== 0)) { result.push(this.convertPoint(pt1.clone(), false)); } pt0 = pt1; pt1 = pt2; } const source = this.state.getVisibleTerminalState(true); const target = this.state.getVisibleTerminalState(false); const rpts = this.state.absolutePoints; const end = pts[pts.length - 1]; // A straight line is represented by 3 handles if (result.length === 0 && pts[0] && end && (Math.round(pts[0].x - end.x) === 0 || Math.round(pts[0].y - end.y) === 0)) { result = [point, point]; } // Handles special case of transitions from straight vertical to routed else if (pts.length === 5 && result.length === 2 && source != null && target != null && rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) === 0) { const view = this.graph.getView(); const scale = view.getScale(); const tr = view.getTranslate(); let y0 = view.getRoutingCenterY(source) / scale - tr.y; // Use fixed connection point y-coordinate if one exists const sc = this.graph.getConnectionConstraint(edge, source, true); if (sc != null) { const pt = this.graph.getConnectionPoint(source, sc); if (pt != null) { this.convertPoint(pt, false); y0 = pt.y; } } let ye = view.getRoutingCenterY(target) / scale - tr.y; // Use fixed connection point y-coordinate if one exists const tc = this.graph.getConnectionConstraint(edge, target, false); if (tc) { const pt = this.graph.getConnectionPoint(target, tc); if (pt != null) { this.convertPoint(pt, false); ye = pt.y; } } result = [new Point(point.x, y0), new Point(point.x, ye)]; } this.points = result; // LATER: Check if points and result are different edge.view.updateFixedTerminalPoints(edge, source, target); edge.view.updatePoints(edge, this.points, source, target); edge.view.updateFloatingTerminalPoints(edge, source, target); } } /** * Overriden to merge edge segments. */ connect(edge, terminal, isSource, isClone, me) { const model = this.graph.getDataModel(); let geo = edge.getGeometry(); let result = null; // Merges adjacent edge segments if (geo != null && geo.points != null && geo.points.length > 0) { const pts = this.abspoints; let pt0 = pts[0]; let pt1 = pts[1]; result = []; for (let i = 2; i < pts.length; i += 1) { const pt2 = pts[i]; // Merges adjacent segments only if more than 2 to allow for straight edges if (pt0 && pt1 && pt2 && (Math.round(pt0.x - pt1.x) !== 0 || Math.round(pt1.x - pt2.x) !== 0) && (Math.round(pt0.y - pt1.y) !== 0 || Math.round(pt1.y - pt2.y) !== 0)) { result.push(this.convertPoint(pt1.clone(), false)); } pt0 = pt1; pt1 = pt2; } } this.graph.batchUpdate(() => { if (result != null) { geo = edge.getGeometry(); if (geo != null) { geo = geo.clone(); geo.points = result; model.setGeometry(edge, geo); } } edge = super.connect(edge, terminal, isSource, isClone, me); }); return edge; } /** * Returns no tooltips. */ getTooltipForNode(node) { return null; } /** * Adds custom bends for the center of each segment. */ start(x, y, index) { super.start(x, y, index); if (this.bends != null && this.bends[index] != null && !this.isSource && !this.isTarget) { setOpacity(this.bends[index].node, 100); } } /** * Adds custom bends for the center of each segment. */ createBends() { const bends = []; // Source let bend = this.createHandleShape(0); this.initBend(bend); bend.setCursor(EdgeHandlerConfig.cursorTerminal); bends.push(bend); const pts = this.getCurrentPoints(); // Waypoints (segment handles) if (this.graph.isCellBendable(this.state.cell)) { if (this.points == null) { this.points = []; } for (let i = 0; i < pts.length - 1; i += 1) { bend = this.createVirtualBend(); bends.push(bend); let horizontal = Math.round(pts[i].x - pts[i + 1].x) === 0; // Special case where dy is 0 as well if (Math.round(pts[i].y - pts[i + 1].y) === 0 && i < pts.length - 2) { horizontal = Math.round(pts[i].x - pts[i + 2].x) === 0; } bend.setCursor(horizontal ? 'col-resize' : 'row-resize'); this.points.push(new Point(0, 0)); } } // Target bend = this.createHandleShape(pts.length); this.initBend(bend); bend.setCursor(EdgeHandlerConfig.cursorTerminal); bends.push(bend); return bends; } /** * Overridden to invoke <refresh> before the redraw. */ redraw() { this.refresh(); super.redraw(); } /** * Updates the position of the custom bends. */ redrawInnerBends(p0, pe) { if (this.graph.isCellBendable(this.state.cell)) { const pts = this.getCurrentPoints(); if (pts != null && pts.length > 1) { let straight = false; // Puts handle in the center of straight edges if (pts.length === 4 && pts[0] && pts[1] && pts[2] && pts[3] && Math.round(pts[1].x - pts[2].x) === 0 && Math.round(pts[1].y - pts[2].y) === 0) { straight = true; if (Math.round(pts[0].y - pts[pts.length - 1].y) === 0) { const cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; pts[1] = new Point(cx, pts[1].y); pts[2] = new Point(cx, pts[2].y); } else { const cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; pts[1] = new Point(pts[1].x, cy); pts[2] = new Point(pts[2].x, cy); } } for (let i = 0; i < pts.length - 1; i += 1) { if (this.bends[i + 1] != null) { p0 = pts[i]; pe = pts[i + 1]; const pt = new Point(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); const b = this.bends[i + 1].bounds; this.bends[i + 1].bounds = new Rectangle(Math.floor(pt.x - b.width / 2), Math.floor(pt.y - b.height / 2), b.width, b.height); this.bends[i + 1].redraw(); if (this.manageLabelHandle) { this.checkLabelHandle(this.bends[i + 1].bounds); } } } if (straight) { setOpacity(this.bends[1].node, EdgeHandlerConfig.virtualBendOpacity); setOpacity(this.bends[3].node, EdgeHandlerConfig.virtualBendOpacity); } } } } } export default EdgeSegmentHandler;