UNPKG

@maxgraph/core

Version:

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

970 lines (966 loc) 35.2 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 Rectangle_js_1 = __importDefault(require("../geometry/Rectangle.js")); const utils_js_1 = require("../../internal/utils.js"); const mathUtils_js_1 = require("../../util/mathUtils.js"); const Constants_js_1 = require("../../util/Constants.js"); const Point_js_1 = __importDefault(require("../geometry/Point.js")); const SvgCanvas2D_js_1 = __importDefault(require("../canvas/SvgCanvas2D.js")); const InternalEvent_js_1 = __importDefault(require("../event/InternalEvent.js")); const Client_js_1 = __importDefault(require("../../Client.js")); const config_js_1 = require("../../util/config.js"); /** * Base class for all shapes. * A shape in maxGraph is a separate implementation for SVG, VML and HTML. * Which implementation to use is controlled by the dialect property which * is assigned from within the CellRenderer when the shape is created. * The dialect must be assigned for a shape, and it does normally depend on * the browser and the configuration of the graph (see maxGraph rendering hint). * * For each supported shape in SVG and VML, a corresponding shape exists in * maxGraph, namely for text, image, rectangle, rhombus, ellipse and polyline. * The other shapes are a combination of these shapes (e.g. label and swimlane) * or they consist of one or more (filled) path objects (e.g. actor and cylinder). * The HTML implementation is optional but may be required for a HTML-only view * of the graph. * * ### Custom Shapes * To extend from this class, the basic code looks as follows. * In the special case where the custom shape consists only of one filled region * or one filled region and an additional stroke the mxActor and mxCylinder * should be subclassed, respectively. * ```javascript * function CustomShape() { } * * CustomShape.prototype = new mxShape(); * CustomShape.prototype.constructor = CustomShape; * ``` * To register a custom shape in an existing graph instance, one must register the * shape under a new name in the graph’s cell renderer as follows: * ```javascript * ShapeRegistry.add('customShape', CustomShape); * ``` * The second argument is the name of the constructor. * In order to use the shape you can refer to the given name above in a stylesheet. * For example, to change the shape for the default vertex style, the following code * is used: * ```javascript * const style = graph.getStylesheet().getDefaultVertexStyle(); * style.shape = 'customShape'; * ``` * * @category Shape */ class Shape { constructor(stencil = null) { /** * Switch to preserve image aspect. * @default false */ this.preserveImageAspect = false; this.overlay = null; this.indicator = null; this.indicatorShape = null; /** @see CellHighlight */ this.opacity = 100; this.isDashed = false; this.fill = Constants_js_1.NONE; this.gradient = Constants_js_1.NONE; this.gradientDirection = 'east'; this.fillOpacity = 100; this.strokeOpacity = 100; this.stroke = Constants_js_1.NONE; this.strokeWidth = 1; this.spacing = 0; this.startSize = 1; this.endSize = 1; this.startArrow = Constants_js_1.NONE; this.endArrow = Constants_js_1.NONE; this.direction = 'east'; this.flipH = false; this.flipV = false; this.isShadow = false; this.isRounded = false; this.rotation = 0; this.cursor = ''; this.verticalTextRotation = 0; this.oldGradients = {}; this.glass = false; /** * Holds the dialect in which the shape is to be painted. */ this.dialect = null; /** * Holds the scale in which the shape is being painted. */ this.scale = 1; /** * Rendering hint for configuring the canvas. */ this.antiAlias = true; /** * Minimum stroke width for SVG output. */ this.minSvgStrokeWidth = 1; /** * Holds the {@link Rectangle} that specifies the bounds of this shape. */ this.bounds = null; /** * Holds the array of <Point> that specify the points of this shape. */ this.points = []; /** * Optional reference to the corresponding <CellState>. */ this.state = null; /** * Optional reference to the style of the corresponding <CellState>. */ this.style = null; /** * Contains the bounding box of the shape, that is, the smallest rectangle * that includes all pixels of the shape. */ this.boundingBox = null; /** * Holds the {@link StencilShape} that defines the shape. */ this.stencil = null; /** * Event-tolerance for SVG strokes (in px). * This is only passed to the canvas in {@link createSvgCanvas} if {@link pointerEvents} is `true`. * @default 8 */ this.svgStrokeTolerance = 8; /** * Specifies if pointer events should be handled. Default is true. */ this.pointerEvents = true; this.originalPointerEvents = null; /** * Specifies if pointer events should be handled. Default is true. */ this.svgPointerEvents = 'all'; /** * Specifies if pointer events outside of shape should be handled. Default * is false. */ this.shapePointerEvents = false; /** * Specifies if pointer events outside of stencils should be handled. Default * is false. Set this to true for backwards compatibility with the 1.x branch. */ this.stencilPointerEvents = false; /** * Specifies if the shape should be drawn as an outline. This disables all * fill colors and can be used to disable other drawing states that should * not be painted for outlines. Default is false. This should be set before * calling <apply>. */ this.outline = false; /** * Specifies if the shape is visible. Default is true. */ this.visible = true; /** * Allows to use the SVG bounding box in SVG. Default is false for performance * reasons. */ this.useSvgBoundingBox = true; this.image = null; this.imageSrc = null; this.indicatorColor = Constants_js_1.NONE; this.indicatorStrokeColor = Constants_js_1.NONE; this.indicatorGradientColor = Constants_js_1.NONE; this.indicatorDirection = 'east'; this.indicatorImageSrc = null; // `stencil` is not null when instantiated directly, // but can be null when instantiated through a child class. if (stencil) { this.stencil = stencil; } // moved from init() this.node = this.create(); } /** * Initializes the shape by adding it into the given container if the node of the shape doesn't already have a parent. * * @param container DOM node that will contain the shape. */ init(container) { if (!this.node.parentNode) { container.appendChild(this.node); } } /** * Sets the styles to their default values. */ initStyles() { this.strokeWidth = 1; this.rotation = 0; this.opacity = 100; this.fillOpacity = 100; this.strokeOpacity = 100; this.flipH = false; this.flipV = false; } /** * Returns true if HTML is allowed for this shape. This implementation always returns `false`. */ isHtmlAllowed() { return false; } /** * Returns 0, or 0.5 if {@link strokeWidth} % 2 == 1. */ getSvgScreenOffset() { const sw = this.stencil && this.stencil.strokeWidthValue !== 'inherit' ? Number(this.stencil.strokeWidthValue) : (this.strokeWidth ?? 0); return (0, mathUtils_js_1.mod)(Math.max(1, Math.round(sw * this.scale)), 2) === 1 ? 0.5 : 0; } /** * Creates and returns the DOM node for the shape. * This implementation assumes that `maxGraph` produces SVG elements. */ create() { return document.createElementNS(Constants_js_1.NS_SVG, 'g'); } redraw() { this.updateBoundsFromPoints(); if (this.visible && this.checkBounds()) { this.node.style.visibility = 'visible'; this.clear(); this.redrawShape(); this.updateBoundingBox(); } else { this.node.style.visibility = 'hidden'; this.boundingBox = null; } } /** * Removes all child nodes and resets all CSS. */ clear() { while (this.node.lastChild) { this.node.removeChild(this.node.lastChild); } } /** * Updates the bounds based on the points. */ updateBoundsFromPoints() { const pts = this.points; if (pts.length > 0 && pts[0]) { this.bounds = new Rectangle_js_1.default(Math.round(pts[0].x), Math.round(pts[0].y), 1, 1); for (const pt of pts) { if (pt) { this.bounds.add(new Rectangle_js_1.default(Math.round(pt.x), Math.round(pt.y), 1, 1)); } } } } /** * Returns the {@link Rectangle} for the label bounds of this shape, based on the * given scaled and translated bounds of the shape. This method should not * change the rectangle in-place. This implementation returns the given rect. */ getLabelBounds(rect) { const d = this.style?.direction ?? 'east'; let bounds = rect.clone(); // Normalizes argument for getLabelMargins hook if (d !== 'south' && d !== 'north' && this.state && this.state.text && this.state.text.isPaintBoundsInverted()) { bounds = bounds.clone(); [bounds.width, bounds.height] = [bounds.height, bounds.width]; } let labelMargins = this.getLabelMargins(bounds); if (labelMargins) { labelMargins = labelMargins.clone(); let flipH = this.style?.flipH ?? false; let flipV = this.style?.flipV ?? false; // Handles special case for vertical labels if (this.state && this.state.text && this.state.text.isPaintBoundsInverted()) { const tmp = labelMargins.x; labelMargins.x = labelMargins.height; labelMargins.height = labelMargins.width; labelMargins.width = labelMargins.y; labelMargins.y = tmp; [flipH, flipV] = [flipV, flipH]; } return (0, mathUtils_js_1.getDirectedBounds)(rect, labelMargins, this.style, flipH, flipV); } return rect; } /** * Returns the scaled top, left, bottom and right margin to be used for * computing the label bounds as an {@link Rectangle}, where the bottom and right * margin are defined in the width and height of the rectangle, respectively. */ getLabelMargins(rect) { return null; } /** * Returns true if the bounds are not null and all of its variables are numeric. */ checkBounds() { return (!Number.isNaN(this.scale) && Number.isFinite(this.scale) && this.scale > 0 && this.bounds && !Number.isNaN(this.bounds.x) && !Number.isNaN(this.bounds.y) && !Number.isNaN(this.bounds.width) && !Number.isNaN(this.bounds.height) && this.bounds.width > 0 && this.bounds.height > 0); } /** * Updates the SVG or VML shape. */ redrawShape() { const canvas = this.createCanvas(); if (canvas) { // Specifies if events should be handled canvas.pointerEvents = this.pointerEvents; this.beforePaint(canvas); this.paint(canvas); this.afterPaint(canvas); if (this.node !== canvas.root && canvas.root) { // Forces parsing in IE8 standards mode - slow! avoid this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML); } this.destroyCanvas(canvas); } } /** * Creates a new canvas for drawing this shape. May return null. */ createCanvas() { const canvas = this.createSvgCanvas(); if (canvas && this.outline) { canvas.setStrokeWidth(this.strokeWidth); canvas.setStrokeColor(this.stroke); if (this.isDashed) { canvas.setDashed(this.isDashed); } canvas.setStrokeWidth = () => { return; }; canvas.setStrokeColor = () => { return; }; canvas.setFillColor = () => { return; }; canvas.setGradient = () => { return; }; canvas.setDashed = () => { return; }; canvas.text = () => { return; }; } return canvas; } /** * Creates and returns an {@link SvgCanvas2D} for rendering this shape. */ createSvgCanvas() { if (!this.node) return null; const canvas = new SvgCanvas2D_js_1.default(this.node, false); canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0; canvas.pointerEventsValue = this.svgPointerEvents; const off = this.getSvgScreenOffset(); if (off !== 0) { this.node.setAttribute('transform', `translate(${off},${off})`); } else { this.node.removeAttribute('transform'); } canvas.minStrokeWidth = this.minSvgStrokeWidth; if (!this.antiAlias) { // Rounds all numbers in the SVG output to integers canvas.format = (value) => { return Math.round(value); }; } return canvas; } /** * Destroys the given canvas which was used for drawing. This implementation * increments the reference counts on all shared gradients used in the canvas. */ destroyCanvas(canvas) { // Manages reference counts if (canvas instanceof SvgCanvas2D_js_1.default) { // Increments ref counts for (const key in canvas.gradients) { const gradient = canvas.gradients[key]; if (gradient) { gradient.mxRefCount = (gradient.mxRefCount || 0) + 1; } } this.releaseSvgGradients(this.oldGradients); this.oldGradients = canvas.gradients; } } /** * Invoked before paint is called. */ beforePaint(c) { return; } /** * Invokes after paint was called. */ afterPaint(c) { return; } /** * Generic rendering code. */ paint(c) { let strokeDrawn = false; if (c && this.outline) { const { stroke } = c; c.stroke = (...args) => { strokeDrawn = true; stroke.apply(c, args); }; const { fillAndStroke } = c; c.fillAndStroke = (...args) => { strokeDrawn = true; fillAndStroke.apply(c, args); }; } // Scale is passed-through to canvas const s = this.scale; const bounds = this.bounds; if (bounds) { let x = bounds.x / s; let y = bounds.y / s; let w = bounds.width / s; let h = bounds.height / s; if (this.isPaintBoundsInverted()) { const t = (w - h) / 2; x += t; y -= t; const tmp = w; w = h; h = tmp; } this.updateTransform(c, x, y, w, h); this.configureCanvas(c, x, y, w, h); // Adds background rectangle to capture events let bg = null; if ((!this.stencil && this.points.length === 0 && this.shapePointerEvents) || (this.stencil && this.stencilPointerEvents)) { const bb = this.createBoundingBox(); if (bb && this.node) { bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height); this.node.appendChild(bg); } } if (this.stencil) { this.stencil.drawShape(c, this, x, y, w, h); } else { // Stencils have separate strokewidth c.setStrokeWidth(this.strokeWidth); if (this.points.length > 0) { // Paints edge shape const pts = []; for (let i = 0; i < this.points.length; i += 1) { const p = this.points[i]; if (p) { pts.push(new Point_js_1.default(p.x / s, p.y / s)); } } this.paintEdgeShape(c, pts); } else { // Paints vertex shape this.paintVertexShape(c, x, y, w, h); } } if (bg && c.state && !(0, utils_js_1.isNullish)(c.state.transform)) { bg.setAttribute('transform', c.state.transform); } // Draws highlight rectangle if no stroke was used if (c && this.outline && !strokeDrawn) { c.rect(x, y, w, h); c.stroke(); } } } /** * Sets the state of the canvas for drawing the shape. */ configureCanvas(c, x, y, w, h) { let dash = null; if (this.style && this.style.dashPattern != null) { dash = this.style.dashPattern; } c.setAlpha(this.opacity / 100); c.setFillAlpha(this.fillOpacity / 100); c.setStrokeAlpha(this.strokeOpacity / 100); // Sets alpha, colors and gradients if (this.isShadow) { c.setShadow(this.isShadow); } // Dash pattern if (this.isDashed) { c.setDashed(this.isDashed, this.style?.fixDash ?? false); } if (dash) { c.setDashPattern(dash); } if (this.fill !== Constants_js_1.NONE && this.gradient !== Constants_js_1.NONE) { const b = this.getGradientBounds(c, x, y, w, h); c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection); } else { c.setFillColor(this.fill); } c.setStrokeColor(this.stroke); } /** * Returns the bounding box for the gradient box for this shape. */ getGradientBounds(c, x, y, w, h) { return new Rectangle_js_1.default(x, y, w, h); } /** * Sets the scale and rotation on the given canvas. */ updateTransform(c, x, y, w, h) { // NOTE: Currently, scale is implemented in state and canvas. This will // move to canvas in a later version, so that the states are unscaled // and untranslated and do not need an update after zooming or panning. c.scale(this.scale); c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2); } /** * Paints the vertex shape. */ paintVertexShape(c, x, y, w, h) { this.paintBackground(c, x, y, w, h); if (!this.outline || !this.style || !(this.style.backgroundOutline ?? false)) { c.setShadow(false); this.paintForeground(c, x, y, w, h); } } /** * Hook for subclassers. This implementation is empty. */ paintBackground(c, x, y, w, h) { return; } /** * Hook for subclassers. This implementation is empty. */ paintForeground(c, x, y, w, h) { return; } /** * Hook for subclassers. This implementation is empty. */ paintEdgeShape(c, pts) { return; } /** * Base arc size for the shape, taken from the style. * @since 0.21.0 */ getBaseArcSize() { return (this.style?.arcSize ?? Constants_js_1.LINE_ARCSIZE) / 2; } /** * Returns the arc size for the given dimension. */ getArcSize(w, h) { if (this.style?.absoluteArcSize) { return Math.min(this.getBaseArcSize(), Math.min(h, w) / 2); } const roundingFactor = (this.style?.arcSize ?? Constants_js_1.RECTANGLE_ROUNDING_FACTOR * 100) / 100; return Math.min(w, h) * roundingFactor; } /** * Paints the glass gradient effect. */ paintGlassEffect(c, x, y, w, h, arc) { const sw = Math.ceil((this.strokeWidth ?? 0) / 2); const size = 0.4; c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1); c.begin(); arc += 2 * sw; if (this.isRounded) { c.moveTo(x - sw + arc, y - sw); c.quadTo(x - sw, y - sw, x - sw, y - sw + arc); c.lineTo(x - sw, y + h * size); c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size); c.lineTo(x + w + sw, y - sw + arc); c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw); } else { c.moveTo(x - sw, y - sw); c.lineTo(x - sw, y + h * size); c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size); c.lineTo(x + w + sw, y - sw); } c.close(); c.fill(); } /** * Paints the given points with rounded corners. */ addPoints(c, pts, rounded = false, arcSize, close = false, exclude = [], initialMove = true) { if (pts.length > 0) { const pe = pts[pts.length - 1]; // Adds virtual waypoint in the center between start and end point if (close && rounded) { pts = pts.slice(); const p0 = pts[0]; const wp = new Point_js_1.default(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2); pts.splice(0, 0, wp); } let pt = pts[0]; let i = 1; // Draws the line segments if (initialMove) { c.moveTo(pt.x, pt.y); } else { c.lineTo(pt.x, pt.y); } while (i < (close ? pts.length : pts.length - 1)) { let tmp = pts[(0, mathUtils_js_1.mod)(i, pts.length)]; let dx = pt.x - tmp.x; let dy = pt.y - tmp.y; if (rounded && (dx !== 0 || dy !== 0) && exclude.indexOf(i - 1) < 0) { // Draws a line from the last point to the current // point with a spacing of size off the current point // into direction of the last point let dist = Math.sqrt(dx * dx + dy * dy); const nx1 = (dx * Math.min(arcSize, dist / 2)) / dist; const ny1 = (dy * Math.min(arcSize, dist / 2)) / dist; const x1 = tmp.x + nx1; const y1 = tmp.y + ny1; c.lineTo(x1, y1); // Draws a curve from the last point to the current // point with a spacing of size off the current point // into direction of the next point let next = pts[(0, mathUtils_js_1.mod)(i + 1, pts.length)]; // Uses next non-overlapping point while (i < pts.length - 2 && Math.round(next.x - tmp.x) === 0 && Math.round(next.y - tmp.y) === 0) { next = pts[(0, mathUtils_js_1.mod)(i + 2, pts.length)]; i++; } dx = next.x - tmp.x; dy = next.y - tmp.y; dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); const nx2 = (dx * Math.min(arcSize, dist / 2)) / dist; const ny2 = (dy * Math.min(arcSize, dist / 2)) / dist; const x2 = tmp.x + nx2; const y2 = tmp.y + ny2; c.quadTo(tmp.x, tmp.y, x2, y2); tmp = new Point_js_1.default(x2, y2); } else { c.lineTo(tmp.x, tmp.y); } pt = tmp; i += 1; } if (close) { c.close(); } else { c.lineTo(pe.x, pe.y); } } } /** * Resets all styles. */ resetStyles() { this.initStyles(); this.spacing = 0; this.fill = Constants_js_1.NONE; this.gradient = Constants_js_1.NONE; this.gradientDirection = 'east'; this.stroke = Constants_js_1.NONE; this.startSize = 1; this.endSize = 1; this.startArrow = Constants_js_1.NONE; this.endArrow = Constants_js_1.NONE; this.direction = 'east'; this.isShadow = false; this.isDashed = false; this.isRounded = false; this.glass = false; } /** * Applies the style of the given <CellState> to the shape. This * implementation assigns the following styles to local fields: * * - <'fillColor'> => fill * - <'gradientColor'> => gradient * - <'gradientDirection'> => gradientDirection * - <'opacity'> => opacity * - {@link Constants#STYLE_FILL_OPACITY} => fillOpacity * - {@link Constants#STYLE_STROKE_OPACITY} => strokeOpacity * - <'strokeColor'> => stroke * - <'strokeWidth'> => strokewidth * - <'shadow'> => isShadow * - <'dashed'> => isDashed * - <'spacing'> => spacing * - <'startSize'> => startSize * - <'endSize'> => endSize * - <'rounded'> => isRounded * - <'startArrow'> => startArrow * - <'endArrow'> => endArrow * - <'rotation'> => rotation * - <'direction'> => direction * - <'glass'> => glass * * This keeps a reference to the <style>. If you need to keep a reference to * the cell, you can override this method and store a local reference to * state.cell or the <CellState> itself. If <outline> should be true, make * sure to set it before calling this method. * * @param state <CellState> of the corresponding cell. */ apply(state) { this.state = state; this.style = state.style; if (this.style) { this.fill = this.style.fillColor ?? this.fill; this.gradient = this.style.gradientColor ?? this.gradient; this.gradientDirection = this.style.gradientDirection ?? this.gradientDirection; this.opacity = this.style.opacity ?? this.opacity; this.fillOpacity = this.style.fillOpacity ?? this.fillOpacity; this.strokeOpacity = this.style.strokeOpacity ?? this.strokeOpacity; this.stroke = this.style.strokeColor ?? this.stroke; this.strokeWidth = this.style.strokeWidth ?? this.strokeWidth; this.spacing = this.style.spacing ?? this.spacing; this.startSize = this.style.startSize ?? this.startSize; this.endSize = this.style.endSize ?? this.endSize; this.startArrow = this.style.startArrow ?? this.startArrow; this.endArrow = this.style.endArrow ?? this.endArrow; this.rotation = this.style.rotation ?? this.rotation; this.direction = this.style.direction ?? this.direction; this.flipH = !!this.style.flipH; this.flipV = !!this.style.flipV; if (this.direction === 'north' || this.direction === 'south') { const tmp = this.flipH; this.flipH = this.flipV; this.flipV = tmp; } this.isShadow = this.style.shadow ?? this.isShadow; this.isDashed = this.style.dashed ?? this.isDashed; this.isRounded = this.style.rounded ?? this.isRounded; this.glass = this.style.glass ?? this.glass; } } /** * Sets the cursor on the given shape. * * @param cursor The cursor to be used. */ setCursor(cursor) { this.cursor = cursor; this.node.style.cursor = cursor; } /** * Returns the current cursor. */ getCursor() { return this.cursor; } /** * Hook for subclassers. This implementation returns `false`. */ isRoundable(c, x, y, w, h) { return false; } /** * Updates the <boundingBox> for this shape using <createBoundingBox> and * <augmentBoundingBox> and stores the result in <boundingBox>. */ updateBoundingBox() { // Tries to get bounding box from SVG subsystem // LATER: Use getBoundingClientRect for fallback in VML if (this.useSvgBoundingBox && this.node.ownerSVGElement) { try { const b = this.node.getBBox(); if (b.width > 0 && b.height > 0) { this.boundingBox = new Rectangle_js_1.default(b.x, b.y, b.width, b.height); // Adds strokeWidth this.boundingBox.grow(((this.strokeWidth ?? 0) * this.scale) / 2); return; } } catch (e) { // fallback to code below } } if (this.bounds) { let bbox = this.createBoundingBox(); if (bbox) { this.augmentBoundingBox(bbox); const rot = this.getShapeRotation(); if (rot !== 0) { bbox = (0, mathUtils_js_1.getBoundingBox)(bbox, rot); } } this.boundingBox = bbox; } } /** * Returns a new rectangle that represents the bounding box of the bare shape * with no shadows or strokewidths. */ createBoundingBox() { if (!this.bounds) return null; const bb = this.bounds.clone(); if ((this.stencil && (this.direction === 'north' || this.direction === 'south')) || this.isPaintBoundsInverted()) { bb.rotate90(); } return bb; } /** * Augments the bounding box with the strokewidth and shadow offsets. */ augmentBoundingBox(bbox) { if (this.isShadow) { bbox.width += Math.ceil(config_js_1.StyleDefaultsConfig.shadowOffsetX * this.scale); bbox.height += Math.ceil(config_js_1.StyleDefaultsConfig.shadowOffsetX * this.scale); } // Adds strokeWidth bbox.grow(((this.strokeWidth ?? 0) * this.scale) / 2); } /** * Returns true if the bounds should be inverted. */ isPaintBoundsInverted() { // Stencil implements inversion via aspect return !this.stencil && (this.direction === 'north' || this.direction === 'south'); } /** * Returns the rotation from the style. */ getRotation() { return this.rotation ?? 0; } /** * Returns the rotation for the text label. */ getTextRotation() { let rot = this.getRotation(); if (!(this.style?.horizontal ?? true)) { rot += this.verticalTextRotation || -90; // WARNING WARNING!!!! =============================================================================================== } return rot; } /** * Returns the actual rotation of the shape. */ getShapeRotation() { let rot = this.getRotation(); if (this.direction === 'north') { rot += 270; } else if (this.direction === 'west') { rot += 180; } else if (this.direction === 'south') { rot += 90; } return rot; } /** * Adds a transparent rectangle that catches all events. */ createTransparentSvgRectangle(x, y, w, h) { const rect = document.createElementNS(Constants_js_1.NS_SVG, 'rect'); rect.setAttribute('x', String(x)); rect.setAttribute('y', String(y)); rect.setAttribute('width', String(w)); rect.setAttribute('height', String(h)); rect.setAttribute('fill', Constants_js_1.NONE); rect.setAttribute('stroke', Constants_js_1.NONE); rect.setAttribute('pointer-events', 'all'); return rect; } redrawHtmlShape() { return; } /** * Sets a transparent background CSS style to catch all events. * * Paints the line shape. */ setTransparentBackgroundImage(node) { node.style.backgroundImage = `url('${Client_js_1.default.imageBasePath}/transparent.gif')`; } /** * Paints the line shape. */ releaseSvgGradients(grads) { for (const key in grads) { const gradient = grads[key]; if (gradient) { gradient.mxRefCount = (gradient.mxRefCount || 0) - 1; if (gradient.mxRefCount === 0 && gradient.parentNode) { gradient.parentNode.removeChild(gradient); } } } } /** * Destroys the shape by removing it from the DOM and releasing the DOM * node associated with the shape using {@link Event#release}. */ destroy() { InternalEvent_js_1.default.release(this.node); if (this.node.parentNode) { this.node.parentNode.removeChild(this.node); } this.node.innerHTML = ''; // Decrements refCount and removes unused this.releaseSvgGradients(this.oldGradients); this.oldGradients = {}; } } exports.default = Shape;