1673 lines
38 KiB
JavaScript
1673 lines
38 KiB
JavaScript
/**
|
|
* 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: minSvgStrokeWidth
|
|
*
|
|
* Minimum stroke width for SVG output.
|
|
*/
|
|
mxShape.prototype.minSvgStrokeWidth = 1;
|
|
|
|
/**
|
|
* 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.beforePaint(canvas);
|
|
this.paint(canvas);
|
|
this.afterPaint(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() {};
|
|
canvas.text = 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;
|
|
var 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 = 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: updateHtmlColors
|
|
*
|
|
* 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: updateHtmlBounds
|
|
*
|
|
* 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: beforePaint
|
|
*
|
|
* Invoked before paint is called.
|
|
*/
|
|
mxShape.prototype.beforePaint = function(c) { }
|
|
|
|
/**
|
|
* Function: afterPaint
|
|
*
|
|
* Invokes after paint was called.
|
|
*/
|
|
mxShape.prototype.afterPaint = function(c) { }
|
|
|
|
/**
|
|
* Function: paint
|
|
*
|
|
* Generic rendering code.
|
|
*/
|
|
mxShape.prototype.paint = function(c)
|
|
{
|
|
var strokeDrawn = false;
|
|
|
|
if (c != null && this.outline)
|
|
{
|
|
var stroke = c.stroke;
|
|
|
|
c.stroke = function()
|
|
{
|
|
strokeDrawn = true;
|
|
stroke.apply(this, arguments);
|
|
};
|
|
|
|
var fillAndStroke = c.fillAndStroke;
|
|
|
|
c.fillAndStroke = function()
|
|
{
|
|
strokeDrawn = true;
|
|
fillAndStroke.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Draws highlight rectangle if no stroke was used
|
|
if (c != null && this.outline && !strokeDrawn)
|
|
{
|
|
c.rect(x, y, w, h);
|
|
c.stroke();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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);
|
|
|
|
if (!this.outline || this.style == null || mxUtils.getValue(
|
|
this.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0)
|
|
{
|
|
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: isRoundable
|
|
*
|
|
* Hook for subclassers.
|
|
*/
|
|
mxShape.prototype.isRoundable = function()
|
|
{
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|