| /** |
| * Copyright (c) 2006-2015, JGraph Ltd |
| * Copyright (c) 2006-2015, Gaudenz Alder |
| */ |
| /** |
| * Class: mxShape |
| * |
| * Base class for all shapes. A shape in mxGraph 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 <mxCellRenderer> |
| * when the shape is created. The dialect must be assigned |
| * for a shape, and it does normally depend on the browser and |
| * the confiuration of the graph (see <mxGraph> rendering hint). |
| * |
| * For each supported shape in SVG and VML, a corresponding |
| * shape exists in mxGraph, namely for text, image, rectangle, |
| * rhombus, ellipse and polyline. The other shapes are a |
| * combination of these shapes (eg. label and swimlane) |
| * or they consist of one or more (filled) path objects |
| * (eg. 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. |
| * |
| * (code) |
| * function CustomShape() { } |
| * |
| * CustomShape.prototype = new mxShape(); |
| * CustomShape.prototype.constructor = CustomShape; |
| * (end) |
| * |
| * 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: |
| * |
| * (code) |
| * mxCellRenderer.registerShape('customShape', CustomShape); |
| * (end) |
| * |
| * 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: |
| * |
| * (code) |
| * var style = graph.getStylesheet().getDefaultVertexStyle(); |
| * style[mxConstants.STYLE_SHAPE] = 'customShape'; |
| * (end) |
| * |
| * Constructor: mxShape |
| * |
| * Constructs a new shape. |
| */ |
| function mxShape(stencil) |
| { |
| this.stencil = stencil; |
| this.initStyles(); |
| }; |
| |
| /** |
| * Variable: dialect |
| * |
| * Holds the dialect in which the shape is to be painted. |
| * This can be one of the DIALECT constants in <mxConstants>. |
| */ |
| mxShape.prototype.dialect = null; |
| |
| /** |
| * Variable: scale |
| * |
| * Holds the scale in which the shape is being painted. |
| */ |
| mxShape.prototype.scale = 1; |
| |
| /** |
| * Variable: antiAlias |
| * |
| * Rendering hint for configuring the canvas. |
| */ |
| mxShape.prototype.antiAlias = true; |
| |
| /** |
| * Variable: bounds |
| * |
| * Holds the <mxRectangle> that specifies the bounds of this shape. |
| */ |
| mxShape.prototype.bounds = null; |
| |
| /** |
| * Variable: points |
| * |
| * Holds the array of <mxPoints> that specify the points of this shape. |
| */ |
| mxShape.prototype.points = null; |
| |
| /** |
| * Variable: node |
| * |
| * Holds the outermost DOM node that represents this shape. |
| */ |
| mxShape.prototype.node = null; |
| |
| /** |
| * Variable: state |
| * |
| * Optional reference to the corresponding <mxCellState>. |
| */ |
| mxShape.prototype.state = null; |
| |
| /** |
| * Variable: style |
| * |
| * Optional reference to the style of the corresponding <mxCellState>. |
| */ |
| mxShape.prototype.style = null; |
| |
| /** |
| * Variable: boundingBox |
| * |
| * Contains the bounding box of the shape, that is, the smallest rectangle |
| * that includes all pixels of the shape. |
| */ |
| mxShape.prototype.boundingBox = null; |
| |
| /** |
| * Variable: stencil |
| * |
| * Holds the <mxStencil> that defines the shape. |
| */ |
| mxShape.prototype.stencil = null; |
| |
| /** |
| * Variable: svgStrokeTolerance |
| * |
| * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed |
| * to the canvas in <createSvgCanvas> if <pointerEvents> is true. |
| */ |
| mxShape.prototype.svgStrokeTolerance = 8; |
| |
| /** |
| * Variable: pointerEvents |
| * |
| * Specifies if pointer events should be handled. Default is true. |
| */ |
| mxShape.prototype.pointerEvents = true; |
| |
| /** |
| * Variable: svgPointerEvents |
| * |
| * Specifies if pointer events should be handled. Default is true. |
| */ |
| mxShape.prototype.svgPointerEvents = 'all'; |
| |
| /** |
| * Variable: shapePointerEvents |
| * |
| * Specifies if pointer events outside of shape should be handled. Default |
| * is false. |
| */ |
| mxShape.prototype.shapePointerEvents = false; |
| |
| /** |
| * Variable: stencilPointerEvents |
| * |
| * 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. |
| */ |
| mxShape.prototype.stencilPointerEvents = false; |
| |
| /** |
| * Variable: vmlScale |
| * |
| * Scale for improving the precision of VML rendering. Default is 1. |
| */ |
| mxShape.prototype.vmlScale = 1; |
| |
| /** |
| * Variable: outline |
| * |
| * 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>. |
| */ |
| mxShape.prototype.outline = false; |
| |
| /** |
| * Variable: visible |
| * |
| * Specifies if the shape is visible. Default is true. |
| */ |
| mxShape.prototype.visible = true; |
| |
| /** |
| * Variable: useSvgBoundingBox |
| * |
| * Allows to use the SVG bounding box in SVG. Default is false for performance |
| * reasons. |
| */ |
| mxShape.prototype.useSvgBoundingBox = false; |
| |
| /** |
| * Function: init |
| * |
| * Initializes the shape by creaing the DOM node using <create> |
| * and adding it into the given container. |
| * |
| * Parameters: |
| * |
| * container - DOM node that will contain the shape. |
| */ |
| mxShape.prototype.init = function(container) |
| { |
| if (this.node == null) |
| { |
| this.node = this.create(container); |
| |
| if (container != null) |
| { |
| container.appendChild(this.node); |
| } |
| } |
| }; |
| |
| /** |
| * Function: initStyles |
| * |
| * Sets the styles to their default values. |
| */ |
| mxShape.prototype.initStyles = function(container) |
| { |
| this.strokewidth = 1; |
| this.rotation = 0; |
| this.opacity = 100; |
| this.fillOpacity = 100; |
| this.strokeOpacity = 100; |
| this.flipH = false; |
| this.flipV = false; |
| }; |
| |
| /** |
| * Function: isParseVml |
| * |
| * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This |
| * is only needed in IE8 and only if the shape contains VML markup. This method |
| * returns true. |
| */ |
| mxShape.prototype.isParseVml = function() |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: isHtmlAllowed |
| * |
| * Returns true if HTML is allowed for this shape. This implementation always |
| * returns false. |
| */ |
| mxShape.prototype.isHtmlAllowed = function() |
| { |
| return false; |
| }; |
| |
| /** |
| * Function: getSvgScreenOffset |
| * |
| * Returns 0, or 0.5 if <strokewidth> % 2 == 1. |
| */ |
| mxShape.prototype.getSvgScreenOffset = function() |
| { |
| var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth; |
| |
| return (mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1) ? 0.5 : 0; |
| }; |
| |
| /** |
| * Function: create |
| * |
| * Creates and returns the DOM node(s) for the shape in |
| * the given container. This implementation invokes |
| * <createSvg>, <createHtml> or <createVml> depending |
| * on the <dialect> and style settings. |
| * |
| * Parameters: |
| * |
| * container - DOM node that will contain the shape. |
| */ |
| mxShape.prototype.create = function(container) |
| { |
| var node = null; |
| |
| if (container != null && container.ownerSVGElement != null) |
| { |
| node = this.createSvg(container); |
| } |
| else if (document.documentMode == 8 || !mxClient.IS_VML || |
| (this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed())) |
| { |
| node = this.createHtml(container); |
| } |
| else |
| { |
| node = this.createVml(container); |
| } |
| |
| return node; |
| }; |
| |
| /** |
| * Function: createSvg |
| * |
| * Creates and returns the SVG node(s) to represent this shape. |
| */ |
| mxShape.prototype.createSvg = function() |
| { |
| return document.createElementNS(mxConstants.NS_SVG, 'g'); |
| }; |
| |
| /** |
| * Function: createVml |
| * |
| * Creates and returns the VML node to represent this shape. |
| */ |
| mxShape.prototype.createVml = function() |
| { |
| var node = document.createElement(mxClient.VML_PREFIX + ':group'); |
| node.style.position = 'absolute'; |
| |
| return node; |
| }; |
| |
| /** |
| * Function: createHtml |
| * |
| * Creates and returns the HTML DOM node(s) to represent |
| * this shape. This implementation falls back to <createVml> |
| * so that the HTML creation is optional. |
| */ |
| mxShape.prototype.createHtml = function() |
| { |
| var node = document.createElement('div'); |
| node.style.position = 'absolute'; |
| |
| return node; |
| }; |
| |
| /** |
| * Function: reconfigure |
| * |
| * Reconfigures this shape. This will update the colors etc in |
| * addition to the bounds or points. |
| */ |
| mxShape.prototype.reconfigure = function() |
| { |
| this.redraw(); |
| }; |
| |
| /** |
| * Function: redraw |
| * |
| * Creates and returns the SVG node(s) to represent this shape. |
| */ |
| mxShape.prototype.redraw = function() |
| { |
| this.updateBoundsFromPoints(); |
| |
| if (this.visible && this.checkBounds()) |
| { |
| this.node.style.visibility = 'visible'; |
| this.clear(); |
| |
| if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML)) |
| { |
| this.redrawHtmlShape(); |
| } |
| else |
| { |
| this.redrawShape(); |
| } |
| |
| this.updateBoundingBox(); |
| } |
| else |
| { |
| this.node.style.visibility = 'hidden'; |
| this.boundingBox = null; |
| } |
| }; |
| |
| /** |
| * Function: clear |
| * |
| * Removes all child nodes and resets all CSS. |
| */ |
| mxShape.prototype.clear = function() |
| { |
| if (this.node.ownerSVGElement != null) |
| { |
| while (this.node.lastChild != null) |
| { |
| this.node.removeChild(this.node.lastChild); |
| } |
| } |
| else |
| { |
| this.node.style.cssText = 'position:absolute;' + ((this.cursor != null) ? |
| ('cursor:' + this.cursor + ';') : ''); |
| this.node.innerHTML = ''; |
| } |
| }; |
| |
| /** |
| * Function: updateBoundsFromPoints |
| * |
| * Updates the bounds based on the points. |
| */ |
| mxShape.prototype.updateBoundsFromPoints = function() |
| { |
| var pts = this.points; |
| |
| if (pts != null && pts.length > 0 && pts[0] != null) |
| { |
| this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1); |
| |
| for (var i = 1; i < this.points.length; i++) |
| { |
| if (pts[i] != null) |
| { |
| this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1)); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: getLabelBounds |
| * |
| * Returns the <mxRectangle> 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. |
| */ |
| mxShape.prototype.getLabelBounds = function(rect) |
| { |
| var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST); |
| var bounds = rect; |
| |
| // Normalizes argument for getLabelMargins hook |
| if (d != mxConstants.DIRECTION_SOUTH && d != mxConstants.DIRECTION_NORTH && |
| this.state != null && this.state.text != null && |
| this.state.text.isPaintBoundsInverted()) |
| { |
| bounds = bounds.clone(); |
| var tmp = bounds.width; |
| bounds.width = bounds.height; |
| bounds.height = tmp; |
| } |
| |
| var m = this.getLabelMargins(bounds); |
| |
| if (m != null) |
| { |
| var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1'; |
| var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1'; |
| |
| // Handles special case for vertical labels |
| if (this.state != null && this.state.text != null && |
| this.state.text.isPaintBoundsInverted()) |
| { |
| var tmp = m.x; |
| m.x = m.height; |
| m.height = m.width; |
| m.width = m.y; |
| m.y = tmp; |
| |
| tmp = flipH; |
| flipH = flipV; |
| flipV = tmp; |
| } |
| |
| return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV); |
| } |
| |
| return rect; |
| }; |
| |
| /** |
| * Function: getLabelMargins |
| * |
| * Returns the scaled top, left, bottom and right margin to be used for |
| * computing the label bounds as an <mxRectangle>, where the bottom and right |
| * margin are defined in the width and height of the rectangle, respectively. |
| */ |
| mxShape.prototype.getLabelMargins= function(rect) |
| { |
| return null; |
| }; |
| |
| /** |
| * Function: checkBounds |
| * |
| * Returns true if the bounds are not null and all of its variables are numeric. |
| */ |
| mxShape.prototype.checkBounds = function() |
| { |
| return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 && |
| this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) && |
| !isNaN(this.bounds.width) && !isNaN(this.bounds.height) && |
| this.bounds.width > 0 && this.bounds.height > 0); |
| }; |
| |
| /** |
| * Function: createVmlGroup |
| * |
| * Returns the temporary element used for rendering in IE8 standards mode. |
| */ |
| mxShape.prototype.createVmlGroup = function() |
| { |
| var node = document.createElement(mxClient.VML_PREFIX + ':group'); |
| node.style.position = 'absolute'; |
| node.style.width = this.node.style.width; |
| node.style.height = this.node.style.height; |
| |
| return node; |
| }; |
| |
| /** |
| * Function: redrawShape |
| * |
| * Updates the SVG or VML shape. |
| */ |
| mxShape.prototype.redrawShape = function() |
| { |
| var canvas = this.createCanvas(); |
| |
| if (canvas != null) |
| { |
| // Specifies if events should be handled |
| canvas.pointerEvents = this.pointerEvents; |
| |
| this.paint(canvas); |
| |
| if (this.node != canvas.root) |
| { |
| // Forces parsing in IE8 standards mode - slow! avoid |
| this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML); |
| } |
| |
| if (this.node.nodeName == 'DIV' && document.documentMode == 8) |
| { |
| // Makes DIV transparent to events for IE8 in IE8 standards |
| // mode (Note: Does not work for IE9 in IE8 standards mode |
| // and not for IE11 in enterprise mode) |
| this.node.style.filter = ''; |
| |
| // Adds event transparency in IE8 standards |
| mxUtils.addTransparentBackgroundFilter(this.node); |
| } |
| |
| this.destroyCanvas(canvas); |
| } |
| }; |
| |
| /** |
| * Function: createCanvas |
| * |
| * Creates a new canvas for drawing this shape. May return null. |
| */ |
| mxShape.prototype.createCanvas = function() |
| { |
| var canvas = null; |
| |
| // LATER: Check if reusing existing DOM nodes improves performance |
| if (this.node.ownerSVGElement != null) |
| { |
| canvas = this.createSvgCanvas(); |
| } |
| else if (mxClient.IS_VML) |
| { |
| this.updateVmlContainer(); |
| canvas = this.createVmlCanvas(); |
| } |
| |
| if (canvas != null && this.outline) |
| { |
| canvas.setStrokeWidth(this.strokewidth); |
| canvas.setStrokeColor(this.stroke); |
| |
| if (this.isDashed != null) |
| { |
| canvas.setDashed(this.isDashed); |
| } |
| |
| canvas.setStrokeWidth = function() {}; |
| canvas.setStrokeColor = function() {}; |
| canvas.setFillColor = function() {}; |
| canvas.setGradient = function() {}; |
| canvas.setDashed = function() {}; |
| } |
| |
| return canvas; |
| }; |
| |
| /** |
| * Function: createSvgCanvas |
| * |
| * Creates and returns an <mxSvgCanvas2D> for rendering this shape. |
| */ |
| mxShape.prototype.createSvgCanvas = function() |
| { |
| var canvas = new mxSvgCanvas2D(this.node, false); |
| canvas.strokeTolerance = (this.pointerEvents) ? this.svgStrokeTolerance : 0; |
| canvas.pointerEventsValue = this.svgPointerEvents; |
| canvas.blockImagePointerEvents = mxClient.IS_FF; |
| var off = this.getSvgScreenOffset(); |
| |
| if (off != 0) |
| { |
| this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')'); |
| } |
| else |
| { |
| this.node.removeAttribute('transform'); |
| } |
| |
| if (!this.antiAlias) |
| { |
| // Rounds all numbers in the SVG output to integers |
| canvas.format = function(value) |
| { |
| return Math.round(parseFloat(value)); |
| }; |
| } |
| |
| return canvas; |
| }; |
| |
| /** |
| * Function: createVmlCanvas |
| * |
| * Creates and returns an <mxVmlCanvas2D> for rendering this shape. |
| */ |
| mxShape.prototype.createVmlCanvas = function() |
| { |
| // Workaround for VML rendering bug in IE8 standards mode |
| var node = (document.documentMode == 8 && this.isParseVml()) ? this.createVmlGroup() : this.node; |
| var canvas = new mxVmlCanvas2D(node, false); |
| |
| if (node.tagUrn != '') |
| { |
| var w = Math.max(1, Math.round(this.bounds.width)); |
| var h = Math.max(1, Math.round(this.bounds.height)); |
| node.coordsize = (w * this.vmlScale) + ',' + (h * this.vmlScale); |
| canvas.scale(this.vmlScale); |
| canvas.vmlScale = this.vmlScale; |
| } |
| |
| // Painting relative to top, left shape corner |
| var s = this.scale; |
| canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s)); |
| |
| return canvas; |
| }; |
| |
| /** |
| * Function: updateVmlContainer |
| * |
| * Updates the bounds of the VML container. |
| */ |
| mxShape.prototype.updateVmlContainer = function() |
| { |
| this.node.style.left = Math.round(this.bounds.x) + 'px'; |
| this.node.style.top = Math.round(this.bounds.y) + 'px'; |
| var w = Math.max(1, Math.round(this.bounds.width)); |
| var h = Math.max(1, Math.round(this.bounds.height)); |
| this.node.style.width = w + 'px'; |
| this.node.style.height = h + 'px'; |
| this.node.style.overflow = 'visible'; |
| }; |
| |
| /** |
| * Function: redrawHtml |
| * |
| * Allow optimization by replacing VML with HTML. |
| */ |
| mxShape.prototype.redrawHtmlShape = function() |
| { |
| // LATER: Refactor methods |
| this.updateHtmlBounds(this.node); |
| this.updateHtmlFilters(this.node); |
| this.updateHtmlColors(this.node); |
| }; |
| |
| /** |
| * Function: updateHtmlFilters |
| * |
| * Allow optimization by replacing VML with HTML. |
| */ |
| mxShape.prototype.updateHtmlFilters = function(node) |
| { |
| var f = ''; |
| |
| if (this.opacity < 100) |
| { |
| f += 'alpha(opacity=' + (this.opacity) + ')'; |
| } |
| |
| if (this.isShadow) |
| { |
| // FIXME: Cannot implement shadow transparency with filter |
| f += 'progid:DXImageTransform.Microsoft.dropShadow (' + |
| 'OffX=\'' + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + '\', ' + |
| 'OffY=\'' + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + '\', ' + |
| 'Color=\'' + mxConstants.VML_SHADOWCOLOR + '\')'; |
| } |
| |
| if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) |
| { |
| var start = this.fill; |
| var end = this.gradient; |
| var type = '0'; |
| |
| var lookup = {east:0,south:1,west:2,north:3}; |
| var dir = (this.direction != null) ? lookup[this.direction] : 0; |
| |
| if (this.gradientDirection != null) |
| { |
| dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4); |
| } |
| |
| if (dir == 1) |
| { |
| type = '1'; |
| var tmp = start; |
| start = end; |
| end = tmp; |
| } |
| else if (dir == 2) |
| { |
| var tmp = start; |
| start = end; |
| end = tmp; |
| } |
| else if (dir == 3) |
| { |
| type = '1'; |
| } |
| |
| f += 'progid:DXImageTransform.Microsoft.gradient(' + |
| 'startColorStr=\'' + start + '\', endColorStr=\'' + end + |
| '\', gradientType=\'' + type + '\')'; |
| } |
| |
| node.style.filter = f; |
| }; |
| |
| /** |
| * Function: mixedModeHtml |
| * |
| * Allow optimization by replacing VML with HTML. |
| */ |
| mxShape.prototype.updateHtmlColors = function(node) |
| { |
| var color = this.stroke; |
| |
| if (color != null && color != mxConstants.NONE) |
| { |
| node.style.borderColor = color; |
| |
| if (this.isDashed) |
| { |
| node.style.borderStyle = 'dashed'; |
| } |
| else if (this.strokewidth > 0) |
| { |
| node.style.borderStyle = 'solid'; |
| } |
| |
| node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px'; |
| } |
| else |
| { |
| node.style.borderWidth = '0px'; |
| } |
| |
| color = (this.outline) ? null : this.fill; |
| |
| if (color != null && color != mxConstants.NONE) |
| { |
| node.style.backgroundColor = color; |
| node.style.backgroundImage = 'none'; |
| } |
| else if (this.pointerEvents) |
| { |
| node.style.backgroundColor = 'transparent'; |
| } |
| else if (document.documentMode == 8) |
| { |
| mxUtils.addTransparentBackgroundFilter(node); |
| } |
| else |
| { |
| this.setTransparentBackgroundImage(node); |
| } |
| }; |
| |
| /** |
| * Function: mixedModeHtml |
| * |
| * Allow optimization by replacing VML with HTML. |
| */ |
| mxShape.prototype.updateHtmlBounds = function(node) |
| { |
| var sw = (document.documentMode >= 9) ? 0 : Math.ceil(this.strokewidth * this.scale); |
| node.style.borderWidth = Math.max(1, sw) + 'px'; |
| node.style.overflow = 'hidden'; |
| |
| node.style.left = Math.round(this.bounds.x - sw / 2) + 'px'; |
| node.style.top = Math.round(this.bounds.y - sw / 2) + 'px'; |
| |
| if (document.compatMode == 'CSS1Compat') |
| { |
| sw = -sw; |
| } |
| |
| node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px'; |
| node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px'; |
| }; |
| |
| /** |
| * Function: destroyCanvas |
| * |
| * Destroys the given canvas which was used for drawing. This implementation |
| * increments the reference counts on all shared gradients used in the canvas. |
| */ |
| mxShape.prototype.destroyCanvas = function(canvas) |
| { |
| // Manages reference counts |
| if (canvas instanceof mxSvgCanvas2D) |
| { |
| // Increments ref counts |
| for (var key in canvas.gradients) |
| { |
| var gradient = canvas.gradients[key]; |
| |
| if (gradient != null) |
| { |
| gradient.mxRefCount = (gradient.mxRefCount || 0) + 1; |
| } |
| } |
| |
| this.releaseSvgGradients(this.oldGradients); |
| this.oldGradients = canvas.gradients; |
| } |
| }; |
| |
| /** |
| * Function: paint |
| * |
| * Generic rendering code. |
| */ |
| mxShape.prototype.paint = function(c) |
| { |
| // Scale is passed-through to canvas |
| var s = this.scale; |
| var x = this.bounds.x / s; |
| var y = this.bounds.y / s; |
| var w = this.bounds.width / s; |
| var h = this.bounds.height / s; |
| |
| if (this.isPaintBoundsInverted()) |
| { |
| var t = (w - h) / 2; |
| x += t; |
| y -= t; |
| var 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 |
| var bg = null; |
| |
| if ((this.stencil == null && this.points == null && this.shapePointerEvents) || |
| (this.stencil != null && this.stencilPointerEvents)) |
| { |
| var bb = this.createBoundingBox(); |
| |
| if (this.dialect == mxConstants.DIALECT_SVG) |
| { |
| bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height); |
| this.node.appendChild(bg); |
| } |
| else |
| { |
| var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s); |
| rect.appendChild(c.createTransparentFill()); |
| rect.stroked = 'false'; |
| c.root.appendChild(rect); |
| } |
| } |
| |
| if (this.stencil != null) |
| { |
| this.stencil.drawShape(c, this, x, y, w, h); |
| } |
| else |
| { |
| // Stencils have separate strokewidth |
| c.setStrokeWidth(this.strokewidth); |
| |
| if (this.points != null) |
| { |
| // Paints edge shape |
| var pts = []; |
| |
| for (var i = 0; i < this.points.length; i++) |
| { |
| if (this.points[i] != null) |
| { |
| pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s)); |
| } |
| } |
| |
| this.paintEdgeShape(c, pts); |
| } |
| else |
| { |
| // Paints vertex shape |
| this.paintVertexShape(c, x, y, w, h); |
| } |
| } |
| |
| if (bg != null && c.state != null && c.state.transform != null) |
| { |
| bg.setAttribute('transform', c.state.transform); |
| } |
| }; |
| |
| /** |
| * Function: configureCanvas |
| * |
| * Sets the state of the canvas for drawing the shape. |
| */ |
| mxShape.prototype.configureCanvas = function(c, x, y, w, h) |
| { |
| var dash = null; |
| |
| if (this.style != 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 != null) |
| { |
| c.setShadow(this.isShadow); |
| } |
| |
| // Dash pattern |
| if (this.isDashed != null) |
| { |
| c.setDashed(this.isDashed, (this.style != null) ? |
| mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false); |
| } |
| |
| if (dash != null) |
| { |
| c.setDashPattern(dash); |
| } |
| |
| if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) |
| { |
| var 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); |
| }; |
| |
| /** |
| * Function: getGradientBounds |
| * |
| * Returns the bounding box for the gradient box for this shape. |
| */ |
| mxShape.prototype.getGradientBounds = function(c, x, y, w, h) |
| { |
| return new mxRectangle(x, y, w, h); |
| }; |
| |
| /** |
| * Function: updateTransform |
| * |
| * Sets the scale and rotation on the given canvas. |
| */ |
| mxShape.prototype.updateTransform = function(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); |
| }; |
| |
| /** |
| * Function: paintVertexShape |
| * |
| * Paints the vertex shape. |
| */ |
| mxShape.prototype.paintVertexShape = function(c, x, y, w, h) |
| { |
| this.paintBackground(c, x, y, w, h); |
| c.setShadow(false); |
| this.paintForeground(c, x, y, w, h); |
| }; |
| |
| /** |
| * Function: paintBackground |
| * |
| * Hook for subclassers. This implementation is empty. |
| */ |
| mxShape.prototype.paintBackground = function(c, x, y, w, h) { }; |
| |
| /** |
| * Function: paintForeground |
| * |
| * Hook for subclassers. This implementation is empty. |
| */ |
| mxShape.prototype.paintForeground = function(c, x, y, w, h) { }; |
| |
| /** |
| * Function: paintEdgeShape |
| * |
| * Hook for subclassers. This implementation is empty. |
| */ |
| mxShape.prototype.paintEdgeShape = function(c, pts) { }; |
| |
| /** |
| * Function: getArcSize |
| * |
| * Returns the arc size for the given dimension. |
| */ |
| mxShape.prototype.getArcSize = function(w, h) |
| { |
| var r = 0; |
| |
| if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') |
| { |
| r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style, |
| mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)); |
| } |
| else |
| { |
| var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, |
| mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100; |
| r = Math.min(w * f, h * f); |
| } |
| |
| return r; |
| }; |
| |
| /** |
| * Function: paintGlassEffect |
| * |
| * Paints the glass gradient effect. |
| */ |
| mxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc) |
| { |
| var sw = Math.ceil(this.strokewidth / 2); |
| var 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(); |
| }; |
| |
| /** |
| * Function: addPoints |
| * |
| * Paints the given points with rounded corners. |
| */ |
| mxShape.prototype.addPoints = function(c, pts, rounded, arcSize, close, exclude, initialMove) |
| { |
| if (pts != null && pts.length > 0) |
| { |
| initialMove = (initialMove != null) ? initialMove : true; |
| var pe = pts[pts.length - 1]; |
| |
| // Adds virtual waypoint in the center between start and end point |
| if (close && rounded) |
| { |
| pts = pts.slice(); |
| var p0 = pts[0]; |
| var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2); |
| pts.splice(0, 0, wp); |
| } |
| |
| var pt = pts[0]; |
| var 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)) |
| { |
| var tmp = pts[mxUtils.mod(i, pts.length)]; |
| var dx = pt.x - tmp.x; |
| var dy = pt.y - tmp.y; |
| |
| if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, 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 |
| var dist = Math.sqrt(dx * dx + dy * dy); |
| var nx1 = dx * Math.min(arcSize, dist / 2) / dist; |
| var ny1 = dy * Math.min(arcSize, dist / 2) / dist; |
| |
| var x1 = tmp.x + nx1; |
| var 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 |
| var next = pts[mxUtils.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[mxUtils.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)); |
| var nx2 = dx * Math.min(arcSize, dist / 2) / dist; |
| var ny2 = dy * Math.min(arcSize, dist / 2) / dist; |
| |
| var x2 = tmp.x + nx2; |
| var y2 = tmp.y + ny2; |
| |
| c.quadTo(tmp.x, tmp.y, x2, y2); |
| tmp = new mxPoint(x2, y2); |
| } |
| else |
| { |
| c.lineTo(tmp.x, tmp.y); |
| } |
| |
| pt = tmp; |
| i++; |
| } |
| |
| if (close) |
| { |
| c.close(); |
| } |
| else |
| { |
| c.lineTo(pe.x, pe.y); |
| } |
| } |
| }; |
| |
| /** |
| * Function: resetStyles |
| * |
| * Resets all styles. |
| */ |
| mxShape.prototype.resetStyles = function() |
| { |
| this.initStyles(); |
| |
| this.spacing = 0; |
| |
| delete this.fill; |
| delete this.gradient; |
| delete this.gradientDirection; |
| delete this.stroke; |
| delete this.startSize; |
| delete this.endSize; |
| delete this.startArrow; |
| delete this.endArrow; |
| delete this.direction; |
| delete this.isShadow; |
| delete this.isDashed; |
| delete this.isRounded; |
| delete this.glass; |
| }; |
| |
| /** |
| * Function: apply |
| * |
| * Applies the style of the given <mxCellState> to the shape. This |
| * implementation assigns the following styles to local fields: |
| * |
| * - <mxConstants.STYLE_FILLCOLOR> => fill |
| * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient |
| * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection |
| * - <mxConstants.STYLE_OPACITY> => opacity |
| * - <mxConstants.STYLE_FILL_OPACITY> => fillOpacity |
| * - <mxConstants.STYLE_STROKE_OPACITY> => strokeOpacity |
| * - <mxConstants.STYLE_STROKECOLOR> => stroke |
| * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth |
| * - <mxConstants.STYLE_SHADOW> => isShadow |
| * - <mxConstants.STYLE_DASHED> => isDashed |
| * - <mxConstants.STYLE_SPACING> => spacing |
| * - <mxConstants.STYLE_STARTSIZE> => startSize |
| * - <mxConstants.STYLE_ENDSIZE> => endSize |
| * - <mxConstants.STYLE_ROUNDED> => isRounded |
| * - <mxConstants.STYLE_STARTARROW> => startArrow |
| * - <mxConstants.STYLE_ENDARROW> => endArrow |
| * - <mxConstants.STYLE_ROTATION> => rotation |
| * - <mxConstants.STYLE_DIRECTION> => direction |
| * - <mxConstants.STYLE_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 <mxCellState> itself. If <outline> should be true, make |
| * sure to set it before calling this method. |
| * |
| * Parameters: |
| * |
| * state - <mxCellState> of the corresponding cell. |
| */ |
| mxShape.prototype.apply = function(state) |
| { |
| this.state = state; |
| this.style = state.style; |
| |
| if (this.style != null) |
| { |
| this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill); |
| this.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient); |
| this.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection); |
| this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity); |
| this.fillOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_FILL_OPACITY, this.fillOpacity); |
| this.strokeOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_STROKE_OPACITY, this.strokeOpacity); |
| this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR, this.stroke); |
| this.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth); |
| this.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing); |
| this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize); |
| this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize); |
| this.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow); |
| this.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, this.endArrow); |
| this.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation); |
| this.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction); |
| this.flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1; |
| this.flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1; |
| |
| // Legacy support for stencilFlipH/V |
| if (this.stencil != null) |
| { |
| this.flipH = mxUtils.getValue(this.style, 'stencilFlipH', 0) == 1 || this.flipH; |
| this.flipV = mxUtils.getValue(this.style, 'stencilFlipV', 0) == 1 || this.flipV; |
| } |
| |
| if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH) |
| { |
| var tmp = this.flipH; |
| this.flipH = this.flipV; |
| this.flipV = tmp; |
| } |
| |
| this.isShadow = mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow) == 1; |
| this.isDashed = mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed) == 1; |
| this.isRounded = mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded) == 1; |
| this.glass = mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass) == 1; |
| |
| if (this.fill == mxConstants.NONE) |
| { |
| this.fill = null; |
| } |
| |
| if (this.gradient == mxConstants.NONE) |
| { |
| this.gradient = null; |
| } |
| |
| if (this.stroke == mxConstants.NONE) |
| { |
| this.stroke = null; |
| } |
| } |
| }; |
| |
| /** |
| * Function: setCursor |
| * |
| * Sets the cursor on the given shape. |
| * |
| * Parameters: |
| * |
| * cursor - The cursor to be used. |
| */ |
| mxShape.prototype.setCursor = function(cursor) |
| { |
| if (cursor == null) |
| { |
| cursor = ''; |
| } |
| |
| this.cursor = cursor; |
| |
| if (this.node != null) |
| { |
| this.node.style.cursor = cursor; |
| } |
| }; |
| |
| /** |
| * Function: getCursor |
| * |
| * Returns the current cursor. |
| */ |
| mxShape.prototype.getCursor = function() |
| { |
| return this.cursor; |
| }; |
| |
| /** |
| * Function: updateBoundingBox |
| * |
| * Updates the <boundingBox> for this shape using <createBoundingBox> and |
| * <augmentBoundingBox> and stores the result in <boundingBox>. |
| */ |
| mxShape.prototype.updateBoundingBox = function() |
| { |
| // Tries to get bounding box from SVG subsystem |
| // LATER: Use getBoundingClientRect for fallback in VML |
| if (this.useSvgBoundingBox && this.node != null && this.node.ownerSVGElement != null) |
| { |
| try |
| { |
| var b = this.node.getBBox(); |
| |
| if (b.width > 0 && b.height > 0) |
| { |
| this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height); |
| |
| // Adds strokeWidth |
| this.boundingBox.grow(this.strokewidth * this.scale / 2); |
| |
| return; |
| } |
| } |
| catch(e) |
| { |
| // fallback to code below |
| } |
| } |
| |
| if (this.bounds != null) |
| { |
| var bbox = this.createBoundingBox(); |
| |
| if (bbox != null) |
| { |
| this.augmentBoundingBox(bbox); |
| var rot = this.getShapeRotation(); |
| |
| if (rot != 0) |
| { |
| bbox = mxUtils.getBoundingBox(bbox, rot); |
| } |
| } |
| |
| this.boundingBox = bbox; |
| } |
| }; |
| |
| /** |
| * Function: createBoundingBox |
| * |
| * Returns a new rectangle that represents the bounding box of the bare shape |
| * with no shadows or strokewidths. |
| */ |
| mxShape.prototype.createBoundingBox = function() |
| { |
| var bb = this.bounds.clone(); |
| |
| if ((this.stencil != null && (this.direction == mxConstants.DIRECTION_NORTH || |
| this.direction == mxConstants.DIRECTION_SOUTH)) || this.isPaintBoundsInverted()) |
| { |
| bb.rotate90(); |
| } |
| |
| return bb; |
| }; |
| |
| /** |
| * Function: augmentBoundingBox |
| * |
| * Augments the bounding box with the strokewidth and shadow offsets. |
| */ |
| mxShape.prototype.augmentBoundingBox = function(bbox) |
| { |
| if (this.isShadow) |
| { |
| bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale); |
| bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale); |
| } |
| |
| // Adds strokeWidth |
| bbox.grow(this.strokewidth * this.scale / 2); |
| }; |
| |
| /** |
| * Function: isPaintBoundsInverted |
| * |
| * Returns true if the bounds should be inverted. |
| */ |
| mxShape.prototype.isPaintBoundsInverted = function() |
| { |
| // Stencil implements inversion via aspect |
| return this.stencil == null && (this.direction == mxConstants.DIRECTION_NORTH || |
| this.direction == mxConstants.DIRECTION_SOUTH); |
| }; |
| |
| /** |
| * Function: getRotation |
| * |
| * Returns the rotation from the style. |
| */ |
| mxShape.prototype.getRotation = function() |
| { |
| return (this.rotation != null) ? this.rotation : 0; |
| }; |
| |
| /** |
| * Function: getTextRotation |
| * |
| * Returns the rotation for the text label. |
| */ |
| mxShape.prototype.getTextRotation = function() |
| { |
| var rot = this.getRotation(); |
| |
| if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) != 1) |
| { |
| rot += mxText.prototype.verticalTextRotation; |
| } |
| |
| return rot; |
| }; |
| |
| /** |
| * Function: getShapeRotation |
| * |
| * Returns the actual rotation of the shape. |
| */ |
| mxShape.prototype.getShapeRotation = function() |
| { |
| var rot = this.getRotation(); |
| |
| if (this.direction != null) |
| { |
| if (this.direction == mxConstants.DIRECTION_NORTH) |
| { |
| rot += 270; |
| } |
| else if (this.direction == mxConstants.DIRECTION_WEST) |
| { |
| rot += 180; |
| } |
| else if (this.direction == mxConstants.DIRECTION_SOUTH) |
| { |
| rot += 90; |
| } |
| } |
| |
| return rot; |
| }; |
| |
| /** |
| * Function: createTransparentSvgRectangle |
| * |
| * Adds a transparent rectangle that catches all events. |
| */ |
| mxShape.prototype.createTransparentSvgRectangle = function(x, y, w, h) |
| { |
| var rect = document.createElementNS(mxConstants.NS_SVG, 'rect'); |
| rect.setAttribute('x', x); |
| rect.setAttribute('y', y); |
| rect.setAttribute('width', w); |
| rect.setAttribute('height', h); |
| rect.setAttribute('fill', 'none'); |
| rect.setAttribute('stroke', 'none'); |
| rect.setAttribute('pointer-events', 'all'); |
| |
| return rect; |
| }; |
| |
| /** |
| * Function: setTransparentBackgroundImage |
| * |
| * Sets a transparent background CSS style to catch all events. |
| * |
| * Paints the line shape. |
| */ |
| mxShape.prototype.setTransparentBackgroundImage = function(node) |
| { |
| node.style.backgroundImage = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')'; |
| }; |
| |
| /** |
| * Function: releaseSvgGradients |
| * |
| * Paints the line shape. |
| */ |
| mxShape.prototype.releaseSvgGradients = function(grads) |
| { |
| if (grads != null) |
| { |
| for (var key in grads) |
| { |
| var gradient = grads[key]; |
| |
| if (gradient != null) |
| { |
| gradient.mxRefCount = (gradient.mxRefCount || 0) - 1; |
| |
| if (gradient.mxRefCount == 0 && gradient.parentNode != null) |
| { |
| gradient.parentNode.removeChild(gradient); |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: destroy |
| * |
| * Destroys the shape by removing it from the DOM and releasing the DOM |
| * node associated with the shape using <mxEvent.release>. |
| */ |
| mxShape.prototype.destroy = function() |
| { |
| if (this.node != null) |
| { |
| mxEvent.release(this.node); |
| |
| if (this.node.parentNode != null) |
| { |
| this.node.parentNode.removeChild(this.node); |
| } |
| |
| this.node = null; |
| } |
| |
| // Decrements refCount and removes unused |
| this.releaseSvgGradients(this.oldGradients); |
| this.oldGradients = null; |
| }; |