481 lines
15 KiB
Plaintext
481 lines
15 KiB
Plaintext
define(function(require) {
|
|
|
|
'use strict';
|
|
|
|
var zrUtil = require('zrender/core/util');
|
|
|
|
var pathTool = require('zrender/tool/path');
|
|
var round = Math.round;
|
|
var Path = require('zrender/graphic/Path');
|
|
var colorTool = require('zrender/tool/color');
|
|
var matrix = require('zrender/core/matrix');
|
|
var vector = require('zrender/core/vector');
|
|
var Gradient = require('zrender/graphic/Gradient');
|
|
|
|
var graphic = {};
|
|
|
|
graphic.Group = require('zrender/container/Group');
|
|
|
|
graphic.Image = require('zrender/graphic/Image');
|
|
|
|
graphic.Text = require('zrender/graphic/Text');
|
|
|
|
graphic.Circle = require('zrender/graphic/shape/Circle');
|
|
|
|
graphic.Sector = require('zrender/graphic/shape/Sector');
|
|
|
|
graphic.Ring = require('zrender/graphic/shape/Ring');
|
|
|
|
graphic.Polygon = require('zrender/graphic/shape/Polygon');
|
|
|
|
graphic.Polyline = require('zrender/graphic/shape/Polyline');
|
|
|
|
graphic.Rect = require('zrender/graphic/shape/Rect');
|
|
|
|
graphic.Line = require('zrender/graphic/shape/Line');
|
|
|
|
graphic.BezierCurve = require('zrender/graphic/shape/BezierCurve');
|
|
|
|
graphic.Arc = require('zrender/graphic/shape/Arc');
|
|
|
|
graphic.CompoundPath = require('zrender/graphic/CompoundPath');
|
|
|
|
graphic.LinearGradient = require('zrender/graphic/LinearGradient');
|
|
|
|
graphic.RadialGradient = require('zrender/graphic/RadialGradient');
|
|
|
|
graphic.BoundingRect = require('zrender/core/BoundingRect');
|
|
|
|
/**
|
|
* Extend shape with parameters
|
|
*/
|
|
graphic.extendShape = function (opts) {
|
|
return Path.extend(opts);
|
|
};
|
|
|
|
/**
|
|
* Extend path
|
|
*/
|
|
graphic.extendPath = function (pathData, opts) {
|
|
return pathTool.extendFromString(pathData, opts);
|
|
};
|
|
|
|
/**
|
|
* Create a path element from path data string
|
|
* @param {string} pathData
|
|
* @param {Object} opts
|
|
* @param {module:zrender/core/BoundingRect} rect
|
|
* @param {string} [layout=cover] 'center' or 'cover'
|
|
*/
|
|
graphic.makePath = function (pathData, opts, rect, layout) {
|
|
var path = pathTool.createFromString(pathData, opts);
|
|
var boundingRect = path.getBoundingRect();
|
|
if (rect) {
|
|
var aspect = boundingRect.width / boundingRect.height;
|
|
|
|
if (layout === 'center') {
|
|
// Set rect to center, keep width / height ratio.
|
|
var width = rect.height * aspect;
|
|
var height;
|
|
if (width <= rect.width) {
|
|
height = rect.height;
|
|
}
|
|
else {
|
|
width = rect.width;
|
|
height = width / aspect;
|
|
}
|
|
var cx = rect.x + rect.width / 2;
|
|
var cy = rect.y + rect.height / 2;
|
|
|
|
rect.x = cx - width / 2;
|
|
rect.y = cy - height / 2;
|
|
rect.width = width;
|
|
rect.height = height;
|
|
}
|
|
|
|
this.resizePath(path, rect);
|
|
}
|
|
return path;
|
|
};
|
|
|
|
graphic.mergePath = pathTool.mergePath,
|
|
|
|
/**
|
|
* Resize a path to fit the rect
|
|
* @param {module:zrender/graphic/Path} path
|
|
* @param {Object} rect
|
|
*/
|
|
graphic.resizePath = function (path, rect) {
|
|
if (!path.applyTransform) {
|
|
return;
|
|
}
|
|
|
|
var pathRect = path.getBoundingRect();
|
|
|
|
var m = pathRect.calculateTransform(rect);
|
|
|
|
path.applyTransform(m);
|
|
};
|
|
|
|
/**
|
|
* Sub pixel optimize line for canvas
|
|
*
|
|
* @param {Object} param
|
|
* @param {Object} [param.shape]
|
|
* @param {number} [param.shape.x1]
|
|
* @param {number} [param.shape.y1]
|
|
* @param {number} [param.shape.x2]
|
|
* @param {number} [param.shape.y2]
|
|
* @param {Object} [param.style]
|
|
* @param {number} [param.style.lineWidth]
|
|
* @return {Object} Modified param
|
|
*/
|
|
graphic.subPixelOptimizeLine = function (param) {
|
|
var subPixelOptimize = graphic.subPixelOptimize;
|
|
var shape = param.shape;
|
|
var lineWidth = param.style.lineWidth;
|
|
|
|
if (round(shape.x1 * 2) === round(shape.x2 * 2)) {
|
|
shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true);
|
|
}
|
|
if (round(shape.y1 * 2) === round(shape.y2 * 2)) {
|
|
shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true);
|
|
}
|
|
return param;
|
|
};
|
|
|
|
/**
|
|
* Sub pixel optimize rect for canvas
|
|
*
|
|
* @param {Object} param
|
|
* @param {Object} [param.shape]
|
|
* @param {number} [param.shape.x]
|
|
* @param {number} [param.shape.y]
|
|
* @param {number} [param.shape.width]
|
|
* @param {number} [param.shape.height]
|
|
* @param {Object} [param.style]
|
|
* @param {number} [param.style.lineWidth]
|
|
* @return {Object} Modified param
|
|
*/
|
|
graphic.subPixelOptimizeRect = function (param) {
|
|
var subPixelOptimize = graphic.subPixelOptimize;
|
|
var shape = param.shape;
|
|
var lineWidth = param.style.lineWidth;
|
|
var originX = shape.x;
|
|
var originY = shape.y;
|
|
var originWidth = shape.width;
|
|
var originHeight = shape.height;
|
|
shape.x = subPixelOptimize(shape.x, lineWidth, true);
|
|
shape.y = subPixelOptimize(shape.y, lineWidth, true);
|
|
shape.width = Math.max(
|
|
subPixelOptimize(originX + originWidth, lineWidth, false) - shape.x,
|
|
originWidth === 0 ? 0 : 1
|
|
);
|
|
shape.height = Math.max(
|
|
subPixelOptimize(originY + originHeight, lineWidth, false) - shape.y,
|
|
originHeight === 0 ? 0 : 1
|
|
);
|
|
return param;
|
|
};
|
|
|
|
/**
|
|
* Sub pixel optimize for canvas
|
|
*
|
|
* @param {number} position Coordinate, such as x, y
|
|
* @param {number} lineWidth Should be nonnegative integer.
|
|
* @param {boolean=} positiveOrNegative Default false (negative).
|
|
* @return {number} Optimized position.
|
|
*/
|
|
graphic.subPixelOptimize = function (position, lineWidth, positiveOrNegative) {
|
|
// Assure that (position + lineWidth / 2) is near integer edge,
|
|
// otherwise line will be fuzzy in canvas.
|
|
var doubledPosition = round(position * 2);
|
|
return (doubledPosition + round(lineWidth)) % 2 === 0
|
|
? doubledPosition / 2
|
|
: (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
|
|
};
|
|
|
|
function hasFillOrStroke(fillOrStroke) {
|
|
return fillOrStroke != null && fillOrStroke != 'none';
|
|
}
|
|
|
|
function liftColor(color) {
|
|
return color instanceof Gradient ? color : colorTool.lift(color, -0.1);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
function cacheElementStl(el) {
|
|
if (el.__hoverStlDirty) {
|
|
var stroke = el.style.stroke;
|
|
var fill = el.style.fill;
|
|
|
|
// Create hoverStyle on mouseover
|
|
var hoverStyle = el.__hoverStl;
|
|
hoverStyle.fill = hoverStyle.fill
|
|
|| (hasFillOrStroke(fill) ? liftColor(fill) : null);
|
|
hoverStyle.stroke = hoverStyle.stroke
|
|
|| (hasFillOrStroke(stroke) ? liftColor(stroke) : null);
|
|
|
|
var normalStyle = {};
|
|
for (var name in hoverStyle) {
|
|
if (hoverStyle.hasOwnProperty(name)) {
|
|
normalStyle[name] = el.style[name];
|
|
}
|
|
}
|
|
|
|
el.__normalStl = normalStyle;
|
|
|
|
el.__hoverStlDirty = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
function doSingleEnterHover(el) {
|
|
if (el.__isHover) {
|
|
return;
|
|
}
|
|
|
|
cacheElementStl(el);
|
|
|
|
el.setStyle(el.__hoverStl);
|
|
el.z2 += 1;
|
|
|
|
el.__isHover = true;
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function doSingleLeaveHover(el) {
|
|
if (!el.__isHover) {
|
|
return;
|
|
}
|
|
|
|
var normalStl = el.__normalStl;
|
|
normalStl && el.setStyle(normalStl);
|
|
el.z2 -= 1;
|
|
|
|
el.__isHover = false;
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function doEnterHover(el) {
|
|
el.type === 'group'
|
|
? el.traverse(function (child) {
|
|
if (child.type !== 'group') {
|
|
doSingleEnterHover(child);
|
|
}
|
|
})
|
|
: doSingleEnterHover(el);
|
|
}
|
|
|
|
function doLeaveHover(el) {
|
|
el.type === 'group'
|
|
? el.traverse(function (child) {
|
|
if (child.type !== 'group') {
|
|
doSingleLeaveHover(child);
|
|
}
|
|
})
|
|
: doSingleLeaveHover(el);
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function setElementHoverStl(el, hoverStl) {
|
|
// If element has sepcified hoverStyle, then use it instead of given hoverStyle
|
|
// Often used when item group has a label element and it's hoverStyle is different
|
|
el.__hoverStl = el.hoverStyle || hoverStl || {};
|
|
el.__hoverStlDirty = true;
|
|
|
|
if (el.__isHover) {
|
|
cacheElementStl(el);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function onElementMouseOver() {
|
|
// Only if element is not in emphasis status
|
|
!this.__isEmphasis && doEnterHover(this);
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function onElementMouseOut() {
|
|
// Only if element is not in emphasis status
|
|
!this.__isEmphasis && doLeaveHover(this);
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function enterEmphasis() {
|
|
this.__isEmphasis = true;
|
|
doEnterHover(this);
|
|
}
|
|
|
|
/**
|
|
* @inner
|
|
*/
|
|
function leaveEmphasis() {
|
|
this.__isEmphasis = false;
|
|
doLeaveHover(this);
|
|
}
|
|
|
|
/**
|
|
* Set hover style of element
|
|
* @param {module:zrender/Element} el
|
|
* @param {Object} [hoverStyle]
|
|
*/
|
|
graphic.setHoverStyle = function (el, hoverStyle) {
|
|
el.type === 'group'
|
|
? el.traverse(function (child) {
|
|
if (child.type !== 'group') {
|
|
setElementHoverStl(child, hoverStyle);
|
|
}
|
|
})
|
|
: setElementHoverStl(el, hoverStyle);
|
|
// Remove previous bound handlers
|
|
el.on('mouseover', onElementMouseOver)
|
|
.on('mouseout', onElementMouseOut);
|
|
|
|
// Emphasis, normal can be triggered manually
|
|
el.on('emphasis', enterEmphasis)
|
|
.on('normal', leaveEmphasis);
|
|
};
|
|
|
|
/**
|
|
* Set text option in the style
|
|
* @param {Object} textStyle
|
|
* @param {module:echarts/model/Model} labelModel
|
|
* @param {string} color
|
|
*/
|
|
graphic.setText = function (textStyle, labelModel, color) {
|
|
var labelPosition = labelModel.getShallow('position') || 'inside';
|
|
var labelColor = labelPosition.indexOf('inside') >= 0 ? 'white' : color;
|
|
var textStyleModel = labelModel.getModel('textStyle');
|
|
zrUtil.extend(textStyle, {
|
|
textDistance: labelModel.getShallow('distance') || 5,
|
|
textFont: textStyleModel.getFont(),
|
|
textPosition: labelPosition,
|
|
textFill: textStyleModel.getTextColor() || labelColor
|
|
});
|
|
};
|
|
|
|
function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) {
|
|
if (typeof dataIndex === 'function') {
|
|
cb = dataIndex;
|
|
dataIndex = null;
|
|
}
|
|
|
|
var postfix = isUpdate ? 'Update' : '';
|
|
var duration = animatableModel
|
|
&& animatableModel.getShallow('animationDuration' + postfix);
|
|
var animationEasing = animatableModel
|
|
&& animatableModel.getShallow('animationEasing' + postfix);
|
|
var animationDelay = animatableModel
|
|
&& animatableModel.getShallow('animationDelay' + postfix);
|
|
if (typeof animationDelay === 'function') {
|
|
animationDelay = animationDelay(dataIndex);
|
|
}
|
|
|
|
animatableModel && animatableModel.getShallow('animation')
|
|
? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb)
|
|
: (el.attr(props), cb && cb());
|
|
}
|
|
/**
|
|
* Update graphic element properties with or without animation according to the configuration in series
|
|
* @param {module:zrender/Element} el
|
|
* @param {Object} props
|
|
* @param {module:echarts/model/Model} [animatableModel]
|
|
* @param {number} [dataIndex]
|
|
* @param {Function} [cb]
|
|
* @example
|
|
* graphic.updateProps(el, {
|
|
* position: [100, 100]
|
|
* }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
|
|
* // Or
|
|
* graphic.updateProps(el, {
|
|
* position: [100, 100]
|
|
* }, seriesModel, function () { console.log('Animation done!'); });
|
|
*/
|
|
graphic.updateProps = zrUtil.curry(animateOrSetProps, true);
|
|
|
|
/**
|
|
* Init graphic element properties with or without animation according to the configuration in series
|
|
* @param {module:zrender/Element} el
|
|
* @param {Object} props
|
|
* @param {module:echarts/model/Model} [animatableModel]
|
|
* @param {Function} cb
|
|
*/
|
|
graphic.initProps = zrUtil.curry(animateOrSetProps, false);
|
|
|
|
/**
|
|
* Get transform matrix of target (param target),
|
|
* in coordinate of its ancestor (param ancestor)
|
|
*
|
|
* @param {module:zrender/mixin/Transformable} target
|
|
* @param {module:zrender/mixin/Transformable} [ancestor]
|
|
*/
|
|
graphic.getTransform = function (target, ancestor) {
|
|
var mat = matrix.identity([]);
|
|
|
|
while (target && target !== ancestor) {
|
|
matrix.mul(mat, target.getLocalTransform(), mat);
|
|
target = target.parent;
|
|
}
|
|
|
|
return mat;
|
|
};
|
|
|
|
/**
|
|
* Apply transform to an vertex.
|
|
* @param {Array.<number>} vertex [x, y]
|
|
* @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
|
|
* @param {boolean=} invert Whether use invert matrix.
|
|
* @return {Array.<number>} [x, y]
|
|
*/
|
|
graphic.applyTransform = function (vertex, transform, invert) {
|
|
if (invert) {
|
|
transform = matrix.invert([], transform);
|
|
}
|
|
return vector.applyTransform([], vertex, transform);
|
|
};
|
|
|
|
/**
|
|
* @param {string} direction 'left' 'right' 'top' 'bottom'
|
|
* @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
|
|
* @param {boolean=} invert Whether use invert matrix.
|
|
* @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
|
|
*/
|
|
graphic.transformDirection = function (direction, transform, invert) {
|
|
|
|
// Pick a base, ensure that transform result will not be (0, 0).
|
|
var hBase = (transform[4] === 0 || transform[5] === 0 || transform[0] === 0)
|
|
? 1 : Math.abs(2 * transform[4] / transform[0]);
|
|
var vBase = (transform[4] === 0 || transform[5] === 0 || transform[2] === 0)
|
|
? 1 : Math.abs(2 * transform[4] / transform[2]);
|
|
|
|
var vertex = [
|
|
direction === 'left' ? -hBase : direction === 'right' ? hBase : 0,
|
|
direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0
|
|
];
|
|
|
|
vertex = graphic.applyTransform(vertex, transform, invert);
|
|
|
|
return Math.abs(vertex[0]) > Math.abs(vertex[1])
|
|
? (vertex[0] > 0 ? 'right' : 'left')
|
|
: (vertex[1] > 0 ? 'bottom' : 'top');
|
|
};
|
|
|
|
return graphic;
|
|
}); |