UNPKG

@maxgraph/core

Version:

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

335 lines (331 loc) 13.3 kB
"use strict"; /* 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 }); const Constants_js_1 = require("../../util/Constants.js"); const Point_js_1 = __importDefault(require("../geometry/Point.js")); const PolylineShape_js_1 = __importDefault(require("../shape/edge/PolylineShape.js")); /** * Implements the alignment of selection cells to other cells in the graph. * * Constructor: mxGuide * * Constructs a new guide object. */ class Guide { constructor(graph, states) { /** * Contains the {@link CellState}s that are used for alignment. */ this.states = []; /** * Specifies if horizontal guides are enabled. Default is true. */ this.horizontal = true; /** * Specifies if vertical guides are enabled. Default is true. */ this.vertical = true; /** * Holds the {@link Shape} for the horizontal guide. */ this.guideX = null; /** * Holds the {@link Shape} for the vertical guide. */ this.guideY = null; /** * Specifies if rounded coordinates should be used. Default is false. */ this.rounded = false; /** * Default tolerance in px if grid is disabled. Default is 2. */ this.tolerance = 2; this.graph = graph; this.setStates(states); } /** * Sets the {@link CellState}s that should be used for alignment. */ setStates(states) { this.states = states; } /** * Returns true if the guide should be enabled for the given native event. This * implementation always returns true. */ isEnabledForEvent(evt) { return true; } /** * Returns the tolerance for the guides. Default value is gridSize / 2. */ getGuideTolerance(gridEnabled = false) { return gridEnabled && this.graph.isGridEnabled() ? this.graph.getGridSize() / 2 : this.tolerance; } /** * Returns the mxShape to be used for painting the respective guide. This * implementation returns a new, dashed and crisp {@link PolylineShape} using * {@link GUIDE_COLOR} and {@link GUIDE_STROKEWIDTH} as the format. * * @param horizontal Boolean that specifies which guide should be created. */ createGuideShape(horizontal = false) { // TODO: Should vertical guides be supported here?? ============================ const guide = new PolylineShape_js_1.default([], Constants_js_1.GUIDE_COLOR, Constants_js_1.GUIDE_STROKEWIDTH); guide.isDashed = true; return guide; } /** * Returns true if the given state should be ignored. * @param state */ isStateIgnored(state) { return false; } /** * Moves the <bounds> by the given {@link Point} and returnt the snapped point. */ move(bounds = null, delta, gridEnabled = false, clone = false) { if ((this.horizontal || this.vertical) && bounds) { const { scale } = this.graph.getView(); const tt = this.getGuideTolerance(gridEnabled) * scale; const b = bounds.clone(); b.x += delta.x; b.y += delta.y; let overrideX = false; let stateX = null; let valueX = null; let overrideY = false; let stateY = null; let valueY = null; let ttX = tt; let ttY = tt; const left = b.x; const right = b.x + b.width; const center = b.getCenterX(); const top = b.y; const bottom = b.y + b.height; const middle = b.getCenterY(); // Snaps the left, center and right to the given x-coordinate const snapX = (x, state, centerAlign) => { let override = false; if (centerAlign && Math.abs(x - center) < ttX) { delta.x = x - bounds.getCenterX(); ttX = Math.abs(x - center); override = true; } else if (!centerAlign) { if (Math.abs(x - left) < ttX) { delta.x = x - bounds.x; ttX = Math.abs(x - left); override = true; } else if (Math.abs(x - right) < ttX) { delta.x = x - bounds.x - bounds.width; ttX = Math.abs(x - right); override = true; } } if (override) { stateX = state; valueX = x; if (!this.guideX) { this.guideX = this.createGuideShape(true); // Makes sure to use SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideX.dialect = 'svg'; this.guideX.pointerEvents = false; this.guideX.init(this.graph.getView().getOverlayPane()); } } overrideX = overrideX || override; }; // Snaps the top, middle or bottom to the given y-coordinate const snapY = (y, state, centerAlign) => { let override = false; if (centerAlign && Math.abs(y - middle) < ttY) { delta.y = y - bounds.getCenterY(); ttY = Math.abs(y - middle); override = true; } else if (!centerAlign) { if (Math.abs(y - top) < ttY) { delta.y = y - bounds.y; ttY = Math.abs(y - top); override = true; } else if (Math.abs(y - bottom) < ttY) { delta.y = y - bounds.y - bounds.height; ttY = Math.abs(y - bottom); override = true; } } if (override) { stateY = state; valueY = y; if (!this.guideY) { this.guideY = this.createGuideShape(false); // Makes sure to use SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideY.dialect = 'svg'; this.guideY.pointerEvents = false; this.guideY.init(this.graph.getView().getOverlayPane()); } } overrideY = overrideY || override; }; for (let i = 0; i < this.states.length; i += 1) { const state = this.states[i]; if (state && !this.isStateIgnored(state)) { // Align x if (this.horizontal) { snapX(state.getCenterX(), state, true); snapX(state.x, state, false); snapX(state.x + state.width, state, false); // Aligns left and right of shape to center of page if (!state.cell) { snapX(state.getCenterX(), state, false); } } // Align y if (this.vertical) { snapY(state.getCenterY(), state, true); snapY(state.y, state, false); snapY(state.y + state.height, state, false); // Aligns left and right of shape to center of page if (!state.cell) { snapY(state.getCenterY(), state, false); } } } } // Moves cells to the raster if not aligned this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY); delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y); // Redraws the guides const c = this.graph.container; if (!overrideX && this.guideX) { this.guideX.node.style.visibility = 'hidden'; } else if (this.guideX) { let minY = null; let maxY = null; if (stateX) { minY = Math.min(bounds.y + delta.y - this.graph.getPanDy(), stateX.y); maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.getPanDy(), // @ts-ignore stateX! doesn't work for some reason... stateX.y + stateX.height); } if (minY !== null && maxY !== null) { this.guideX.points = [new Point_js_1.default(valueX, minY), new Point_js_1.default(valueX, maxY)]; } else { this.guideX.points = [ new Point_js_1.default(valueX, -this.graph.getPanDy()), new Point_js_1.default(valueX, c.scrollHeight - 3 - this.graph.getPanDy()), ]; } this.guideX.stroke = this.getGuideColor(stateX, true); this.guideX.node.style.visibility = 'visible'; this.guideX.redraw(); } if (!overrideY && this.guideY != null) { this.guideY.node.style.visibility = 'hidden'; } else if (this.guideY != null) { let minX = null; let maxX = null; if (stateY != null && bounds != null) { minX = Math.min(bounds.x + delta.x - this.graph.getPanDx(), stateY.x); maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.getPanDx(), // @ts-ignore stateY.x + stateY.width); } if (minX != null && maxX != null && valueY !== null) { this.guideY.points = [new Point_js_1.default(minX, valueY), new Point_js_1.default(maxX, valueY)]; } else if (valueY !== null) { this.guideY.points = [ new Point_js_1.default(-this.graph.getPanDx(), valueY), new Point_js_1.default(c.scrollWidth - 3 - this.graph.getPanDx(), valueY), ]; } this.guideY.stroke = this.getGuideColor(stateY, false); this.guideY.node.style.visibility = 'visible'; this.guideY.redraw(); } } return delta; } /** * Rounds to pixels for virtual states (eg. page guides) */ getDelta(bounds, stateX = null, dx, stateY = null, dy) { const s = this.graph.view.scale; if (this.rounded || (stateX != null && stateX.cell == null)) { dx = Math.round((bounds.x + dx) / s) * s - bounds.x; } if (this.rounded || (stateY != null && stateY.cell == null)) { dy = Math.round((bounds.y + dy) / s) * s - bounds.y; } return new Point_js_1.default(dx, dy); } /** * Hides all current guides. */ getGuideColor(state, horizontal) { return Constants_js_1.GUIDE_COLOR; } /** * Hides all current guides. */ hide() { this.setVisible(false); } /** * Shows or hides the current guides. */ setVisible(visible) { if (this.guideX) { this.guideX.node.style.visibility = visible ? 'visible' : 'hidden'; } if (this.guideY) { this.guideY.node.style.visibility = visible ? 'visible' : 'hidden'; } } /** * Destroys all resources that this object uses. */ destroy() { if (this.guideX) { this.guideX.destroy(); this.guideX = null; } if (this.guideY) { this.guideY.destroy(); this.guideY = null; } } } exports.default = Guide;