@maxgraph/core
Version:
maxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.
254 lines (250 loc) • 10.5 kB
JavaScript
;
/*
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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SegmentConnector = void 0;
const Point_js_1 = __importDefault(require("../../geometry/Point.js"));
const mathUtils_js_1 = require("../../../util/mathUtils.js");
const shared_js_1 = require("./shared.js");
/**
* Implements an orthogonal edge style.
* Use {@link EdgeSegmentHandler} as an interactive handler for this style.
*
* This EdgeStyle is registered under `segmentEdgeStyle` in {@link EdgeStyleRegistry} when using {@link Graph} or calling {@link registerDefaultEdgeStyles}.
*
* **IMPORTANT**: When registering it manually in {@link EdgeStyleRegistry}, the following metadata must be used:
* - handlerKind: 'segment'
* - isOrthogonal: true
*
* @param state {@link CellState} that represents the edge to be updated.
* @param sourceScaled {@link CellState} that represents the source terminal.
* @param targetScaled {@link CellState} that represents the target terminal.
* @param controlHints List of relative control points.
* @param result Array of {@link Point} that represent the actual points of the edge.
*/
const SegmentConnector = (state, sourceScaled, targetScaled, controlHints, result) => {
// Creates array of all way- and terminal points
// TODO: Figure out what to do when there are nulls in `pts`!
const pts = (0, shared_js_1.scalePointArray)(state.absolutePoints, state.view.scale);
const source = (0, shared_js_1.scaleCellState)(sourceScaled, state.view.scale);
const target = (0, shared_js_1.scaleCellState)(targetScaled, state.view.scale);
const tol = 1;
// Whether the first segment outgoing from the source end is horizontal
let lastPushed = result.length > 0 ? result[0] : null;
let horizontal = true;
let hint = null;
// Adds waypoints only if outside of tolerance
function pushPoint(pt) {
pt.x = Math.round(pt.x * state.view.scale * 10) / 10;
pt.y = Math.round(pt.y * state.view.scale * 10) / 10;
if (lastPushed == null ||
Math.abs(lastPushed.x - pt.x) >= tol ||
Math.abs(lastPushed.y - pt.y) >= Math.max(1, state.view.scale)) {
result.push(pt);
lastPushed = pt;
}
return lastPushed;
}
// Adds the first point
let pt = pts[0];
if (pt == null && source != null) {
pt = new Point_js_1.default(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
}
else if (pt != null) {
pt = pt.clone();
}
const lastInx = pts.length - 1;
let pe = null;
// Adds the waypoints
if (controlHints != null && controlHints.length > 0) {
// Converts all hints and removes nulls
let hints = [];
for (let i = 0; i < controlHints.length; i += 1) {
const tmp = state.view.transformControlPoint(state, controlHints[i], true);
if (tmp != null) {
hints.push(tmp);
}
}
if (hints.length === 0) {
return;
}
// Aligns source and target hint to fixed points
if (pt != null && hints[0] != null) {
if (Math.abs(hints[0].x - pt.x) < tol) {
hints[0].x = pt.x;
}
if (Math.abs(hints[0].y - pt.y) < tol) {
hints[0].y = pt.y;
}
}
pe = pts[lastInx];
if (pe != null && hints[hints.length - 1] != null) {
if (Math.abs(hints[hints.length - 1].x - pe.x) < tol) {
hints[hints.length - 1].x = pe.x;
}
if (Math.abs(hints[hints.length - 1].y - pe.y) < tol) {
hints[hints.length - 1].y = pe.y;
}
}
hint = hints[0];
let currentTerm = source;
let currentPt = pts[0];
let hozChan = false;
let vertChan = false;
let currentHint = hint;
if (currentPt != null) {
currentTerm = null;
}
// Check for alignment with fixed points and with channels
// at source and target segments only
for (let i = 0; i < 2; i += 1) {
const fixedVertAlign = currentPt != null && currentPt.x === currentHint.x;
const fixedHozAlign = currentPt != null && currentPt.y === currentHint.y;
const inHozChan = currentTerm != null &&
currentHint.y >= currentTerm.y &&
currentHint.y <= currentTerm.y + currentTerm.height;
const inVertChan = currentTerm != null &&
currentHint.x >= currentTerm.x &&
currentHint.x <= currentTerm.x + currentTerm.width;
hozChan = fixedHozAlign || (currentPt == null && inHozChan);
vertChan = fixedVertAlign || (currentPt == null && inVertChan);
// If the current hint falls in both the hor and vert channels in the case
// of a floating port, or if the hint is exactly co-incident with a
// fixed point, ignore the source and try to work out the orientation
// from the target end
if (!(i == 0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))) {
if (currentPt != null &&
!fixedHozAlign &&
!fixedVertAlign &&
(inHozChan || inVertChan)) {
horizontal = !inHozChan;
break;
}
if (vertChan || hozChan) {
horizontal = hozChan;
if (i === 1) {
// Work back from target end
horizontal = hints.length % 2 === 0 ? hozChan : vertChan;
}
break;
}
}
currentTerm = target;
currentPt = pts[lastInx];
if (currentPt != null) {
currentTerm = null;
}
currentHint = hints[hints.length - 1];
if (fixedVertAlign && fixedHozAlign) {
hints = hints.slice(1);
}
}
if (horizontal &&
((pts[0] != null && pts[0].y !== hint.y) ||
(pts[0] == null &&
source != null &&
(hint.y < source.y || hint.y > source.y + source.height)))) {
pushPoint(new Point_js_1.default(pt.x, hint.y));
}
else if (!horizontal &&
((pts[0] != null && pts[0].x !== hint.x) ||
(pts[0] == null &&
source != null &&
(hint.x < source.x || hint.x > source.x + source.width)))) {
pushPoint(new Point_js_1.default(hint.x, pt.y));
}
if (horizontal) {
pt.y = hint.y;
}
else {
pt.x = hint.x;
}
for (let i = 0; i < hints.length; i += 1) {
horizontal = !horizontal;
hint = hints[i];
if (horizontal) {
pt.y = hint.y;
}
else {
pt.x = hint.x;
}
pushPoint(pt.clone());
}
}
else {
hint = pt;
// FIXME: First click in connect preview toggles orientation
horizontal = true;
}
// Adds the last point
pt = pts[lastInx];
if (pt == null && target != null) {
pt = new Point_js_1.default(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
}
if (pt != null) {
if (hint != null) {
if (horizontal &&
((pts[lastInx] != null && pts[lastInx].y !== hint.y) ||
(pts[lastInx] == null &&
target != null &&
(hint.y < target.y || hint.y > target.y + target.height)))) {
pushPoint(new Point_js_1.default(pt.x, hint.y));
}
else if (!horizontal &&
((pts[lastInx] != null && pts[lastInx].x !== hint.x) ||
(pts[lastInx] == null &&
target != null &&
(hint.x < target.x || hint.x > target.x + target.width)))) {
pushPoint(new Point_js_1.default(hint.x, pt.y));
}
}
}
// Removes bends inside the source terminal for floating ports
if (pts[0] == null && source != null) {
while (result.length > 1 &&
result[1] != null &&
(0, mathUtils_js_1.contains)(source, result[1].x, result[1].y)) {
result.splice(1, 1);
}
}
// Removes bends inside the target terminal
if (pts[lastInx] == null && target != null) {
while (result.length > 1 &&
result[result.length - 1] != null &&
(0, mathUtils_js_1.contains)(target, result[result.length - 1].x, result[result.length - 1].y)) {
result.splice(result.length - 1, 1);
}
}
// Removes last point if inside tolerance with end point
if (pe != null &&
result[result.length - 1] != null &&
Math.abs(pe.x - result[result.length - 1].x) <= tol &&
Math.abs(pe.y - result[result.length - 1].y) <= tol) {
result.splice(result.length - 1, 1);
// Lines up second last point in result with end point
if (result[result.length - 1] != null) {
if (Math.abs(result[result.length - 1].x - pe.x) < tol) {
result[result.length - 1].x = pe.x;
}
if (Math.abs(result[result.length - 1].y - pe.y) < tol) {
result[result.length - 1].y = pe.y;
}
}
}
};
exports.SegmentConnector = SegmentConnector;