Comparing version 2.0.2 to 3.0.0
{ | ||
"name": "zrender", | ||
"version": "2.0.2", | ||
"maintainers": [ | ||
{ "name": "Kener", "email": "linzhifeng@baidu.com" }, | ||
{ "name": "Pissang", "email": "shenyi01@baidu.com" }, | ||
{ "name": "Erik", "email": "erik@baidu.com)" }, | ||
{ "name": "Loutongbing", "email": "loutongbing@baidu.com" }, | ||
{ "name": "Yangji", "email": "yangji01@baidu.com" }, | ||
{ "name": "Sushuang", "email": "sushuang@baidu.com" } | ||
"version": "3.0.0", | ||
"description": "A lightweight canvas library.", | ||
"keywords": [ | ||
"canvas", | ||
"2d" | ||
], | ||
"main": "zrender", | ||
"description": "一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型,让canvas绘图大不同!", | ||
"homepage": "https://ecomfe.github.com/zrender" | ||
"author": [{ | ||
"name": "Kenner", | ||
"email": "kener.linfeng@gmail.com" | ||
}, { | ||
"name": "Yi Shen", | ||
"url": "https://github.com/pissang" | ||
}, { | ||
"name": "Shuang Su", | ||
"url": "https://github.com/100pah" | ||
}], | ||
"contributors": [{ | ||
"name": "erik", | ||
"email": "errorrik@gmail.com" | ||
}], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/ecomfe/zrender.git" | ||
}, | ||
"main": "build/zrender.min.js", | ||
"files": [ | ||
"build", | ||
"src" | ||
] | ||
} |
135
README.md
ZRender | ||
======= | ||
http://ecomfe.github.com/zrender | ||
一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型,让canvas绘图大不同! | ||
A lightweight canvas library which providing 2d draw for [ECharts](https://github.com/ecomfe/echarts) | ||
Architecture | ||
------------ | ||
MVC核心封装实现图形仓库、视图渲染和交互控制: | ||
* Stroage(M) : shape数据CURD管理 | ||
* Painter(V) : canvase元素生命周期管理,视图渲染,绘画,更新控制 | ||
* Handler(C) : 事件交互处理,实现完整dom事件模拟封装 | ||
* shape : 图形实体,分而治之的图形策略,可定义扩展 | ||
* tool : 绘画扩展相关实用方法,工具及脚手架 | ||
## License | ||
Copyright (c) 2013, Baidu Inc. | ||
All rights reserved. | ||
![ZRender Architecture](doc/asset/img/zrender.png) | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
特色 | ||
---- | ||
### 简单 | ||
无需canvas基础,精简的接口方法,符合AMD标准,易学易用。 | ||
1. Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
2. Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
require( | ||
['zrender/zrender'], | ||
function(zrender) { | ||
// just init to get a zrender Instance | ||
var zr = zrender.init(document.getElementById('main')); | ||
// zr can be used now! | ||
... | ||
} | ||
); | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
### 数据驱动 | ||
利用zrender绘图,你只需做的是定义图形数据,剩下的事情就交给zrender吧~ | ||
哦,对了,差点忘记告诉你,只要在你定义图形数据时设置draggable属性为true,图形拖拽就已经可用了! | ||
var CircleShape = require('zrender/shape/Circle'); | ||
zr.addShape( | ||
new CircleShape({ | ||
style : { | ||
x : 100, | ||
y : 100, | ||
r : 50, | ||
color : 'rgba(220, 20, 60, 0.8)' | ||
} | ||
}) | ||
); | ||
zr.render(); | ||
### 完整的事件封装 | ||
用你再熟悉不过的dom事件模型去操作canvas里的图形元素是件很cool的事情~ | ||
你不仅可以响应zrender全局事件,你甚至可以为在特定shape上添加特定事件,后续发生的一切都会按你想的那样去运行~ | ||
var CircleShape = require('zrender/shape/Circle'); | ||
zr.addShape( | ||
new CircleShape({ | ||
style : {...}, | ||
// 图形元素上绑定事件 | ||
onmouseover : function(params) { | ||
concole.log('catch you!'); | ||
} | ||
}) | ||
); | ||
// 全局事件 | ||
zr.on('click', function(params) {alert('Hello, zrender!')}); | ||
### 高效的分层刷新 | ||
正如css中zlevel的作用一样,你可以定义把不同的shape分别放在不同的层中,这不仅实现了视觉上的上下覆盖, | ||
更重要的是当图形元素发生变化后的refresh将局限在发生了变化的图形层中,这在你利用zrender做各种动画效果时将十分有用, | ||
性能自然也更加出色~ | ||
zr.addShape(shapeA); // shapeA.zlevel = 0; (default) | ||
zr.addShape(shapeB); // shapeB.zlevel = 1; | ||
zr.render(); | ||
zr.modShape(shapeB.id, {color:'red'}); | ||
// Don't worry! Is merge! | ||
zr.refresh(); | ||
// Just the level 1 canvas has been refresh~ | ||
### 丰富的图形选项 | ||
当前内置多种图形元素(圆形、椭圆、圆环、扇形、矩形、多边形、直线、曲线、心形、水滴、路径、文字、图片。Will be more..),统一且丰富的图形属性充分满足你的个性化需求! | ||
var CircleShape = require('zrender/shape/Circle'); | ||
var myShape = new CircleShape({ | ||
zlevel : 1, | ||
style : { | ||
... // color | strokeColor | text | textFont | ... | ||
}, | ||
draggable : true | ||
}); | ||
### 强大的动画支持 | ||
提供promise式的动画接口和常用缓动函数,轻松实现各种动画需求~ | ||
zr.addShape(newShape); | ||
zr.render(); | ||
zr.animate(newShape.id) | ||
.when(1000, { position : [300, 200] }) | ||
.when(2000, { position : [30, 400] }) | ||
.start('BounceOut'); | ||
### 易于扩展 | ||
分而治之的图形定义策略允许你扩展自己独有的图形元素,你既可以完整实现三个接口方法(brush、drift、isCover), | ||
也可以通过base派生后仅实现你所关心的图形细节。 | ||
function MyShape() { ... } | ||
zr.addShape( | ||
new MyShape({ // and use it! | ||
style : {...}, | ||
... | ||
}) | ||
); | ||
The views and conclusions contained in the software and documentation are those | ||
of the authors and should not be interpreted as representing official policies, | ||
either expressed or implied, of the FreeBSD Project. |
/** | ||
* 动画主类, 调度和管理所有动画控制器 | ||
* | ||
* @module zrender/animation/Animation | ||
* @author pissang(https://github.com/pissang) | ||
* | ||
* @class : Animation | ||
* @config : stage(optional) 绘制类, 需要提供update接口 | ||
* @config : onframe(optional) | ||
* @method : add | ||
* @method : remove | ||
* @method : update | ||
* @method : start | ||
* @method : stop | ||
*/ | ||
define( | ||
function(require) { | ||
'use strict'; | ||
// TODO Additive animation | ||
// http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/ | ||
// https://developer.apple.com/videos/wwdc2014/#236 | ||
define(function(require) { | ||
var Clip = require('./Clip'); | ||
var color = require('../tool/color'); | ||
var util = require('../tool/util'); | ||
var Dispatcher = require('../tool/event').Dispatcher; | ||
'use strict'; | ||
var requestAnimationFrame = window.requestAnimationFrame | ||
var util = require('../core/util'); | ||
var Dispatcher = require('../core/event').Dispatcher; | ||
var requestAnimationFrame = (typeof window !== 'undefined' && | ||
(window.requestAnimationFrame | ||
|| window.msRequestAnimationFrame | ||
|| window.mozRequestAnimationFrame | ||
|| window.webkitRequestAnimationFrame | ||
|| function(func){setTimeout(func, 16);}; | ||
|| window.webkitRequestAnimationFrame)) | ||
|| function (func) { | ||
setTimeout(func, 16); | ||
}; | ||
var arraySlice = Array.prototype.slice; | ||
var Animator = require('./Animator'); | ||
/** | ||
* @typedef {Object} IZRenderStage | ||
* @property {Function} update | ||
*/ | ||
function Animation(options) { | ||
/** | ||
* @alias module:zrender/animation/Animation | ||
* @constructor | ||
* @param {Object} [options] | ||
* @param {Function} [options.onframe] | ||
* @param {IZRenderStage} [options.stage] | ||
* @example | ||
* var animation = new Animation(); | ||
* var obj = { | ||
* x: 100, | ||
* y: 100 | ||
* }; | ||
* animation.animate(node.position) | ||
* .when(1000, { | ||
* x: 500, | ||
* y: 500 | ||
* }) | ||
* .when(2000, { | ||
* x: 100, | ||
* y: 100 | ||
* }) | ||
* .start('spline'); | ||
*/ | ||
var Animation = function (options) { | ||
options = options || {}; | ||
options = options || {}; | ||
this.stage = options.stage || {}; | ||
this.stage = options.stage || {}; | ||
this.onframe = options.onframe || function() {}; | ||
this.onframe = options.onframe || function() {}; | ||
// private properties | ||
this._clips = []; | ||
// private properties | ||
this._clips = []; | ||
this._running = false; | ||
this._running = false; | ||
this._time = 0; | ||
this._time = 0; | ||
Dispatcher.call(this); | ||
} | ||
Dispatcher.call(this); | ||
}; | ||
Animation.prototype = { | ||
add : function(clip) { | ||
this._clips.push(clip); | ||
}, | ||
remove : function(clip) { | ||
var idx = util.indexOf(this._clips, clip); | ||
if (idx >= 0) { | ||
this._clips.splice(idx, 1); | ||
} | ||
}, | ||
update : function() { | ||
Animation.prototype = { | ||
var time = new Date().getTime(); | ||
var delta = time - this._time; | ||
var clips = this._clips; | ||
var len = clips.length; | ||
constructor: Animation, | ||
/** | ||
* 添加 clip | ||
* @param {module:zrender/animation/Clip} clip | ||
*/ | ||
addClip: function (clip) { | ||
this._clips.push(clip); | ||
}, | ||
/** | ||
* 添加 animator | ||
* @param {module:zrender/animation/Animator} animator | ||
*/ | ||
addAnimator: function (animator) { | ||
animator.animation = this; | ||
var clips = animator.getClips(); | ||
for (var i = 0; i < clips.length; i++) { | ||
this.addClip(clips[i]); | ||
} | ||
}, | ||
/** | ||
* 删除动画片段 | ||
* @param {module:zrender/animation/Clip} clip | ||
*/ | ||
removeClip: function(clip) { | ||
var idx = util.indexOf(this._clips, clip); | ||
if (idx >= 0) { | ||
this._clips.splice(idx, 1); | ||
} | ||
}, | ||
var deferredEvents = []; | ||
var deferredClips = []; | ||
for (var i = 0; i < len; i++) { | ||
var clip = clips[i]; | ||
var e = clip.step(time); | ||
// Throw out the events need to be called after | ||
// stage.update, like destroy | ||
if (e) { | ||
deferredEvents.push(e); | ||
deferredClips.push(clip); | ||
} | ||
} | ||
if (this.stage.update) { | ||
this.stage.update(); | ||
} | ||
/** | ||
* 删除动画片段 | ||
* @param {module:zrender/animation/Animator} animator | ||
*/ | ||
removeAnimator: function (animator) { | ||
var clips = animator.getClips(); | ||
for (var i = 0; i < clips.length; i++) { | ||
this.removeClip(clips[i]); | ||
} | ||
animator.animation = null; | ||
}, | ||
// Remove the finished clip | ||
for (var i = 0; i < len;) { | ||
if (clips[i]._needsRemove) { | ||
clips[i] = clips[len-1]; | ||
clips.pop(); | ||
len--; | ||
} else { | ||
i++; | ||
} | ||
} | ||
_update: function() { | ||
len = deferredEvents.length; | ||
for (var i = 0; i < len; i++) { | ||
deferredClips[i].fire(deferredEvents[i]); | ||
} | ||
var time = new Date().getTime(); | ||
var delta = time - this._time; | ||
var clips = this._clips; | ||
var len = clips.length; | ||
this._time = time; | ||
this.onframe(delta); | ||
this.dispatch('frame', delta); | ||
}, | ||
start : function() { | ||
var self = this; | ||
this._running = true; | ||
function step() { | ||
if (self._running) { | ||
self.update(); | ||
requestAnimationFrame(step); | ||
} | ||
var deferredEvents = []; | ||
var deferredClips = []; | ||
for (var i = 0; i < len; i++) { | ||
var clip = clips[i]; | ||
var e = clip.step(time); | ||
// Throw out the events need to be called after | ||
// stage.update, like destroy | ||
if (e) { | ||
deferredEvents.push(e); | ||
deferredClips.push(clip); | ||
} | ||
this._time = new Date().getTime(); | ||
requestAnimationFrame(step); | ||
}, | ||
stop : function() { | ||
this._running = false; | ||
}, | ||
clear : function() { | ||
this._clips = []; | ||
}, | ||
animate : function(target, options) { | ||
options = options || {}; | ||
var deferred = new Deferred( | ||
target, | ||
options.loop, | ||
options.getter, | ||
options.setter | ||
); | ||
deferred.animation = this; | ||
return deferred; | ||
}, | ||
constructor: Animation | ||
}; | ||
util.merge(Animation.prototype, Dispatcher.prototype, true); | ||
function _defaultGetter(target, key) { | ||
return target[key]; | ||
} | ||
function _defaultSetter(target, key, value) { | ||
target[key] = value; | ||
} | ||
function _interpolateNumber(p0, p1, percent) { | ||
return (p1 - p0) * percent + p0; | ||
} | ||
function _interpolateArray(p0, p1, percent, out, arrDim) { | ||
var len = p0.length; | ||
if (arrDim == 1) { | ||
for (var i = 0; i < len; i++) { | ||
out[i] = _interpolateNumber(p0[i], p1[i], percent); | ||
} | ||
} else { | ||
var len2 = p0[0].length; | ||
for (var i = 0; i < len; i++) { | ||
for (var j = 0; j < len2; j++) { | ||
out[i][j] = _interpolateNumber( | ||
p0[i][j], p1[i][j], percent | ||
); | ||
} | ||
} | ||
} | ||
} | ||
function _isArrayLike(data) { | ||
switch (typeof data) { | ||
case 'undefined': | ||
case 'string': | ||
return false; | ||
} | ||
return typeof data.length !== 'undefined'; | ||
} | ||
function _catmullRomInterpolateArray( | ||
p0, p1, p2, p3, t, t2, t3, out, arrDim | ||
) { | ||
var len = p0.length; | ||
if (arrDim == 1) { | ||
for (var i = 0; i < len; i++) { | ||
out[i] = _catmullRomInterpolate( | ||
p0[i], p1[i], p2[i], p3[i], t, t2, t3 | ||
); | ||
// Remove the finished clip | ||
for (var i = 0; i < len;) { | ||
if (clips[i]._needsRemove) { | ||
clips[i] = clips[len - 1]; | ||
clips.pop(); | ||
len--; | ||
} | ||
} else { | ||
var len2 = p0[0].length; | ||
for (var i = 0; i < len; i++) { | ||
for (var j = 0; j < len2; j++) { | ||
out[i][j] = _catmullRomInterpolate( | ||
p0[i][j], p1[i][j], p2[i][j], p3[i][j], | ||
t, t2, t3 | ||
); | ||
} | ||
else { | ||
i++; | ||
} | ||
} | ||
} | ||
function _catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) { | ||
var v0 = (p2 - p0) * 0.5; | ||
var v1 = (p3 - p1) * 0.5; | ||
return (2 * (p1 - p2) + v0 + v1) * t3 | ||
+ (- 3 * (p1 - p2) - 2 * v0 - v1) * t2 | ||
+ v0 * t + p1; | ||
} | ||
function _cloneValue(value) { | ||
if (_isArrayLike(value)) { | ||
var len = value.length; | ||
if (_isArrayLike(value[0])) { | ||
var ret = []; | ||
for (var i = 0; i < len; i++) { | ||
ret.push(arraySlice.call(value[i])); | ||
} | ||
return ret; | ||
} else { | ||
return arraySlice.call(value); | ||
} | ||
} else { | ||
return value; | ||
len = deferredEvents.length; | ||
for (var i = 0; i < len; i++) { | ||
deferredClips[i].fire(deferredEvents[i]); | ||
} | ||
} | ||
function rgba2String(rgba) { | ||
rgba[0] = Math.floor(rgba[0]); | ||
rgba[1] = Math.floor(rgba[1]); | ||
rgba[2] = Math.floor(rgba[2]); | ||
this._time = time; | ||
return 'rgba(' + rgba.join(',') + ')'; | ||
} | ||
this.onframe(delta); | ||
function Deferred(target, loop, getter, setter) { | ||
this._tracks = {}; | ||
this._target = target; | ||
this.trigger('frame', delta); | ||
this._loop = loop || false; | ||
if (this.stage.update) { | ||
this.stage.update(); | ||
} | ||
}, | ||
/** | ||
* 开始运行动画 | ||
*/ | ||
start: function () { | ||
var self = this; | ||
this._getter = getter || _defaultGetter; | ||
this._setter = setter || _defaultSetter; | ||
this._running = true; | ||
this._clipCount = 0; | ||
function step() { | ||
if (self._running) { | ||
this._delay = 0; | ||
requestAnimationFrame(step); | ||
this._doneList = []; | ||
self._update(); | ||
} | ||
} | ||
this._onframeList = []; | ||
this._time = new Date().getTime(); | ||
requestAnimationFrame(step); | ||
}, | ||
/** | ||
* 停止运行动画 | ||
*/ | ||
stop: function () { | ||
this._running = false; | ||
}, | ||
/** | ||
* 清除所有动画片段 | ||
*/ | ||
clear: function () { | ||
this._clips = []; | ||
}, | ||
/** | ||
* 对一个目标创建一个animator对象,可以指定目标中的属性使用动画 | ||
* @param {Object} target | ||
* @param {Object} options | ||
* @param {boolean} [options.loop=false] 是否循环播放动画 | ||
* @param {Function} [options.getter=null] | ||
* 如果指定getter函数,会通过getter函数取属性值 | ||
* @param {Function} [options.setter=null] | ||
* 如果指定setter函数,会通过setter函数设置属性值 | ||
* @return {module:zrender/animation/Animation~Animator} | ||
*/ | ||
animate: function (target, options) { | ||
options = options || {}; | ||
var animator = new Animator( | ||
target, | ||
options.loop, | ||
options.getter, | ||
options.setter | ||
); | ||
this._clipList = []; | ||
return animator; | ||
} | ||
}; | ||
Deferred.prototype = { | ||
when : function(time /* ms */, props) { | ||
for (var propName in props) { | ||
if (! this._tracks[propName]) { | ||
this._tracks[propName] = []; | ||
// If time is 0 | ||
// Then props is given initialize value | ||
// Else | ||
// Initialize value from current prop value | ||
if (time !== 0) { | ||
this._tracks[propName].push({ | ||
time : 0, | ||
value : _cloneValue( | ||
this._getter(this._target, propName) | ||
) | ||
}); | ||
} | ||
} | ||
this._tracks[propName].push({ | ||
time : parseInt(time, 10), | ||
value : props[propName] | ||
}); | ||
} | ||
return this; | ||
}, | ||
during : function(callback) { | ||
this._onframeList.push(callback); | ||
return this; | ||
}, | ||
start : function(easing) { | ||
util.mixin(Animation, Dispatcher); | ||
var self = this; | ||
var setter = this._setter; | ||
var getter = this._getter; | ||
var onFrameListLen = self._onframeList.length; | ||
var useSpline = easing === 'spline'; | ||
var ondestroy = function() { | ||
self._clipCount--; | ||
if (self._clipCount === 0) { | ||
// Clear all tracks | ||
self._tracks = {}; | ||
var len = self._doneList.length; | ||
for (var i = 0; i < len; i++) { | ||
self._doneList[i].call(self); | ||
} | ||
} | ||
}; | ||
var createTrackClip = function(keyframes, propName) { | ||
var trackLen = keyframes.length; | ||
if (!trackLen) { | ||
return; | ||
} | ||
// Guess data type | ||
var firstVal = keyframes[0].value; | ||
var isValueArray = _isArrayLike(firstVal); | ||
var isValueColor = false; | ||
// For vertices morphing | ||
var arrDim = ( | ||
isValueArray | ||
&& _isArrayLike(firstVal[0]) | ||
) | ||
? 2 : 1; | ||
// Sort keyframe as ascending | ||
keyframes.sort(function(a, b) { | ||
return a.time - b.time; | ||
}); | ||
var trackMaxTime; | ||
if (trackLen) { | ||
trackMaxTime = keyframes[trackLen-1].time; | ||
}else{ | ||
return; | ||
} | ||
// Percents of each keyframe | ||
var kfPercents = []; | ||
// Value of each keyframe | ||
var kfValues = []; | ||
for (var i = 0; i < trackLen; i++) { | ||
kfPercents.push(keyframes[i].time / trackMaxTime); | ||
// Assume value is a color when it is a string | ||
var value = keyframes[i].value; | ||
if (typeof(value) == 'string') { | ||
value = color.toArray(value); | ||
if (value.length === 0) { // Invalid color | ||
value[0] = value[1] = value[2] = 0; | ||
value[3] = 1; | ||
} | ||
isValueColor = true; | ||
} | ||
kfValues.push(value); | ||
} | ||
// Cache the key of last frame to speed up when | ||
// animation playback is sequency | ||
var cacheKey = 0; | ||
var cachePercent = 0; | ||
var start; | ||
var i, w; | ||
var p0, p1, p2, p3; | ||
if (isValueColor) { | ||
var rgba = [0, 0, 0, 0]; | ||
} | ||
var onframe = function(target, percent) { | ||
// Find the range keyframes | ||
// kf1-----kf2---------current--------kf3 | ||
// find kf2 and kf3 and do interpolation | ||
if (percent < cachePercent) { | ||
// Start from next key | ||
start = Math.min(cacheKey + 1, trackLen - 1); | ||
for (i = start; i >= 0; i--) { | ||
if (kfPercents[i] <= percent) { | ||
break; | ||
} | ||
} | ||
i = Math.min(i, trackLen-2); | ||
} else { | ||
for (i = cacheKey; i < trackLen; i++) { | ||
if (kfPercents[i] > percent) { | ||
break; | ||
} | ||
} | ||
i = Math.min(i-1, trackLen-2); | ||
} | ||
cacheKey = i; | ||
cachePercent = percent; | ||
var range = (kfPercents[i+1] - kfPercents[i]); | ||
if (range === 0) { | ||
return; | ||
} else { | ||
w = (percent - kfPercents[i]) / range; | ||
} | ||
if (useSpline) { | ||
p1 = kfValues[i]; | ||
p0 = kfValues[i === 0 ? i : i - 1]; | ||
p2 = kfValues[i > trackLen - 2 ? trackLen - 1 : i + 1]; | ||
p3 = kfValues[i > trackLen - 3 ? trackLen - 1 : i + 2]; | ||
if (isValueArray) { | ||
_catmullRomInterpolateArray( | ||
p0, p1, p2, p3, w, w*w, w*w*w, | ||
getter(target, propName), | ||
arrDim | ||
); | ||
} else { | ||
var value; | ||
if (isValueColor) { | ||
value = _catmullRomInterpolateArray( | ||
p0, p1, p2, p3, w, w*w, w*w*w, | ||
rgba, 1 | ||
); | ||
value = rgba2String(rgba); | ||
} else { | ||
value = _catmullRomInterpolate( | ||
p0, p1, p2, p3, w, w*w, w*w*w | ||
); | ||
} | ||
setter( | ||
target, | ||
propName, | ||
value | ||
); | ||
} | ||
} else { | ||
if (isValueArray) { | ||
_interpolateArray( | ||
kfValues[i], kfValues[i+1], w, | ||
getter(target, propName), | ||
arrDim | ||
); | ||
} else { | ||
var value; | ||
if (isValueColor) { | ||
_interpolateArray( | ||
kfValues[i], kfValues[i+1], w, | ||
rgba, 1 | ||
); | ||
value = rgba2String(rgba); | ||
} else { | ||
value = _interpolateNumber(kfValues[i], kfValues[i+1], w); | ||
} | ||
setter( | ||
target, | ||
propName, | ||
value | ||
); | ||
} | ||
} | ||
for (i = 0; i < onFrameListLen; i++) { | ||
self._onframeList[i](target, percent); | ||
} | ||
}; | ||
var clip = new Clip({ | ||
target : self._target, | ||
life : trackMaxTime, | ||
loop : self._loop, | ||
delay : self._delay, | ||
onframe : onframe, | ||
ondestroy : ondestroy | ||
}); | ||
if (easing && easing !== 'spline') { | ||
clip.easing = easing; | ||
} | ||
self._clipList.push(clip); | ||
self._clipCount++; | ||
self.animation.add(clip); | ||
}; | ||
for (var propName in this._tracks) { | ||
createTrackClip(this._tracks[propName], propName); | ||
} | ||
return this; | ||
}, | ||
stop : function() { | ||
for (var i = 0; i < this._clipList.length; i++) { | ||
var clip = this._clipList[i]; | ||
this.animation.remove(clip); | ||
} | ||
this._clipList = []; | ||
}, | ||
delay : function(time){ | ||
this._delay = time; | ||
return this; | ||
}, | ||
done : function(func) { | ||
this._doneList.push(func); | ||
return this; | ||
} | ||
}; | ||
return Animation; | ||
} | ||
); | ||
return Animation; | ||
}); |
@@ -12,93 +12,97 @@ /** | ||
* @config onrestart(optional) | ||
* | ||
* TODO pause | ||
*/ | ||
define( | ||
function(require) { | ||
define(function(require) { | ||
var Easing = require('./easing'); | ||
var easingFuncs = require('./easing'); | ||
function Clip(options) { | ||
function Clip(options) { | ||
this._targetPool = options.target || {}; | ||
if (!(this._targetPool instanceof Array)) { | ||
this._targetPool = [this._targetPool]; | ||
} | ||
this._target = options.target; | ||
//生命周期 | ||
this._life = options.life || 1000; | ||
//延时 | ||
this._delay = options.delay || 0; | ||
//开始时间 | ||
this._startTime = new Date().getTime() + this._delay;//单位毫秒 | ||
// 生命周期 | ||
this._life = options.life || 1000; | ||
// 延时 | ||
this._delay = options.delay || 0; | ||
// 开始时间 | ||
// this._startTime = new Date().getTime() + this._delay;// 单位毫秒 | ||
this._initialized = false; | ||
//结束时间 | ||
this._endTime = this._startTime + this._life * 1000; | ||
// 是否循环 | ||
this.loop = options.loop == null ? false : options.loop; | ||
//是否循环 | ||
this.loop = typeof options.loop == 'undefined' | ||
? false : options.loop; | ||
this.gap = options.gap || 0; | ||
this.gap = options.gap || 0; | ||
this.easing = options.easing || 'Linear'; | ||
this.easing = options.easing || 'Linear'; | ||
this.onframe = options.onframe; | ||
this.ondestroy = options.ondestroy; | ||
this.onrestart = options.onrestart; | ||
}; | ||
this.onframe = options.onframe; | ||
this.ondestroy = options.ondestroy; | ||
this.onrestart = options.onrestart; | ||
} | ||
Clip.prototype = { | ||
Clip.prototype = { | ||
step : function (time) { | ||
var percent = (time - this._startTime) / this._life; | ||
constructor: Clip, | ||
//还没开始 | ||
if (percent < 0) { | ||
return; | ||
} | ||
step: function (time) { | ||
// Set startTime on first step, or _startTime may has milleseconds different between clips | ||
// PENDING | ||
if (!this._initialized) { | ||
this._startTime = new Date().getTime() + this._delay; | ||
this._initialized = true; | ||
} | ||
percent = Math.min(percent, 1); | ||
var percent = (time - this._startTime) / this._life; | ||
var easingFunc = typeof this.easing == 'string' | ||
? Easing[this.easing] | ||
: this.easing; | ||
var schedule = typeof easingFunc === 'function' | ||
? easingFunc(percent) | ||
: percent; | ||
// 还没开始 | ||
if (percent < 0) { | ||
return; | ||
} | ||
this.fire('frame', schedule); | ||
percent = Math.min(percent, 1); | ||
// 结束 | ||
if (percent == 1) { | ||
if (this.loop) { | ||
this.restart(); | ||
// 重新开始周期 | ||
// 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件 | ||
return 'restart'; | ||
var easing = this.easing; | ||
var easingFunc = typeof easing == 'string' ? easingFuncs[easing] : easing; | ||
var schedule = typeof easingFunc === 'function' | ||
? easingFunc(percent) | ||
: percent; | ||
} | ||
// 动画完成将这个控制器标识为待删除 | ||
// 在Animation.update中进行批量删除 | ||
this._needsRemove = true; | ||
return 'destroy'; | ||
this.fire('frame', schedule); | ||
// 结束 | ||
if (percent == 1) { | ||
if (this.loop) { | ||
this.restart(); | ||
// 重新开始周期 | ||
// 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件 | ||
return 'restart'; | ||
} | ||
return null; | ||
}, | ||
restart : function() { | ||
var time = new Date().getTime(); | ||
var remainder = (time - this._startTime) % this._life; | ||
this._startTime = new Date().getTime() - remainder + this.gap; | ||
}, | ||
fire : function(eventType, arg) { | ||
for (var i = 0, len = this._targetPool.length; i < len; i++) { | ||
if (this['on' + eventType]) { | ||
this['on' + eventType](this._targetPool[i], arg); | ||
} | ||
} | ||
}, | ||
constructor: Clip | ||
}; | ||
return Clip; | ||
} | ||
); | ||
// 动画完成将这个控制器标识为待删除 | ||
// 在Animation.update中进行批量删除 | ||
this._needsRemove = true; | ||
return 'destroy'; | ||
} | ||
return null; | ||
}, | ||
restart: function() { | ||
var time = new Date().getTime(); | ||
var remainder = (time - this._startTime) % this._life; | ||
this._startTime = new Date().getTime() - remainder + this.gap; | ||
this._needsRemove = false; | ||
}, | ||
fire: function(eventType, arg) { | ||
eventType = 'on' + eventType; | ||
if (this[eventType]) { | ||
this[eventType](this._target, arg); | ||
} | ||
} | ||
}; | ||
return Clip; | ||
}); |
/** | ||
* 缓动代码来自 https://github.com/sole/tween.js/blob/master/src/Tween.js | ||
* author: lang(shenyi01@baidu.com) | ||
* @see http://sole.github.io/tween.js/examples/03_graphs.html | ||
* @exports zrender/animation/easing | ||
*/ | ||
define( | ||
function() { | ||
var Easing = { | ||
// 线性 | ||
Linear: function(k) { | ||
return k; | ||
}, | ||
define(function () { | ||
var easing = { | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
linear: function (k) { | ||
return k; | ||
}, | ||
// 二次方的缓动(t^2) | ||
QuadraticIn: function(k) { | ||
return k * k; | ||
}, | ||
QuadraticOut: function(k) { | ||
return k * (2 - k); | ||
}, | ||
QuadraticInOut: function(k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k; | ||
} | ||
return - 0.5 * (--k * (k - 2) - 1); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quadraticIn: function (k) { | ||
return k * k; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quadraticOut: function (k) { | ||
return k * (2 - k); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quadraticInOut: function (k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k; | ||
} | ||
return -0.5 * (--k * (k - 2) - 1); | ||
}, | ||
// 三次方的缓动(t^3) | ||
CubicIn: function(k) { | ||
return k * k * k; | ||
}, | ||
CubicOut: function(k) { | ||
return --k * k * k + 1; | ||
}, | ||
CubicInOut: function(k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k * k; | ||
} | ||
return 0.5 * ((k -= 2) * k * k + 2); | ||
}, | ||
// 三次方的缓动(t^3) | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
cubicIn: function (k) { | ||
return k * k * k; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
cubicOut: function (k) { | ||
return --k * k * k + 1; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
cubicInOut: function (k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k * k; | ||
} | ||
return 0.5 * ((k -= 2) * k * k + 2); | ||
}, | ||
// 四次方的缓动(t^4) | ||
QuarticIn: function(k) { | ||
return k * k * k * k; | ||
}, | ||
QuarticOut: function(k) { | ||
return 1 - (--k * k * k * k); | ||
}, | ||
QuarticInOut: function(k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k * k * k; | ||
} | ||
return - 0.5 * ((k -= 2) * k * k * k - 2); | ||
}, | ||
// 四次方的缓动(t^4) | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quarticIn: function (k) { | ||
return k * k * k * k; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quarticOut: function (k) { | ||
return 1 - (--k * k * k * k); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quarticInOut: function (k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k * k * k; | ||
} | ||
return -0.5 * ((k -= 2) * k * k * k - 2); | ||
}, | ||
// 五次方的缓动(t^5) | ||
QuinticIn: function(k) { | ||
return k * k * k * k * k; | ||
}, | ||
// 五次方的缓动(t^5) | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quinticIn: function (k) { | ||
return k * k * k * k * k; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quinticOut: function (k) { | ||
return --k * k * k * k * k + 1; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
quinticInOut: function (k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k * k * k * k; | ||
} | ||
return 0.5 * ((k -= 2) * k * k * k * k + 2); | ||
}, | ||
QuinticOut: function(k) { | ||
return --k * k * k * k * k + 1; | ||
}, | ||
QuinticInOut: function(k) { | ||
if ((k *= 2) < 1) { | ||
return 0.5 * k * k * k * k * k; | ||
} | ||
return 0.5 * ((k -= 2) * k * k * k * k + 2); | ||
}, | ||
// 正弦曲线的缓动(sin(t)) | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
sinusoidalIn: function (k) { | ||
return 1 - Math.cos(k * Math.PI / 2); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
sinusoidalOut: function (k) { | ||
return Math.sin(k * Math.PI / 2); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
sinusoidalInOut: function (k) { | ||
return 0.5 * (1 - Math.cos(Math.PI * k)); | ||
}, | ||
// 正弦曲线的缓动(sin(t)) | ||
SinusoidalIn: function(k) { | ||
return 1 - Math.cos(k * Math.PI / 2); | ||
}, | ||
SinusoidalOut: function(k) { | ||
return Math.sin(k * Math.PI / 2); | ||
}, | ||
SinusoidalInOut: function(k) { | ||
return 0.5 * (1 - Math.cos(Math.PI * k)); | ||
}, | ||
// 指数曲线的缓动(2^t) | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
exponentialIn: function (k) { | ||
return k === 0 ? 0 : Math.pow(1024, k - 1); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
exponentialOut: function (k) { | ||
return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
exponentialInOut: function (k) { | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if ((k *= 2) < 1) { | ||
return 0.5 * Math.pow(1024, k - 1); | ||
} | ||
return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2); | ||
}, | ||
// 指数曲线的缓动(2^t) | ||
ExponentialIn: function(k) { | ||
return k === 0 ? 0 : Math.pow(1024, k - 1); | ||
}, | ||
ExponentialOut: function(k) { | ||
return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k); | ||
}, | ||
ExponentialInOut: function(k) { | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if ((k *= 2) < 1) { | ||
return 0.5 * Math.pow(1024, k - 1); | ||
} | ||
return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2); | ||
}, | ||
// 圆形曲线的缓动(sqrt(1-t^2)) | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
circularIn: function (k) { | ||
return 1 - Math.sqrt(1 - k * k); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
circularOut: function (k) { | ||
return Math.sqrt(1 - (--k * k)); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
circularInOut: function (k) { | ||
if ((k *= 2) < 1) { | ||
return -0.5 * (Math.sqrt(1 - k * k) - 1); | ||
} | ||
return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); | ||
}, | ||
// 圆形曲线的缓动(sqrt(1-t^2)) | ||
CircularIn: function(k) { | ||
return 1 - Math.sqrt(1 - k * k); | ||
}, | ||
CircularOut: function(k) { | ||
return Math.sqrt(1 - (--k * k)); | ||
}, | ||
CircularInOut: function(k) { | ||
if ((k *= 2) < 1) { | ||
return - 0.5 * (Math.sqrt(1 - k * k) - 1); | ||
} | ||
return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); | ||
}, | ||
// 创建类似于弹簧在停止前来回振荡的动画 | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
elasticIn: function (k) { | ||
var s; | ||
var a = 0.1; | ||
var p = 0.4; | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if (!a || a < 1) { | ||
a = 1; s = p / 4; | ||
} | ||
else { | ||
s = p * Math.asin(1 / a) / (2 * Math.PI); | ||
} | ||
return -(a * Math.pow(2, 10 * (k -= 1)) * | ||
Math.sin((k - s) * (2 * Math.PI) / p)); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
elasticOut: function (k) { | ||
var s; | ||
var a = 0.1; | ||
var p = 0.4; | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if (!a || a < 1) { | ||
a = 1; s = p / 4; | ||
} | ||
else { | ||
s = p * Math.asin(1 / a) / (2 * Math.PI); | ||
} | ||
return (a * Math.pow(2, -10 * k) * | ||
Math.sin((k - s) * (2 * Math.PI) / p) + 1); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
elasticInOut: function (k) { | ||
var s; | ||
var a = 0.1; | ||
var p = 0.4; | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if (!a || a < 1) { | ||
a = 1; s = p / 4; | ||
} | ||
else { | ||
s = p * Math.asin(1 / a) / (2 * Math.PI); | ||
} | ||
if ((k *= 2) < 1) { | ||
return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) | ||
* Math.sin((k - s) * (2 * Math.PI) / p)); | ||
} | ||
return a * Math.pow(2, -10 * (k -= 1)) | ||
* Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1; | ||
// 创建类似于弹簧在停止前来回振荡的动画 | ||
ElasticIn: function(k) { | ||
var s, a = 0.1, p = 0.4; | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if (!a || a < 1) { | ||
a = 1; s = p / 4; | ||
}else{ | ||
s = p * Math.asin(1 / a) / (2 * Math.PI); | ||
} | ||
return - (a * Math.pow(2, 10 * (k -= 1)) * | ||
Math.sin((k - s) * (2 * Math.PI) / p)); | ||
}, | ||
ElasticOut: function(k) { | ||
var s, a = 0.1, p = 0.4; | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if (!a || a < 1) { | ||
a = 1; s = p / 4; | ||
} | ||
else{ | ||
s = p * Math.asin(1 / a) / (2 * Math.PI); | ||
} | ||
return (a * Math.pow(2, - 10 * k) * | ||
Math.sin((k - s) * (2 * Math.PI) / p) + 1); | ||
}, | ||
ElasticInOut: function(k) { | ||
var s, a = 0.1, p = 0.4; | ||
if (k === 0) { | ||
return 0; | ||
} | ||
if (k === 1) { | ||
return 1; | ||
} | ||
if (!a || a < 1) { | ||
a = 1; s = p / 4; | ||
} | ||
else{ | ||
s = p * Math.asin(1 / a) / (2 * Math.PI); | ||
} | ||
if ((k *= 2) < 1) { | ||
return - 0.5 * (a * Math.pow(2, 10 * (k -= 1)) | ||
* Math.sin((k - s) * (2 * Math.PI) / p)); | ||
} | ||
return a * Math.pow(2, -10 * (k -= 1)) | ||
* Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1; | ||
}, | ||
}, | ||
// 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动 | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
backIn: function (k) { | ||
var s = 1.70158; | ||
return k * k * ((s + 1) * k - s); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
backOut: function (k) { | ||
var s = 1.70158; | ||
return --k * k * ((s + 1) * k + s) + 1; | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
backInOut: function (k) { | ||
var s = 1.70158 * 1.525; | ||
if ((k *= 2) < 1) { | ||
return 0.5 * (k * k * ((s + 1) * k - s)); | ||
} | ||
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); | ||
}, | ||
// 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动 | ||
BackIn: function(k) { | ||
var s = 1.70158; | ||
return k * k * ((s + 1) * k - s); | ||
}, | ||
BackOut: function(k) { | ||
var s = 1.70158; | ||
return --k * k * ((s + 1) * k + s) + 1; | ||
}, | ||
BackInOut: function(k) { | ||
var s = 1.70158 * 1.525; | ||
if ((k *= 2) < 1) { | ||
return 0.5 * (k * k * ((s + 1) * k - s)); | ||
} | ||
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); | ||
}, | ||
// 创建弹跳效果 | ||
BounceIn: function(k) { | ||
return 1 - Easing.BounceOut(1 - k); | ||
}, | ||
BounceOut: function(k) { | ||
if (k < (1 / 2.75)) { | ||
return 7.5625 * k * k; | ||
} | ||
else if (k < (2 / 2.75)) { | ||
return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; | ||
} else if (k < (2.5 / 2.75)) { | ||
return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; | ||
} else { | ||
return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; | ||
} | ||
}, | ||
BounceInOut: function(k) { | ||
if (k < 0.5) { | ||
return Easing.BounceIn(k * 2) * 0.5; | ||
} | ||
return Easing.BounceOut(k * 2 - 1) * 0.5 + 0.5; | ||
// 创建弹跳效果 | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
bounceIn: function (k) { | ||
return 1 - easing.bounceOut(1 - k); | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
bounceOut: function (k) { | ||
if (k < (1 / 2.75)) { | ||
return 7.5625 * k * k; | ||
} | ||
}; | ||
return Easing; | ||
else if (k < (2 / 2.75)) { | ||
return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; | ||
} | ||
else if (k < (2.5 / 2.75)) { | ||
return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; | ||
} | ||
else { | ||
return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; | ||
} | ||
}, | ||
/** | ||
* @param {number} k | ||
* @return {number} | ||
*/ | ||
bounceInOut: function (k) { | ||
if (k < 0.5) { | ||
return easing.bounceIn(k * 2) * 0.5; | ||
} | ||
return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5; | ||
} | ||
} | ||
); | ||
return easing; | ||
}); | ||
@@ -1,39 +0,13 @@ | ||
/** | ||
* zrender: config默认配置项 | ||
* | ||
* @desc zrender是一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型。 | ||
* @author Kener (@Kener-林峰, linzhifeng@baidu.com) | ||
* | ||
*/ | ||
define( | ||
{ | ||
EVENT : { // 支持事件列表 | ||
RESIZE : 'resize', // 窗口大小变化 | ||
CLICK : 'click', // 鼠标按钮被(手指)按下,事件对象是:目标图形元素或空 | ||
MOUSEWHEEL : 'mousewheel', // 鼠标滚轮变化,事件对象是:目标图形元素或空 | ||
MOUSEMOVE : 'mousemove', // 鼠标(手指)被移动,事件对象是:目标图形元素或空 | ||
MOUSEOVER : 'mouseover', // 鼠标移到某图形元素之上,事件对象是:目标图形元素 | ||
MOUSEOUT : 'mouseout', // 鼠标从某图形元素移开,事件对象是:目标图形元素 | ||
MOUSEDOWN : 'mousedown', // 鼠标按钮(手指)被按下,事件对象是:目标图形元素或空 | ||
MOUSEUP : 'mouseup', // 鼠标按键(手指)被松开,事件对象是:目标图形元素或空 | ||
// | ||
GLOBALOUT : 'globalout', // 全局离开,MOUSEOUT触发比较频繁,一次离开优化绑定 | ||
// 一次成功元素拖拽的行为事件过程是: | ||
// dragstart > dragenter > dragover [> dragleave] > drop > dragend | ||
DRAGSTART : 'dragstart', // 开始拖拽时触发,事件对象是:被拖拽图形元素 | ||
DRAGEND : 'dragend', // 拖拽完毕时触发(在drop之后触发),事件对象是:被拖拽图形元素 | ||
DRAGENTER : 'dragenter', // 拖拽图形元素进入目标图形元素时触发,事件对象是:目标图形元素 | ||
DRAGOVER : 'dragover', // 拖拽图形元素在目标图形元素上移动时触发,事件对象是:目标图形元素 | ||
DRAGLEAVE : 'dragleave', // 拖拽图形元素离开目标图形元素时触发,事件对象是:目标图形元素 | ||
DROP : 'drop', // 拖拽图形元素放在目标图形元素内时触发,事件对象是:目标图形元素 | ||
touchClickDelay : 300 // touch end - start < delay is click | ||
}, | ||
// 是否异常捕获 | ||
catchBrushException: false, | ||
define(function () { | ||
var dpr = 1; | ||
// If in browser environment | ||
if (typeof window !== 'undefined') { | ||
dpr = Math.max(window.devicePixelRatio || 1, 1); | ||
} | ||
/** | ||
* config默认配置项 | ||
* @exports zrender/config | ||
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com) | ||
*/ | ||
var config = { | ||
/** | ||
@@ -45,4 +19,9 @@ * debug日志选项:catchBrushException为true下有效 | ||
*/ | ||
debugMode: 0 | ||
} | ||
); | ||
debugMode: 0, | ||
// retina 屏幕优化 | ||
devicePixelRatio: dpr | ||
}; | ||
return config; | ||
}); | ||
/** | ||
* Handler控制模块 | ||
* | ||
* @author Kener (@Kener-林峰, linzhifeng@baidu.com) | ||
* @module zrender/Handler | ||
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com) | ||
* errorrik (errorrik@gmail.com) | ||
* pissang (shenyi.914@gmail.com) | ||
*/ | ||
define(function (require) { | ||
define( | ||
function (require) { | ||
'use strict'; | ||
'use strict'; | ||
var env = require('./core/env'); | ||
var eventTool = require('./core/event'); | ||
var util = require('./core/util'); | ||
var Draggable = require('./mixin/Draggable'); | ||
var GestureMgr = require('./core/GestureMgr'); | ||
var config = require('./config'); | ||
var env = require('./tool/env'); | ||
var eventTool = require('./tool/event'); | ||
var util = require('./tool/util'); | ||
var EVENT = config.EVENT; | ||
var Eventful = require('./mixin/Eventful'); | ||
var domHandlerNames = [ | ||
'resize', 'click', | ||
'mousewheel', 'mousemove', 'mouseout', 'mouseup', 'mousedown', | ||
'touchstart', 'touchend', 'touchmove' | ||
]; | ||
var domHandlerNames = [ | ||
'click', 'dblclick', | ||
'mousewheel', 'mousemove', 'mouseout', 'mouseup', 'mousedown' | ||
]; | ||
var domHandlers = { | ||
/** | ||
* 窗口大小改变响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
resize: function (event) { | ||
event = event || window.event; | ||
this._lastHover = null; | ||
this._isMouseDown = 0; | ||
// 分发config.EVENT.RESIZE事件,global | ||
this.dispatch(EVENT.RESIZE, event); | ||
}, | ||
var touchHandlerNames = [ | ||
'touchstart', 'touchend', 'touchmove' | ||
]; | ||
/** | ||
* 点击响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
click: function (event) { | ||
event = this._zrenderEventFixed(event); | ||
var TOUCH_CLICK_DELAY = 300; | ||
//分发config.EVENT.CLICK事件 | ||
var _lastHover = this._lastHover; | ||
if (( _lastHover && _lastHover.clickable ) | ||
|| !_lastHover | ||
) { | ||
this._dispatchAgency(_lastHover, EVENT.CLICK, event); | ||
} | ||
// touch指尖错觉的尝试偏移量配置 | ||
// var MOBILE_TOUCH_OFFSETS = [ | ||
// { x: 10 }, | ||
// { x: -20 }, | ||
// { x: 10, y: 10 }, | ||
// { y: -20 } | ||
// ]; | ||
this._mousemoveHandler(event); | ||
}, | ||
var addEventListener = eventTool.addEventListener; | ||
var removeEventListener = eventTool.removeEventListener; | ||
var normalizeEvent = eventTool.normalizeEvent; | ||
/** | ||
* 鼠标滚轮响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
mousewheel: function (event) { | ||
event = this._zrenderEventFixed(event); | ||
function proxyEventName(name) { | ||
return '_' + name + 'Handler'; | ||
} | ||
//分发config.EVENT.MOUSEWHEEL事件 | ||
this._dispatchAgency(this._lastHover, EVENT.MOUSEWHEEL, event); | ||
this._mousemoveHandler(event); | ||
}, | ||
function makeEventPacket(eveType, target, event) { | ||
return { | ||
type: eveType, | ||
event: event, | ||
target: target, | ||
cancelBubble: false, | ||
offsetX: event.zrX, | ||
offsetY: event.zrY, | ||
gestureEvent: event.gestureEvent, | ||
pinchX: event.pinchX, | ||
pinchY: event.pinchY, | ||
pinchScale: event.pinchScale, | ||
wheelDelta: event.zrDelta | ||
}; | ||
} | ||
/** | ||
* 鼠标(手指)移动响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
mousemove: function (event) { | ||
if (this.painter.isLoading()) { | ||
return; | ||
} | ||
var domHandlers = { | ||
/** | ||
* Mouse move handler | ||
* @inner | ||
* @param {Event} event | ||
*/ | ||
mousemove: function (event) { | ||
event = normalizeEvent(this.root, event); | ||
event = this._zrenderEventFixed(event); | ||
this._lastX = this._mouseX; | ||
this._lastY = this._mouseY; | ||
this._mouseX = eventTool.getX(event); | ||
this._mouseY = eventTool.getY(event); | ||
var x = event.zrX; | ||
var y = event.zrY; | ||
// 可能出现config.EVENT.DRAGSTART事件 | ||
// 避免手抖点击误认为拖拽 | ||
//if (this._mouseX - this._lastX > 1 || this._mouseY - this._lastY > 1) { | ||
this._processDragStart(event); | ||
//} | ||
this._hasfound = 0; | ||
this._event = event; | ||
this.storage.iterShape(this._findHover, { normal: 'down'}); | ||
var hovered = this._findHover(x, y, null); | ||
var lastHovered = this._hovered; | ||
// 找到的在迭代函数里做了处理,没找到得在迭代完后处理 | ||
if (!this._hasfound) { | ||
// 过滤首次拖拽产生的mouseout和dragLeave | ||
if (!this._draggingTarget | ||
|| (this._lastHover && this._lastHover != this._draggingTarget) | ||
) { | ||
// 可能出现config.EVENT.MOUSEOUT事件 | ||
this._processOutShape(event); | ||
this._hovered = hovered; | ||
// 可能出现config.EVENT.DRAGLEAVE事件 | ||
this._processDragLeave(event); | ||
} | ||
this.root.style.cursor = hovered ? hovered.cursor : this._defaultCursorStyle; | ||
// Mouse out on previous hovered element | ||
if (lastHovered && hovered !== lastHovered && lastHovered.__zr) { | ||
this._dispatchProxy(lastHovered, 'mouseout', event); | ||
} | ||
this._lastHover = null; | ||
this.storage.delHover(); | ||
this.painter.clearHover(); | ||
} | ||
//如果存在拖拽中元素,被拖拽的图形元素最后addHover | ||
if (this._draggingTarget) { | ||
this.storage.drift( | ||
this._draggingTarget.id, | ||
this._mouseX - this._lastX, | ||
this._mouseY - this._lastY | ||
); | ||
this.storage.addHover(this._draggingTarget); | ||
} | ||
// Mouse moving on one element | ||
this._dispatchProxy(hovered, 'mousemove', event); | ||
// set cursor for root element | ||
var cursor = 'default'; | ||
if (this._draggingTarget || (this._hasfound && this._lastHover.draggable)) { | ||
cursor = 'move'; | ||
} | ||
else if (this._hasfound && this._lastHover.clickable) { | ||
cursor = 'pointer'; | ||
} | ||
this.root.style.cursor = cursor; | ||
// Mouse over on a new element | ||
if (hovered && hovered !== lastHovered) { | ||
this._dispatchProxy(hovered, 'mouseover', event); | ||
} | ||
}, | ||
// 分发config.EVENT.MOUSEMOVE事件 | ||
this._dispatchAgency(this._lastHover, EVENT.MOUSEMOVE, event); | ||
/** | ||
* Mouse out handler | ||
* @inner | ||
* @param {Event} event | ||
*/ | ||
mouseout: function (event) { | ||
event = normalizeEvent(this.root, event); | ||
if (this._draggingTarget || this._hasfound || this.storage.hasHoverShape()) { | ||
this.painter.refreshHover(); | ||
} | ||
}, | ||
/** | ||
* 鼠标(手指)离开响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
mouseout: function (event) { | ||
event = this._zrenderEventFixed(event); | ||
var element = event.toElement || event.relatedTarget; | ||
if (element != this.root) { | ||
while (element && element.nodeType != 9) { | ||
// 忽略包含在root中的dom引起的mouseOut | ||
if (element == this.root) { | ||
this._mousemoveHandler(event); | ||
return; | ||
} | ||
element = element.parentNode; | ||
var element = event.toElement || event.relatedTarget; | ||
if (element != this.root) { | ||
while (element && element.nodeType != 9) { | ||
// 忽略包含在root中的dom引起的mouseOut | ||
if (element === this.root) { | ||
return; | ||
} | ||
} | ||
event.zrenderX = this._lastX; | ||
event.zrenderY = this._lastY; | ||
this.root.style.cursor = 'default'; | ||
this._isMouseDown = 0; | ||
this._processOutShape(event); | ||
this._processDrop(event); | ||
this._processDragEnd(event); | ||
if (!this.painter.isLoading()) { | ||
this.painter.refreshHover(); | ||
element = element.parentNode; | ||
} | ||
this.dispatch(EVENT.GLOBALOUT, event); | ||
}, | ||
} | ||
/** | ||
* 鼠标(手指)按下响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
mousedown: function (event) { | ||
if (this._lastDownButton == 2) { | ||
this._lastDownButton = event.button; | ||
this._mouseDownTarget = null; | ||
// 仅作为关闭右键菜单使用 | ||
return; | ||
} | ||
this._dispatchProxy(this._hovered, 'mouseout', event); | ||
this._lastMouseDownMoment = new Date(); | ||
event = this._zrenderEventFixed(event); | ||
this._isMouseDown = 1; | ||
this.trigger('globalout', { | ||
event: event | ||
}); | ||
}, | ||
//分发config.EVENT.MOUSEDOWN事件 | ||
this._mouseDownTarget = this._lastHover; | ||
this._dispatchAgency(this._lastHover, EVENT.MOUSEDOWN, event); | ||
this._lastDownButton = event.button; | ||
}, | ||
/** | ||
* Touch开始响应函数 | ||
* @inner | ||
* @param {Event} event | ||
*/ | ||
touchstart: function (event) { | ||
// FIXME | ||
// 移动端可能需要default行为,例如静态图表时。 | ||
// eventTool.stop(event);// 阻止浏览器默认事件,重要 | ||
event = normalizeEvent(this.root, event); | ||
/** | ||
* 鼠标(手指)抬起响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
mouseup:function (event) { | ||
event = this._zrenderEventFixed(event); | ||
this.root.style.cursor = 'default'; | ||
this._isMouseDown = 0; | ||
this._mouseDownTarget = null; | ||
this._lastTouchMoment = new Date(); | ||
//分发config.EVENT.MOUSEUP事件 | ||
this._dispatchAgency(this._lastHover, EVENT.MOUSEUP, event); | ||
this._processDrop(event); | ||
this._processDragEnd(event); | ||
}, | ||
processGesture(this, event, 'start'); | ||
/** | ||
* Touch开始响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
touchstart: function (event) { | ||
//eventTool.stop(event);// 阻止浏览器默认事件,重要 | ||
event = this._zrenderEventFixed(event, true); | ||
this._lastTouchMoment = new Date(); | ||
// 平板补充一次findHover | ||
// this._mobileFindFixed(event); | ||
// Trigger mousemove and mousedown | ||
this._mousemoveHandler(event); | ||
//平板补充一次findHover | ||
this._mobildFindFixed(event); | ||
this._mousedownHandler(event); | ||
}, | ||
this._mousedownHandler(event); | ||
}, | ||
/** | ||
* Touch移动响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
touchmove: function (event) { | ||
event = this._zrenderEventFixed(event, true); | ||
this._mousemoveHandler(event); | ||
if (this._isDragging) { | ||
eventTool.stop(event);// 阻止浏览器默认事件,重要 | ||
} | ||
}, | ||
/** | ||
* Touch移动响应函数 | ||
* @inner | ||
* @param {Event} event | ||
*/ | ||
touchmove: function (event) { | ||
// eventTool.stop(event);// 阻止浏览器默认事件,重要 | ||
event = normalizeEvent(this.root, event); | ||
/** | ||
* Touch结束响应函数 | ||
* | ||
* @param {event} event dom事件对象 | ||
*/ | ||
touchend: function (event) { | ||
//eventTool.stop(event);// 阻止浏览器默认事件,重要 | ||
event = this._zrenderEventFixed(event, true); | ||
this._mouseupHandler(event); | ||
processGesture(this, event, 'change'); | ||
if (new Date() - this._lastTouchMoment < EVENT.touchClickDelay) { | ||
this._mobildFindFixed(event); | ||
this._clickHandler(event); | ||
} | ||
this.painter.clearHover(); | ||
} | ||
}; | ||
// Mouse move should always be triggered no matter whether | ||
// there is gestrue event, because mouse move and pinch may | ||
// be used at the same time. | ||
this._mousemoveHandler(event); | ||
}, | ||
/** | ||
* bind一个参数的function | ||
* | ||
* Touch结束响应函数 | ||
* @inner | ||
* @param {Function} handler 要bind的function | ||
* @param {Object} context 运行时this环境 | ||
* @return {Function} | ||
* @param {Event} event | ||
*/ | ||
function bind1Arg( handler, context ) { | ||
return function ( e ) { | ||
return handler.call( context, e ); | ||
}; | ||
} | ||
touchend: function (event) { | ||
// eventTool.stop(event);// 阻止浏览器默认事件,重要 | ||
event = normalizeEvent(this.root, event); | ||
/** | ||
* 为控制类实例初始化dom 事件处理函数 | ||
* | ||
* @inner | ||
* @param {Handler} instance 控制类实例 | ||
*/ | ||
function initDomHandler( instance ) { | ||
var len = domHandlerNames.length; | ||
while ( len-- ) { | ||
var name = domHandlerNames[ len ]; | ||
instance[ '_' + name + 'Handler' ] = bind1Arg( domHandlers[ name ], instance ); | ||
processGesture(this, event, 'end'); | ||
this._mouseupHandler(event); | ||
// click event should always be triggered no matter whether | ||
// there is gestrue event. System click can not be prevented. | ||
if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) { | ||
// this._mobileFindFixed(event); | ||
this._clickHandler(event); | ||
} | ||
} | ||
}; | ||
/** | ||
* 控制类 (C) | ||
* | ||
* @param {HTMLElement} root 绘图区域 | ||
* @param {storage} storage Storage实例 | ||
* @param {painter} painter Painter实例 | ||
* | ||
* 分发事件支持详见config.EVENT | ||
*/ | ||
function Handler(root, storage, painter) { | ||
// 添加事件分发器特性 | ||
eventTool.Dispatcher.call(this); | ||
// Common handlers | ||
util.each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick'], function (name) { | ||
domHandlers[name] = function (event) { | ||
event = normalizeEvent(this.root, event); | ||
this.root = root; | ||
this.storage = storage; | ||
this.painter = painter; | ||
// Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover | ||
var hovered = this._findHover(event.zrX, event.zrY, null); | ||
this._dispatchProxy(hovered, name, event); | ||
}; | ||
}); | ||
// 各种事件标识的私有变量 | ||
// this._hasfound = false; //是否找到hover图形元素 | ||
// this._lastHover = null; //最后一个hover图形元素 | ||
// this._mouseDownTarget = null; | ||
// this._draggingTarget = null; //当前被拖拽的图形元素 | ||
// this._isMouseDown = false; | ||
// this._isDragging = false; | ||
// this._lastMouseDownMoment; | ||
// this._lastTouchMoment; | ||
// this._lastDownButton; | ||
function processGesture(zrHandler, event, stage) { | ||
var gestureMgr = zrHandler._gestureMgr; | ||
this._lastX = | ||
this._lastY = | ||
this._mouseX = | ||
this._mouseY = 0; | ||
stage === 'start' && gestureMgr.clear(); | ||
this._findHover = bind1Arg(findHover, this); | ||
this._domHover = painter.getDomHover(); | ||
initDomHandler(this); | ||
var gestureInfo = gestureMgr.recognize( | ||
event, | ||
zrHandler._findHover(event.zrX, event.zrY, null) | ||
); | ||
// 初始化,事件绑定,支持的所有事件都由如下原生事件计算得来 | ||
if (window.addEventListener) { | ||
window.addEventListener('resize', this._resizeHandler); | ||
if (env.os.tablet || env.os.phone) { | ||
// mobile支持 | ||
root.addEventListener('touchstart', this._touchstartHandler); | ||
root.addEventListener('touchmove', this._touchmoveHandler); | ||
root.addEventListener('touchend', this._touchendHandler); | ||
} | ||
else { | ||
// mobile的click/move/up/down自己模拟 | ||
root.addEventListener('click', this._clickHandler); | ||
root.addEventListener('mousewheel', this._mousewheelHandler); | ||
root.addEventListener('mousemove', this._mousemoveHandler); | ||
root.addEventListener('mousedown', this._mousedownHandler); | ||
root.addEventListener('mouseup', this._mouseupHandler); | ||
} | ||
root.addEventListener('DOMMouseScroll', this._mousewheelHandler); | ||
root.addEventListener('mouseout', this._mouseoutHandler); | ||
} | ||
else { | ||
window.attachEvent('onresize', this._resizeHandler); | ||
stage === 'end' && gestureMgr.clear(); | ||
root.attachEvent('onclick', this._clickHandler); | ||
root.attachEvent('onmousewheel', this._mousewheelHandler); | ||
root.attachEvent('onmousemove', this._mousemoveHandler); | ||
root.attachEvent('onmouseout', this._mouseoutHandler); | ||
root.attachEvent('onmousedown', this._mousedownHandler); | ||
root.attachEvent('onmouseup', this._mouseupHandler); | ||
} | ||
if (gestureInfo) { | ||
// eventTool.stop(event); | ||
var type = gestureInfo.type; | ||
event.gestureEvent = type; | ||
zrHandler._dispatchProxy(gestureInfo.target, type, gestureInfo.event); | ||
} | ||
} | ||
/** | ||
* 为控制类实例初始化dom 事件处理函数 | ||
* | ||
* @inner | ||
* @param {module:zrender/Handler} instance 控制类实例 | ||
*/ | ||
function initDomHandler(instance) { | ||
var handlerNames = domHandlerNames.concat(touchHandlerNames); | ||
var len = handlerNames.length; | ||
while (len--) { | ||
var name = handlerNames[len]; | ||
instance[proxyEventName(name)] = util.bind(domHandlers[name], instance); | ||
} | ||
} | ||
/** | ||
* @alias module:zrender/Handler | ||
* @constructor | ||
* @extends module:zrender/mixin/Eventful | ||
* @param {HTMLElement} root 绘图区域 | ||
* @param {module:zrender/Storage} storage Storage实例 | ||
* @param {module:zrender/Painter} painter Painter实例 | ||
*/ | ||
var Handler = function(root, storage, painter) { | ||
Eventful.call(this); | ||
this.root = root; | ||
this.storage = storage; | ||
this.painter = painter; | ||
/** | ||
* 自定义事件绑定 | ||
* @param {string} eventName 事件名称,resize,hover,drag,etc~ | ||
* @param {Function} handler 响应函数 | ||
* @private | ||
*/ | ||
Handler.prototype.on = function (eventName, handler) { | ||
this.bind(eventName, handler); | ||
return this; | ||
}; | ||
this._hovered; | ||
/** | ||
* 自定义事件解绑 | ||
* @param {string} eventName 事件名称,resize,hover,drag,etc~ | ||
* @param {Function} handler 响应函数 | ||
* @private | ||
*/ | ||
Handler.prototype.un = function (eventName, handler) { | ||
this.unbind(eventName, handler); | ||
return this; | ||
}; | ||
this._lastTouchMoment; | ||
/** | ||
* 事件触发 | ||
* @param {string} eventName 事件名称,resize,hover,drag,etc~ | ||
* @param {event=} eventArgs event dom事件对象 | ||
* @private | ||
*/ | ||
Handler.prototype.trigger = function (eventName, eventArgs) { | ||
switch (eventName) { | ||
case EVENT.RESIZE: | ||
case EVENT.CLICK: | ||
case EVENT.MOUSEWHEEL: | ||
case EVENT.MOUSEMOVE: | ||
case EVENT.MOUSEDOWN: | ||
case EVENT.MOUSEUP: | ||
case EVENT.MOUSEOUT: | ||
this['_' + eventName + 'Handler'](eventArgs); | ||
break; | ||
} | ||
}; | ||
this._lastX; | ||
/** | ||
* 释放 | ||
* @private | ||
*/ | ||
Handler.prototype.dispose = function () { | ||
var root = this.root; | ||
if (window.removeEventListener) { | ||
window.removeEventListener('resize', this._resizeHandler); | ||
if (env.os.tablet || env.os.phone) { | ||
// mobile支持 | ||
root.removeEventListener('touchstart', this._touchstartHandler); | ||
root.removeEventListener('touchmove', this._touchmoveHandler); | ||
root.removeEventListener('touchend', this._touchendHandler); | ||
} | ||
else { | ||
// mobile的click自己模拟 | ||
root.removeEventListener('click', this._clickHandler); | ||
root.removeEventListener('mousewheel', this._mousewheelHandler); | ||
root.removeEventListener('mousemove', this._mousemoveHandler); | ||
root.removeEventListener('mousedown', this._mousedownHandler); | ||
root.removeEventListener('mouseup', this._mouseupHandler); | ||
} | ||
root.removeEventListener('DOMMouseScroll', this._mousewheelHandler); | ||
root.removeEventListener('mouseout', this._mouseoutHandler); | ||
} | ||
else { | ||
window.detachEvent('onresize', this._resizeHandler); | ||
root.detachEvent('onclick', this._clickHandler); | ||
root.detachEvent('onmousewheel', this._mousewheelHandler); | ||
root.detachEvent('onmousemove', this._mousemoveHandler); | ||
root.detachEvent('onmouseout', this._mouseoutHandler); | ||
root.detachEvent('onmousedown', this._mousedownHandler); | ||
root.detachEvent('onmouseup', this._mouseupHandler); | ||
} | ||
this.root = | ||
this._domHover = | ||
this.storage = | ||
this.painter = null; | ||
this.un(); | ||
}; | ||
this._lastY; | ||
/** | ||
* 拖拽开始 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
*/ | ||
Handler.prototype._processDragStart = function (event) { | ||
var _lastHover = this._lastHover; | ||
this._defaultCursorStyle = 'default' | ||
/** | ||
* @private | ||
*/ | ||
this._gestureMgr = new GestureMgr(); | ||
if (this._isMouseDown | ||
&& _lastHover | ||
&& _lastHover.draggable | ||
&& !this._draggingTarget | ||
&& this._mouseDownTarget == _lastHover | ||
) { | ||
// 拖拽点击生效时长阀门,某些场景需要降低拖拽敏感度 | ||
if (_lastHover.dragEnableTime && | ||
new Date() - this._lastMouseDownMoment < _lastHover.dragEnableTime | ||
) { | ||
return; | ||
} | ||
initDomHandler(this); | ||
var _draggingTarget = _lastHover; | ||
this._draggingTarget = _draggingTarget; | ||
this._isDragging = 1; | ||
if (env.os.tablet || env.os.phone) { | ||
// mobile支持 | ||
// mobile的click/move/up/down自己模拟 | ||
util.each(touchHandlerNames, function (name) { | ||
addEventListener(root, name, this[proxyEventName(name)]); | ||
}, this); | ||
_draggingTarget.invisible = true; | ||
this.storage.mod(_draggingTarget.id); | ||
addEventListener(root, 'mouseout', this._mouseoutHandler); | ||
} | ||
else { | ||
util.each(domHandlerNames, function (name) { | ||
addEventListener(root, name, this[proxyEventName(name)]); | ||
}, this); | ||
// Firefox | ||
addEventListener(root, 'DOMMouseScroll', this._mousewheelHandler); | ||
} | ||
//分发config.EVENT.DRAGSTART事件 | ||
this._dispatchAgency( | ||
_draggingTarget, | ||
EVENT.DRAGSTART, | ||
event | ||
); | ||
this.painter.refresh(); | ||
} | ||
}; | ||
Draggable.call(this); | ||
}; | ||
/** | ||
* 拖拽进入目标元素 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
*/ | ||
Handler.prototype._processDragEnter = function (event) { | ||
if (this._draggingTarget) { | ||
//分发config.EVENT.DRAGENTER事件 | ||
this._dispatchAgency( | ||
this._lastHover, | ||
EVENT.DRAGENTER, | ||
event, | ||
this._draggingTarget | ||
); | ||
} | ||
}; | ||
Handler.prototype = { | ||
constructor: Handler, | ||
/** | ||
* 拖拽在目标元素上移动 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
* Resize | ||
*/ | ||
Handler.prototype._processDragOver = function (event) { | ||
if (this._draggingTarget) { | ||
//分发config.EVENT.DRAGOVER事件 | ||
this._dispatchAgency( | ||
this._lastHover, | ||
EVENT.DRAGOVER, | ||
event, | ||
this._draggingTarget | ||
); | ||
} | ||
}; | ||
resize: function (event) { | ||
this._hovered = null; | ||
}, | ||
/** | ||
* 拖拽离开目标元素 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
* Dispatch event | ||
* @param {string} eventName | ||
* @param {event=} eventArgs | ||
*/ | ||
Handler.prototype._processDragLeave = function (event) { | ||
if (this._draggingTarget) { | ||
//分发config.EVENT.DRAGLEAVE事件 | ||
this._dispatchAgency( | ||
this._lastHover, | ||
EVENT.DRAGLEAVE, | ||
event, | ||
this._draggingTarget | ||
); | ||
} | ||
}; | ||
dispatch: function (eventName, eventArgs) { | ||
var handler = this[proxyEventName(eventName)]; | ||
handler && handler(eventArgs); | ||
}, | ||
/** | ||
* 拖拽在目标元素上完成 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
* Dispose | ||
*/ | ||
Handler.prototype._processDrop = function (event) { | ||
if (this._draggingTarget) { | ||
this._draggingTarget.invisible = false; | ||
this.storage.mod(this._draggingTarget.id); | ||
this.painter.refresh(); | ||
dispose: function () { | ||
var root = this.root; | ||
//分发config.EVENT.DROP事件 | ||
this._dispatchAgency( | ||
this._lastHover, | ||
EVENT.DROP, | ||
event, | ||
this._draggingTarget | ||
); | ||
} | ||
}; | ||
var handlerNames = domHandlerNames.concat(touchHandlerNames); | ||
/** | ||
* 拖拽结束 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
*/ | ||
Handler.prototype._processDragEnd = function (event) { | ||
if (this._draggingTarget) { | ||
//分发config.EVENT.DRAGEND事件 | ||
this._dispatchAgency( | ||
this._draggingTarget, | ||
EVENT.DRAGEND, | ||
event | ||
); | ||
this._lastHover = null; | ||
for (var i = 0; i < handlerNames.length; i++) { | ||
var name = handlerNames[i]; | ||
removeEventListener(root, name, this[proxyEventName(name)]); | ||
} | ||
this._isDragging = 0; | ||
this._draggingTarget = null; | ||
}; | ||
// Firefox | ||
removeEventListener(root, 'DOMMouseScroll', this._mousewheelHandler); | ||
/** | ||
* 鼠标在某个图形元素上移动 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
*/ | ||
Handler.prototype._processOverShape = function (event) { | ||
//分发config.EVENT.MOUSEOVER事件 | ||
this._dispatchAgency(this._lastHover, EVENT.MOUSEOVER, event); | ||
}; | ||
this.root = | ||
this.storage = | ||
this.painter = null; | ||
}, | ||
/** | ||
* 鼠标离开某个图形元素 | ||
* | ||
* @private | ||
* @param {Object} event 事件对象 | ||
* 设置默认的cursor style | ||
* @param {string} cursorStyle 例如 crosshair | ||
*/ | ||
Handler.prototype._processOutShape = function (event) { | ||
//分发config.EVENT.MOUSEOUT事件 | ||
this._dispatchAgency(this._lastHover, EVENT.MOUSEOUT, event); | ||
}; | ||
setDefaultCursorStyle: function (cursorStyle) { | ||
this._defaultCursorStyle = cursorStyle; | ||
}, | ||
/** | ||
* 事件分发代理 | ||
* | ||
* | ||
* @private | ||
* @param {Object} targetShape 目标图形元素 | ||
* @param {Object} targetEl 目标图形元素 | ||
* @param {string} eventName 事件名称 | ||
* @param {Object} event 事件对象 | ||
* @param {Object=} draggedShape 拖拽事件特有,当前被拖拽图形元素 | ||
*/ | ||
Handler.prototype._dispatchAgency = function (targetShape, eventName, event, draggedShape) { | ||
_dispatchProxy: function (targetEl, eventName, event) { | ||
var eventHandler = 'on' + eventName; | ||
var eventPacket = { | ||
type : eventName, | ||
event : event, | ||
target : targetShape, | ||
cancelBubble: false | ||
}; | ||
var eventPacket = makeEventPacket(eventName, targetEl, event); | ||
var el = targetShape; | ||
var el = targetEl; | ||
if (draggedShape) { | ||
eventPacket.dragged = draggedShape; | ||
} | ||
while (el) { | ||
el[eventHandler] && el[eventHandler](eventPacket); | ||
el.dispatch(eventName, eventPacket); | ||
el[eventHandler] | ||
&& (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket)); | ||
el.trigger(eventName, eventPacket); | ||
el = el.parent; | ||
if (eventPacket.cancelBubble) { | ||
@@ -640,149 +370,57 @@ break; | ||
if (targetShape) { | ||
if (!eventPacket.cancelBubble) { | ||
// 冒泡到顶级 zrender 对象 | ||
if (!eventPacket.cancelBubble) { | ||
this.dispatch(eventName, eventPacket); | ||
} | ||
} | ||
else if (!draggedShape) { | ||
//无hover目标,无拖拽对象,原生事件分发 | ||
this.dispatch(eventName, { | ||
type: eventName, | ||
event: event | ||
this.trigger(eventName, eventPacket); | ||
// 分发事件到用户自定义层 | ||
// 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在 | ||
this.painter && this.painter.eachOtherLayer(function (layer) { | ||
if (typeof(layer[eventHandler]) == 'function') { | ||
layer[eventHandler].call(layer, eventPacket); | ||
} | ||
if (layer.trigger) { | ||
layer.trigger(eventName, eventPacket); | ||
} | ||
}); | ||
} | ||
}; | ||
// touch指尖错觉的尝试偏移量配置 | ||
var MOBILE_TOUCH_OFFSETS = [ | ||
{ x: 10 }, | ||
{ x: -20 }, | ||
{ x: 10, y: 10}, | ||
{ y: -20} | ||
]; | ||
}, | ||
// touch有指尖错觉,四向尝试,让touch上的点击更好触发事件 | ||
Handler.prototype._mobildFindFixed = function (event) { | ||
this._lastHover = null; | ||
this._mouseX = event.zrenderX; | ||
this._mouseY = event.zrenderY; | ||
this._event = event; | ||
this.storage.iterShape(this._findHover, { normal: 'down'}); | ||
for ( var i = 0; !this._lastHover && i < MOBILE_TOUCH_OFFSETS.length ; i++ ) { | ||
var offset = MOBILE_TOUCH_OFFSETS[ i ]; | ||
offset.x && ( this._mouseX += offset.x ); | ||
offset.y && ( this._mouseX += offset.y ); | ||
this.storage.iterShape(this._findHover, { normal: 'down'}); | ||
} | ||
if (this._lastHover) { | ||
event.zrenderX = this._mouseX; | ||
event.zrenderY = this._mouseY; | ||
} | ||
}; | ||
/** | ||
* 迭代函数,查找hover到的图形元素并即时做些事件分发 | ||
* | ||
* @private | ||
* @param {Object} e 图形元素 | ||
* @param {number} x | ||
* @param {number} y | ||
* @param {module:zrender/graphic/Displayable} exclude | ||
* @method | ||
*/ | ||
function findHover(shape) { | ||
if ( | ||
( this._draggingTarget && this._draggingTarget.id == shape.id ) //迭代到当前拖拽的图形上 | ||
|| shape.isSilent() // 打酱油的路过,啥都不响应的shape~ | ||
) { | ||
return false; | ||
} | ||
var event = this._event; | ||
if (shape.isCover(this._mouseX, this._mouseY)) { | ||
if (shape.hoverable) { | ||
this.storage.addHover(shape); | ||
_findHover: function(x, y, exclude) { | ||
var list = this.storage.getDisplayList(); | ||
for (var i = list.length - 1; i >= 0 ; i--) { | ||
if (!list[i].silent | ||
&& list[i] !== exclude | ||
&& isHover(list[i], x, y)) { | ||
return list[i]; | ||
} | ||
// 查找是否在 clipShape 中 | ||
var p = shape.parent; | ||
while (p) { | ||
if (p.clipShape && !p.clipShape.isCover(this._mouseX, this._mouseY)) { | ||
// 已经被祖先 clip 掉了 | ||
return false; | ||
} | ||
p = p.parent; | ||
} | ||
if (this._lastHover != shape) { | ||
this._processOutShape(event); | ||
//可能出现config.EVENT.DRAGLEAVE事件 | ||
this._processDragLeave(event); | ||
this._lastHover = shape; | ||
//可能出现config.EVENT.DRAGENTER事件 | ||
this._processDragEnter(event); | ||
} | ||
this._processOverShape(event); | ||
//可能出现config.EVENT.DRAGOVER | ||
this._processDragOver(event); | ||
this._hasfound = 1; | ||
return true; //找到则中断迭代查找 | ||
} | ||
return false; | ||
} | ||
}; | ||
/** | ||
* 如果存在第三方嵌入的一些dom触发的事件,或touch事件,需要转换一下事件坐标 | ||
* | ||
* @private | ||
*/ | ||
Handler.prototype._zrenderEventFixed = function (event, isTouch) { | ||
if ( event.zrenderFixed ) { | ||
return event; | ||
} | ||
if (!isTouch) { | ||
event = event || window.event; | ||
// 进入对象优先~ | ||
var target = event.toElement | ||
|| event.relatedTarget | ||
|| event.srcElement | ||
|| event.target; | ||
if (target && target != this._domHover) { | ||
event.zrenderX = (typeof event.offsetX != 'undefined' | ||
? event.offsetX | ||
: event.layerX) | ||
+ target.offsetLeft; | ||
event.zrenderY = (typeof event.offsetY != 'undefined' | ||
? event.offsetY | ||
: event.layerY) | ||
+ target.offsetTop; | ||
function isHover(displayable, x, y) { | ||
if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) { | ||
var p = displayable.parent; | ||
while (p) { | ||
if (p.clipPath && !p.clipPath.contain(x, y)) { | ||
// Clipped by parents | ||
return false; | ||
} | ||
p = p.parent; | ||
} | ||
else { | ||
var touch = event.type != 'touchend' | ||
? event.targetTouches[0] | ||
: event.changedTouches[0]; | ||
if (touch) { | ||
var rBounding = this.root.getBoundingClientRect(); | ||
// touch事件坐标是全屏的~ | ||
event.zrenderX = touch.clientX - rBounding.left; | ||
event.zrenderY = touch.clientY - rBounding.top; | ||
} | ||
} | ||
return true; | ||
} | ||
event.zrenderFixed = 1; | ||
return event; | ||
}; | ||
return false; | ||
} | ||
util.merge(Handler.prototype, eventTool.Dispatcher.prototype, true); | ||
util.mixin(Handler, Eventful); | ||
util.mixin(Handler, Draggable); | ||
return Handler; | ||
} | ||
); | ||
return Handler; | ||
}); |
1124
src/Painter.js
/** | ||
* Painter绘图模块 | ||
* | ||
* @author Kener (@Kener-林峰, linzhifeng@baidu.com) | ||
* Default canvas painter | ||
* @module zrender/Painter | ||
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com) | ||
* errorrik (errorrik@gmail.com) | ||
* pissang (https://www.github.com/pissang) | ||
*/ | ||
define(function (require) { | ||
'use strict'; | ||
var config = require('./config'); | ||
var util = require('./core/util'); | ||
var log = require('./core/log'); | ||
var BoundingRect = require('./core/BoundingRect'); | ||
var Layer = require('./Layer'); | ||
define( | ||
function (require) { | ||
function parseInt10(val) { | ||
return parseInt(val, 10); | ||
} | ||
'use strict'; | ||
function isLayerValid(layer) { | ||
if (!layer) { | ||
return false; | ||
} | ||
var config = require('./config'); | ||
var util = require('./tool/util'); | ||
var log = require('./tool/log'); | ||
var matrix = require('./tool/matrix'); | ||
var BaseLoadingEffect = require('./loadingEffect/Base'); | ||
if (layer.isBuildin) { | ||
return true; | ||
} | ||
// retina 屏幕优化 | ||
var devicePixelRatio = window.devicePixelRatio || 1; | ||
devicePixelRatio = Math.max(devicePixelRatio, 1); | ||
var vmlCanvasManager = window.G_vmlCanvasManager; | ||
/** | ||
* 返回false的方法,用于避免页面被选中 | ||
* | ||
* @inner | ||
*/ | ||
function returnFalse() { | ||
if (typeof(layer.resize) !== 'function' | ||
|| typeof(layer.refresh) !== 'function' | ||
) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
function preProcessLayer(layer) { | ||
layer.__unusedCount++; | ||
} | ||
function postProcessLayer(layer) { | ||
layer.__dirty = false; | ||
if (layer.__unusedCount == 1) { | ||
layer.clear(); | ||
} | ||
} | ||
var tmpRect = new BoundingRect(0, 0, 0, 0); | ||
var viewRect = new BoundingRect(0, 0, 0, 0); | ||
function isDisplayableCulled(el, width, height) { | ||
tmpRect.copy(el.getBoundingRect()); | ||
if (el.transform) { | ||
tmpRect.applyTransform(el.transform); | ||
} | ||
viewRect.width = width; | ||
viewRect.height = height; | ||
return !tmpRect.intersect(viewRect); | ||
} | ||
function isClipPathChanged(clipPaths, prevClipPaths) { | ||
if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) { | ||
return true; | ||
} | ||
for (var i = 0; i < clipPaths.length; i++) { | ||
if (clipPaths[i] !== prevClipPaths[i]) { | ||
return true; | ||
} | ||
} | ||
} | ||
function doClip(clipPaths, ctx) { | ||
for (var i = 0; i < clipPaths.length; i++) { | ||
var clipPath = clipPaths[i]; | ||
var m; | ||
if (clipPath.transform) { | ||
m = clipPath.transform; | ||
ctx.transform( | ||
m[0], m[1], | ||
m[2], m[3], | ||
m[4], m[5] | ||
); | ||
} | ||
var path = clipPath.path; | ||
path.beginPath(ctx); | ||
clipPath.buildPath(path, clipPath.shape); | ||
ctx.clip(); | ||
// Transform back | ||
if (clipPath.transform) { | ||
m = clipPath.invTransform; | ||
ctx.transform( | ||
m[0], m[1], | ||
m[2], m[3], | ||
m[4], m[5] | ||
); | ||
} | ||
} | ||
} | ||
/** | ||
* @alias module:zrender/Painter | ||
* @constructor | ||
* @param {HTMLElement} root 绘图容器 | ||
* @param {module:zrender/Storage} storage | ||
* @param {Ojbect} opts | ||
*/ | ||
var Painter = function (root, storage, opts) { | ||
var singleCanvas = !root.nodeName // In node ? | ||
|| root.nodeName.toUpperCase() === 'CANVAS'; | ||
opts = opts || {}; | ||
/** | ||
* 什么都不干的空方法 | ||
* | ||
* @inner | ||
* @type {number} | ||
*/ | ||
function doNothing() {} | ||
this.dpr = opts.devicePixelRatio || config.devicePixelRatio; | ||
/** | ||
* 绘图类 (V) | ||
* | ||
* @param {HTMLElement} root 绘图区域 | ||
* @param {storage} storage Storage实例 | ||
* @type {boolean} | ||
* @private | ||
*/ | ||
function Painter(root, storage) { | ||
this.root = root; | ||
this.storage = storage; | ||
this._singleCanvas = singleCanvas; | ||
/** | ||
* 绘图容器 | ||
* @type {HTMLElement} | ||
*/ | ||
this.root = root; | ||
var rootStyle = root.style; | ||
// In node environment using node-canvas | ||
if (rootStyle) { | ||
rootStyle['-webkit-tap-highlight-color'] = 'transparent'; | ||
rootStyle['-webkit-user-select'] = 'none'; | ||
rootStyle['user-select'] = 'none'; | ||
rootStyle['-webkit-touch-callout'] = 'none'; | ||
root.innerHTML = ''; | ||
this._width = this._getWidth(); // 宽,缓存记录 | ||
this._height = this._getHeight(); // 高,缓存记录 | ||
} | ||
/** | ||
* @type {module:zrender/Storage} | ||
*/ | ||
this.storage = storage; | ||
if (!singleCanvas) { | ||
var width = this._getWidth(); | ||
var height = this._getHeight(); | ||
this._width = width; | ||
this._height = height; | ||
var domRoot = document.createElement('div'); | ||
this._domRoot = domRoot; | ||
var domRootStyle = domRoot.style; | ||
//domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬 | ||
domRoot.style.position = 'relative'; | ||
domRoot.style.overflow = 'hidden'; | ||
domRoot.style.width = this._width + 'px'; | ||
domRoot.style.height = this._height + 'px'; | ||
// domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬 | ||
domRootStyle.position = 'relative'; | ||
domRootStyle.overflow = 'hidden'; | ||
domRootStyle.width = this._width + 'px'; | ||
domRootStyle.height = this._height + 'px'; | ||
root.appendChild(domRoot); | ||
/** | ||
* @type {Object.<key, module:zrender/Layer>} | ||
* @private | ||
*/ | ||
this._layers = {}; | ||
/** | ||
* @type {Array.<number>} | ||
* @private | ||
*/ | ||
this._zlevelList = []; | ||
} | ||
else { | ||
// Use canvas width and height directly | ||
var width = root.width; | ||
var height = root.height; | ||
this._width = width; | ||
this._height = height; | ||
this._layerConfig = {}; | ||
// Create layer if only one given canvas | ||
// Device pixel ratio is fixed to 1 because given canvas has its specified width and height | ||
var mainLayer = new Layer(root, this, 1); | ||
mainLayer.initContext(); | ||
// FIXME Use canvas width and height | ||
// mainLayer.resize(width, height); | ||
this._layers = { | ||
0: mainLayer | ||
}; | ||
this._zlevelList = [0]; | ||
} | ||
this._loadingEffect = new BaseLoadingEffect({}); | ||
this.shapeToImage = this._createShapeToImageProcessor(); | ||
this._layerConfig = {}; | ||
// 创建各层canvas | ||
// 背景 | ||
this._bgDom = createDom('bg', 'div', this); | ||
domRoot.appendChild(this._bgDom); | ||
this.pathToImage = this._createPathToImage(); | ||
}; | ||
// 高亮 | ||
var hoverLayer = new Layer('_zrender_hover_', this); | ||
this._layers['hover'] = hoverLayer; | ||
domRoot.appendChild(hoverLayer.dom); | ||
hoverLayer.initContext(); | ||
Painter.prototype = { | ||
hoverLayer.onselectstart = returnFalse; | ||
constructor: Painter, | ||
var me = this; | ||
this.updatePainter = function(shapeList, callback) { | ||
me.update(shapeList, callback); | ||
}; | ||
} | ||
/** | ||
* 首次绘图,创建各种dom和context | ||
* | ||
* @param {Function=} callback 绘画结束后的回调函数 | ||
* If painter use a single canvas | ||
* @return {boolean} | ||
*/ | ||
Painter.prototype.render = function (callback) { | ||
if (this.isLoading()) { | ||
this.hideLoading(); | ||
} | ||
// TODO | ||
this.refresh(callback, true); | ||
isSingleCanvas: function () { | ||
return this._singleCanvas; | ||
}, | ||
/** | ||
* @return {HTMLDivElement} | ||
*/ | ||
getViewportRoot: function () { | ||
return this._singleCanvas ? this._layers[0].dom : this._domRoot; | ||
}, | ||
return this; | ||
}; | ||
/** | ||
* 刷新 | ||
* | ||
* @param {Function=} callback 刷新结束后的回调函数 | ||
* @param {Boolean} paintAll 强制绘制所有shape | ||
* @param {boolean} [paintAll=false] 强制绘制所有displayable | ||
*/ | ||
Painter.prototype.refresh = function (callback, paintAll) { | ||
var list = this.storage.getShapeList(true); | ||
refresh: function (paintAll) { | ||
var list = this.storage.getDisplayList(true); | ||
var zlevelList = this._zlevelList; | ||
this._paintList(list, paintAll); | ||
if (typeof callback == 'function') { | ||
callback(); | ||
// Paint custum layers | ||
for (var i = 0; i < zlevelList.length; i++) { | ||
var z = zlevelList[i]; | ||
var layer = this._layers[z]; | ||
if (!layer.isBuildin && layer.refresh) { | ||
layer.refresh(); | ||
} | ||
} | ||
return this; | ||
}; | ||
}, | ||
Painter.prototype._paintList = function(list, paintAll) { | ||
_paintList: function (list, paintAll) { | ||
if (typeof(paintAll) == 'undefined') { | ||
if (paintAll == null) { | ||
paintAll = false; | ||
@@ -136,22 +251,32 @@ } | ||
for (var id in this._layers) { | ||
if (id !== 'hover') { | ||
this._layers[id].unusedCount++; | ||
} | ||
} | ||
var viewWidth = this._width; | ||
var viewHeight = this._height; | ||
var invTransform = []; | ||
this.eachBuildinLayer(preProcessLayer); | ||
// var invTransform = []; | ||
var prevElClipPaths = null; | ||
for (var i = 0, l = list.length; i < l; i++) { | ||
var shape = list[i]; | ||
var el = list[i]; | ||
var elZLevel = this._singleCanvas ? 0 : el.zlevel; | ||
// Change draw layer | ||
if (currentZLevel !== elZLevel) { | ||
// Only 0 zlevel if only has one canvas | ||
currentZLevel = elZLevel; | ||
currentLayer = this.getLayer(currentZLevel); | ||
if (currentZLevel !== shape.zlevel) { | ||
currentLayer = this.getLayer(shape.zlevel, currentLayer); | ||
if (!currentLayer.isBuildin) { | ||
log( | ||
'ZLevel ' + currentZLevel | ||
+ ' has been used by unkown layer ' + currentLayer.id | ||
); | ||
} | ||
ctx = currentLayer.ctx; | ||
currentZLevel = shape.zlevel; | ||
// Reset the count | ||
currentLayer.unusedCount = 0; | ||
currentLayer.__unusedCount = 0; | ||
if (currentLayer.dirty || paintAll) { | ||
if (currentLayer.__dirty || paintAll) { | ||
currentLayer.clear(); | ||
@@ -161,120 +286,195 @@ } | ||
// Start group clipping | ||
if (shape.__startClip && !vmlCanvasManager) { | ||
var clipShape = shape.__startClip; | ||
ctx.save(); | ||
// Set transform | ||
if (clipShape.needTransform) { | ||
var m = clipShape.transform; | ||
matrix.invert(invTransform, m); | ||
ctx.transform( | ||
m[0], m[1], | ||
m[2], m[3], | ||
m[4], m[5] | ||
); | ||
} | ||
if ( | ||
(currentLayer.__dirty || paintAll) | ||
// Ignore invisible element | ||
&& !el.invisible | ||
// Ignore transparent element | ||
&& el.style.opacity !== 0 | ||
// Ignore scale 0 element, in some environment like node-canvas | ||
// Draw a scale 0 element can cause all following draw wrong | ||
&& el.scale[0] && el.scale[1] | ||
// Ignore culled element | ||
&& !(el.culling && isDisplayableCulled(el, viewWidth, viewHeight)) | ||
) { | ||
var clipPaths = el.__clipPaths; | ||
ctx.beginPath(); | ||
clipShape.buildPath(ctx, clipShape.style); | ||
ctx.clip(); | ||
// Transform back | ||
if (clipShape.needTransform) { | ||
var m = invTransform; | ||
ctx.transform( | ||
m[0], m[1], | ||
m[2], m[3], | ||
m[4], m[5] | ||
); | ||
} | ||
} | ||
if ((currentLayer.dirty || paintAll) && !shape.invisible) { | ||
if ( | ||
!shape.onbrush | ||
|| (shape.onbrush && !shape.onbrush(ctx, false)) | ||
) { | ||
if (config.catchBrushException) { | ||
try { | ||
shape.brush(ctx, false, this.updatePainter); | ||
} | ||
catch(error) { | ||
log( | ||
error, | ||
'brush error of ' + shape.type, | ||
shape | ||
); | ||
} | ||
} else { | ||
shape.brush(ctx, false, this.updatePainter); | ||
// Optimize when clipping on group with several elements | ||
if (isClipPathChanged(clipPaths, prevElClipPaths)) { | ||
// If has previous clipping state, restore from it | ||
if (prevElClipPaths) { | ||
ctx.restore(); | ||
} | ||
// New clipping state | ||
if (clipPaths) { | ||
ctx.save(); | ||
doClip(clipPaths, ctx); | ||
} | ||
prevElClipPaths = clipPaths; | ||
} | ||
// TODO Use events ? | ||
el.beforeBrush && el.beforeBrush(ctx); | ||
el.brush(ctx, false); | ||
el.afterBrush && el.afterBrush(ctx); | ||
} | ||
// Stop group clipping | ||
if (shape.__stopClip && !vmlCanvasManager) { | ||
ctx.restore(); | ||
el.__dirty = false; | ||
} | ||
// If still has clipping state | ||
if (prevElClipPaths) { | ||
ctx.restore(); | ||
} | ||
this.eachBuildinLayer(postProcessLayer); | ||
}, | ||
/** | ||
* 获取 zlevel 所在层,如果不存在则会创建一个新的层 | ||
* @param {number} zlevel | ||
* @return {module:zrender/Layer} | ||
*/ | ||
getLayer: function (zlevel) { | ||
if (this._singleCanvas) { | ||
return this._layers[0]; | ||
} | ||
var layer = this._layers[zlevel]; | ||
if (!layer) { | ||
// Create a new layer | ||
layer = new Layer('zr_' + zlevel, this, this.dpr); | ||
layer.isBuildin = true; | ||
if (this._layerConfig[zlevel]) { | ||
util.merge(layer, this._layerConfig[zlevel], true); | ||
} | ||
shape.__dirty = false; | ||
this.insertLayer(zlevel, layer); | ||
// Context is created after dom inserted to document | ||
// Or excanvas will get 0px clientWidth and clientHeight | ||
layer.initContext(); | ||
} | ||
for (var id in this._layers) { | ||
if (id !== 'hover') { | ||
var layer = this._layers[id]; | ||
layer.dirty = false; | ||
// 删除过期的层 | ||
if (layer.unusedCount >= 500) { | ||
delete this._layers[id]; | ||
layer.dom.parentNode.removeChild(layer.dom); | ||
return layer; | ||
}, | ||
insertLayer: function (zlevel, layer) { | ||
var layersMap = this._layers; | ||
var zlevelList = this._zlevelList; | ||
var len = zlevelList.length; | ||
var prevLayer = null; | ||
var i = -1; | ||
var domRoot = this._domRoot; | ||
if (layersMap[zlevel]) { | ||
log('ZLevel ' + zlevel + ' has been used already'); | ||
return; | ||
} | ||
// Check if is a valid layer | ||
if (!isLayerValid(layer)) { | ||
log('Layer of zlevel ' + zlevel + ' is not valid'); | ||
return; | ||
} | ||
if (len > 0 && zlevel > zlevelList[0]) { | ||
for (i = 0; i < len - 1; i++) { | ||
if ( | ||
zlevelList[i] < zlevel | ||
&& zlevelList[i + 1] > zlevel | ||
) { | ||
break; | ||
} | ||
else if (layer.unusedCount == 1) { | ||
layer.clear(); | ||
} | ||
} | ||
prevLayer = layersMap[zlevelList[i]]; | ||
} | ||
}; | ||
zlevelList.splice(i + 1, 0, zlevel); | ||
Painter.prototype.getLayer = function(zlevel, prevLayer) { | ||
// Change draw layer | ||
var currentLayer = this._layers[zlevel]; | ||
if (!currentLayer) { | ||
// Create a new layer | ||
currentLayer = new Layer(zlevel, this); | ||
var prevDom = prevLayer ? prevLayer.dom : this._bgDom; | ||
if (prevLayer) { | ||
var prevDom = prevLayer.dom; | ||
if (prevDom.nextSibling) { | ||
prevDom.parentNode.insertBefore( | ||
currentLayer.dom, | ||
domRoot.insertBefore( | ||
layer.dom, | ||
prevDom.nextSibling | ||
); | ||
} else { | ||
prevDom.parentNode.appendChild( | ||
currentLayer.dom | ||
); | ||
} | ||
currentLayer.initContext(); | ||
this._layers[zlevel] = currentLayer; | ||
else { | ||
domRoot.appendChild(layer.dom); | ||
} | ||
} | ||
else { | ||
if (domRoot.firstChild) { | ||
domRoot.insertBefore(layer.dom, domRoot.firstChild); | ||
} | ||
else { | ||
domRoot.appendChild(layer.dom); | ||
} | ||
} | ||
currentLayer.config = this._layerConfig[zlevel]; | ||
layersMap[zlevel] = layer; | ||
}, | ||
// Iterate each layer | ||
eachLayer: function (cb, context) { | ||
var zlevelList = this._zlevelList; | ||
var z; | ||
var i; | ||
for (i = 0; i < zlevelList.length; i++) { | ||
z = zlevelList[i]; | ||
cb.call(context, this._layers[z], z); | ||
} | ||
}, | ||
return currentLayer; | ||
}; | ||
// Iterate each buildin layer | ||
eachBuildinLayer: function (cb, context) { | ||
var zlevelList = this._zlevelList; | ||
var layer; | ||
var z; | ||
var i; | ||
for (i = 0; i < zlevelList.length; i++) { | ||
z = zlevelList[i]; | ||
layer = this._layers[z]; | ||
if (layer.isBuildin) { | ||
cb.call(context, layer, z); | ||
} | ||
} | ||
}, | ||
Painter.prototype._updateLayerStatus = function(list) { | ||
// Iterate each other layer except buildin layer | ||
eachOtherLayer: function (cb, context) { | ||
var zlevelList = this._zlevelList; | ||
var layer; | ||
var z; | ||
var i; | ||
for (i = 0; i < zlevelList.length; i++) { | ||
z = zlevelList[i]; | ||
layer = this._layers[z]; | ||
if (! layer.isBuildin) { | ||
cb.call(context, layer, z); | ||
} | ||
} | ||
}, | ||
/** | ||
* 获取所有已创建的层 | ||
* @param {Array.<module:zrender/Layer>} [prevLayer] | ||
*/ | ||
getLayers: function () { | ||
return this._layers; | ||
}, | ||
_updateLayerStatus: function (list) { | ||
var layers = this._layers; | ||
var elCounts = {}; | ||
for (var z in layers) { | ||
if (z !== 'hover') { | ||
elCounts[z] = layers[z].elCount; | ||
layers[z].elCount = 0; | ||
} | ||
} | ||
this.eachBuildinLayer(function (layer, z) { | ||
elCounts[z] = layer.elCount; | ||
layer.elCount = 0; | ||
}); | ||
for (var i = 0, l = list.length; i < l; i++) { | ||
var shape = list[i]; | ||
var zlevel = shape.zlevel; | ||
var el = list[i]; | ||
var zlevel = this._singleCanvas ? 0 : el.zlevel; | ||
var layer = layers[zlevel]; | ||
@@ -284,6 +484,6 @@ if (layer) { | ||
// 已经被标记为需要刷新 | ||
if (layer.dirty) { | ||
if (layer.__dirty) { | ||
continue; | ||
} | ||
layer.dirty = shape.__dirty; | ||
layer.__dirty = el.__dirty; | ||
} | ||
@@ -293,62 +493,40 @@ } | ||
// 层中的元素数量有发生变化 | ||
for (var z in layers) { | ||
if (z !== 'hover') { | ||
if (elCounts[z] !== layers[z].elCount) { | ||
layers[z].dirty = true; | ||
} | ||
this.eachBuildinLayer(function (layer, z) { | ||
if (elCounts[z] !== layer.elCount) { | ||
layer.__dirty = true; | ||
} | ||
} | ||
}; | ||
}); | ||
}, | ||
/** | ||
* 视图更新 | ||
* | ||
* @param {Array} shapeList 需要更新的图形元素列表 | ||
* @param {Function} callback 视图更新后回调函数 | ||
* 清除hover层外所有内容 | ||
*/ | ||
Painter.prototype.update = function (shapeList, callback) { | ||
for (var i = 0, l = shapeList.length; i < l; i++) { | ||
var shape = shapeList[i]; | ||
this.storage.mod(shape.id); | ||
} | ||
this.refresh(callback); | ||
clear: function () { | ||
this.eachBuildinLayer(this._clearLayer); | ||
return this; | ||
}; | ||
}, | ||
/** | ||
* 设置loading特效 | ||
* | ||
* @param {Object} loadingEffect loading特效 | ||
* @return {Painter} | ||
*/ | ||
Painter.prototype.setLoadingEffect = function (loadingEffect) { | ||
this._loadingEffect = loadingEffect; | ||
return this; | ||
}; | ||
_clearLayer: function (layer) { | ||
layer.clear(); | ||
}, | ||
/** | ||
* 清除hover层外所有内容 | ||
*/ | ||
Painter.prototype.clear = function () { | ||
for (var k in this._layers) { | ||
if (k == 'hover') { | ||
continue; | ||
} | ||
this._layers[k].clear(); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* 修改指定zlevel的绘制参数 | ||
* | ||
* @param {string} zlevel | ||
* @param {Object} config 配置对象 | ||
* @param {string} [config.clearColor=0] 每次清空画布的颜色 | ||
* @param {string} [config.motionBlur=false] 是否开启动态模糊 | ||
* @param {number} [config.lastFrameAlpha=0.7] | ||
* 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显 | ||
*/ | ||
Painter.prototype.modLayer = function (zlevel, config) { | ||
configLayer: function (zlevel, config) { | ||
if (config) { | ||
if (!this._layerConfig[zlevel]) { | ||
this._layerConfig[zlevel] = config; | ||
} else { | ||
util.merge(this._layerConfig[zlevel], config, true); | ||
var layerConfig = this._layerConfig; | ||
if (!layerConfig[zlevel]) { | ||
layerConfig[zlevel] = config; | ||
} | ||
else { | ||
util.merge(layerConfig[zlevel], config, true); | ||
} | ||
@@ -358,72 +536,34 @@ var layer = this._layers[zlevel]; | ||
if (layer) { | ||
layer.config = this._layerConfig[zlevel]; | ||
util.merge(layer, layerConfig[zlevel], true); | ||
} | ||
} | ||
}; | ||
}, | ||
/** | ||
* 刷新hover层 | ||
* 删除指定层 | ||
* @param {number} zlevel 层所在的zlevel | ||
*/ | ||
Painter.prototype.refreshHover = function () { | ||
this.clearHover(); | ||
var list = this.storage.getHoverShapes(true); | ||
for (var i = 0, l = list.length; i < l; i++) { | ||
this._brushHover(list[i]); | ||
delLayer: function (zlevel) { | ||
var layers = this._layers; | ||
var zlevelList = this._zlevelList; | ||
var layer = layers[zlevel]; | ||
if (!layer) { | ||
return; | ||
} | ||
this.storage.delHover(); | ||
layer.dom.parentNode.removeChild(layer.dom); | ||
delete layers[zlevel]; | ||
return this; | ||
}; | ||
zlevelList.splice(util.indexOf(zlevelList, zlevel), 1); | ||
}, | ||
/** | ||
* 清除hover层所有内容 | ||
*/ | ||
Painter.prototype.clearHover = function () { | ||
var hover = this._layers.hover; | ||
hover && hover.clear(); | ||
return this; | ||
}; | ||
/** | ||
* 显示loading | ||
* | ||
* @param {Object=} loadingEffect loading效果对象 | ||
*/ | ||
Painter.prototype.showLoading = function (loadingEffect) { | ||
this._loadingEffect && this._loadingEffect.stop(); | ||
loadingEffect && this.setLoadingEffect(loadingEffect); | ||
this._loadingEffect.start(this); | ||
this.loading = true; | ||
return this; | ||
}; | ||
/** | ||
* loading结束 | ||
*/ | ||
Painter.prototype.hideLoading = function () { | ||
this._loadingEffect.stop(); | ||
this.clearHover(); | ||
this.loading = false; | ||
return this; | ||
}; | ||
/** | ||
* loading结束判断 | ||
*/ | ||
Painter.prototype.isLoading = function () { | ||
return this.loading; | ||
}; | ||
/** | ||
* 区域大小变化后重绘 | ||
*/ | ||
Painter.prototype.resize = function () { | ||
resize: function (width, height) { | ||
var domRoot = this._domRoot; | ||
// FIXME Why ? | ||
domRoot.style.display = 'none'; | ||
var width = this._getWidth(); | ||
var height = this._getHeight(); | ||
width = width || this._getWidth(); | ||
height = height || this._getHeight(); | ||
@@ -433,6 +573,3 @@ domRoot.style.display = ''; | ||
// 优化没有实际改变的resize | ||
if (this._width != width || height != this._height){ | ||
this._width = width; | ||
this._height = height; | ||
if (this._width != width || height != this._height) { | ||
domRoot.style.width = width + 'px'; | ||
@@ -442,21 +579,24 @@ domRoot.style.height = height + 'px'; | ||
for (var id in this._layers) { | ||
this._layers[id].resize(width, height); | ||
} | ||
this.refresh(null, true); | ||
this.refresh(true); | ||
} | ||
this._width = width; | ||
this._height = height; | ||
return this; | ||
}; | ||
}, | ||
/** | ||
* 清除单独的一个层 | ||
* @param {number} zlevel | ||
*/ | ||
Painter.prototype.clearLayer = function (k) { | ||
var layer = this._layers[k]; | ||
clearLayer: function (zlevel) { | ||
var layer = this._layers[zlevel]; | ||
if (layer) { | ||
layer.clear(); | ||
} | ||
}; | ||
}, | ||
@@ -466,7 +606,3 @@ /** | ||
*/ | ||
Painter.prototype.dispose = function () { | ||
if (this.isLoading()) { | ||
this.hideLoading(); | ||
} | ||
dispose: function () { | ||
this.root.innerHTML = ''; | ||
@@ -477,70 +613,44 @@ | ||
this._domRoot = | ||
this._domRoot = | ||
this._layers = null; | ||
}; | ||
}, | ||
Painter.prototype.getDomHover = function () { | ||
return this._layers.hover.dom; | ||
}; | ||
/** | ||
* Get canvas which has all thing rendered | ||
* @param {Object} opts | ||
* @param {string} [opts.backgroundColor] | ||
*/ | ||
getRenderedCanvas: function (opts) { | ||
opts = opts || {}; | ||
if (this._singleCanvas) { | ||
return this._layers[0].dom; | ||
} | ||
Painter.prototype.toDataURL = function (type, backgroundColor, args) { | ||
if (vmlCanvasManager) { | ||
return null; | ||
var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr); | ||
imageLayer.initContext(); | ||
var ctx = imageLayer.ctx; | ||
imageLayer.clearColor = opts.backgroundColor; | ||
imageLayer.clear(); | ||
var displayList = this.storage.getDisplayList(true); | ||
for (var i = 0; i < displayList.length; i++) { | ||
var el = displayList[i]; | ||
if (!el.invisible) { | ||
el.beforeBrush && el.beforeBrush(ctx); | ||
// TODO Check image cross origin | ||
el.brush(ctx, false); | ||
el.afterBrush && el.afterBrush(ctx); | ||
} | ||
} | ||
var imageDom = createDom('image', 'canvas', this); | ||
this._bgDom.appendChild(imageDom); | ||
var ctx = imageDom.getContext('2d'); | ||
devicePixelRatio != 1 | ||
&& ctx.scale(devicePixelRatio, devicePixelRatio); | ||
ctx.fillStyle = backgroundColor || '#fff'; | ||
ctx.rect( | ||
0, 0, | ||
this._width * devicePixelRatio, | ||
this._height * devicePixelRatio | ||
); | ||
ctx.fill(); | ||
//升序遍历,shape上的zlevel指定绘画图层的z轴层叠 | ||
this.storage.iterShape( | ||
function (shape) { | ||
if (!shape.invisible) { | ||
if (!shape.onbrush //没有onbrush | ||
//有onbrush并且调用执行返回false或undefined则继续粉刷 | ||
|| (shape.onbrush && !shape.onbrush(ctx, false)) | ||
) { | ||
if (config.catchBrushException) { | ||
try { | ||
shape.brush(ctx, false, this.updatePainter); | ||
} | ||
catch(error) { | ||
log( | ||
error, | ||
'brush error of ' + shape.type, | ||
shape | ||
); | ||
} | ||
} | ||
else { | ||
shape.brush(ctx, false, this.updatePainter); | ||
} | ||
} | ||
} | ||
}, | ||
{ normal: 'up', update: true } | ||
); | ||
var image = imageDom.toDataURL(type, args); | ||
ctx = null; | ||
this._bgDom.removeChild(imageDom); | ||
return image; | ||
}; | ||
return imageLayer.dom; | ||
}, | ||
/** | ||
* 获取绘图区域宽度 | ||
*/ | ||
Painter.prototype.getWidth = function () { | ||
getWidth: function () { | ||
return this._width; | ||
}; | ||
}, | ||
@@ -550,80 +660,47 @@ /** | ||
*/ | ||
Painter.prototype.getHeight = function () { | ||
getHeight: function () { | ||
return this._height; | ||
}; | ||
}, | ||
Painter.prototype._getWidth = function() { | ||
_getWidth: function () { | ||
var root = this.root; | ||
var stl = root.currentStyle | ||
|| document.defaultView.getComputedStyle(root); | ||
var stl = document.defaultView.getComputedStyle(root); | ||
return ((root.clientWidth || parseInt(stl.width, 10)) | ||
- parseInt(stl.paddingLeft, 10) // 请原谅我这比较粗暴 | ||
- parseInt(stl.paddingRight, 10)).toFixed(0) - 0; | ||
}; | ||
// FIXME Better way to get the width and height when element has not been append to the document | ||
return ((root.clientWidth || parseInt10(stl.width) || parseInt10(root.style.width)) | ||
- (parseInt10(stl.paddingLeft) || 0) | ||
- (parseInt10(stl.paddingRight) || 0)) | 0; | ||
}, | ||
Painter.prototype._getHeight = function () { | ||
_getHeight: function () { | ||
var root = this.root; | ||
var stl = root.currentStyle | ||
|| document.defaultView.getComputedStyle(root); | ||
var stl = document.defaultView.getComputedStyle(root); | ||
return ((root.clientHeight || parseInt(stl.height, 10)) | ||
- parseInt(stl.paddingTop, 10) // 请原谅我这比较粗暴 | ||
- parseInt(stl.paddingBottom, 10)).toFixed(0) - 0; | ||
}; | ||
return ((root.clientHeight || parseInt10(stl.height) || parseInt10(root.style.height)) | ||
- (parseInt10(stl.paddingTop) || 0) | ||
- (parseInt10(stl.paddingBottom) || 0)) | 0; | ||
}, | ||
/** | ||
* 鼠标悬浮刷画 | ||
*/ | ||
Painter.prototype._brushHover = function (shape) { | ||
var ctx = this._layers.hover.ctx; | ||
if (!shape.onbrush //没有onbrush | ||
//有onbrush并且调用执行返回false或undefined则继续粉刷 | ||
|| (shape.onbrush && !shape.onbrush(ctx, true)) | ||
) { | ||
// Retina 优化 | ||
if (config.catchBrushException) { | ||
try { | ||
shape.brush(ctx, true, this.updatePainter); | ||
} | ||
catch(error) { | ||
log( | ||
error, 'hoverBrush error of ' + shape.type, shape | ||
); | ||
} | ||
} | ||
else { | ||
shape.brush(ctx, true, this.updatePainter); | ||
} | ||
} | ||
}; | ||
Painter.prototype._shapeToImage = function ( | ||
id, shape, width, height, devicePixelRatio | ||
) { | ||
_pathToImage: function (id, path, width, height, dpr) { | ||
var canvas = document.createElement('canvas'); | ||
var ctx = canvas.getContext('2d'); | ||
var devicePixelRatio = window.devicePixelRatio || 1; | ||
canvas.style.width = width + 'px'; | ||
canvas.style.height = height + 'px'; | ||
canvas.setAttribute('width', width * devicePixelRatio); | ||
canvas.setAttribute('height', height * devicePixelRatio); | ||
ctx.clearRect(0, 0, width * devicePixelRatio, height * devicePixelRatio); | ||
canvas.width = width * dpr; | ||
canvas.height = height * dpr; | ||
var shapeTransform = { | ||
position : shape.position, | ||
rotation : shape.rotation, | ||
scale : shape.scale | ||
ctx.clearRect(0, 0, width * dpr, height * dpr); | ||
var pathTransform = { | ||
position : path.position, | ||
rotation : path.rotation, | ||
scale : path.scale | ||
}; | ||
shape.position = [0, 0, 0]; | ||
shape.rotation = 0; | ||
shape.scale = [1, 1]; | ||
if (shape) { | ||
shape.brush(ctx, false); | ||
path.position = [0, 0, 0]; | ||
path.rotation = 0; | ||
path.scale = [1, 1]; | ||
if (path) { | ||
path.brush(ctx); | ||
} | ||
var ImageShape = require( './shape/Image' ); | ||
var ImageShape = require('./graphic/Image'); | ||
var imgShape = new ImageShape({ | ||
@@ -638,190 +715,29 @@ id : id, | ||
if (shapeTransform.position != null) { | ||
imgShape.position = shape.position = shapeTransform.position; | ||
if (pathTransform.position != null) { | ||
imgShape.position = path.position = pathTransform.position; | ||
} | ||
if (shapeTransform.rotation != null) { | ||
imgShape.rotation = shape.rotation = shapeTransform.rotation; | ||
if (pathTransform.rotation != null) { | ||
imgShape.rotation = path.rotation = pathTransform.rotation; | ||
} | ||
if (shapeTransform.scale != null) { | ||
imgShape.scale = shape.scale = shapeTransform.scale; | ||
if (pathTransform.scale != null) { | ||
imgShape.scale = path.scale = pathTransform.scale; | ||
} | ||
return imgShape; | ||
}; | ||
}, | ||
Painter.prototype._createShapeToImageProcessor = function () { | ||
if (vmlCanvasManager) { | ||
return doNothing; | ||
} | ||
_createPathToImage: function () { | ||
var me = this; | ||
var painter = this; | ||
return function (id, e, width, height) { | ||
return painter._shapeToImage( | ||
id, e, width, height, devicePixelRatio | ||
return me._pathToImage( | ||
id, e, width, height, me.dpr | ||
); | ||
}; | ||
}; | ||
/** | ||
* 创建dom | ||
* | ||
* @inner | ||
* @param {string} id dom id 待用 | ||
* @param {string} type dom type,such as canvas, div etc. | ||
* @param {Painter} painter painter instance | ||
*/ | ||
function createDom(id, type, painter) { | ||
var newDom = document.createElement(type); | ||
var width = painter._width; | ||
var height = painter._height; | ||
// 没append呢,请原谅我这样写,清晰~ | ||
newDom.style.position = 'absolute'; | ||
newDom.style.left = 0; | ||
newDom.style.top = 0; | ||
newDom.style.width = width + 'px'; | ||
newDom.style.height = height + 'px'; | ||
newDom.setAttribute('width', width * devicePixelRatio); | ||
newDom.setAttribute('height', height * devicePixelRatio); | ||
// id不作为索引用,避免可能造成的重名,定义为私有属性 | ||
newDom.setAttribute('data-zr-dom-id', id); | ||
return newDom; | ||
} | ||
}; | ||
/***************************************** | ||
* Layer | ||
*****************************************/ | ||
function Layer(id, painter) { | ||
this.dom = createDom(id, 'canvas', painter); | ||
vmlCanvasManager && vmlCanvasManager.initElement(this.dom); | ||
this.domBack = null; | ||
this.ctxBack = null; | ||
this.painter = painter; | ||
this.unusedCount = 0; | ||
this.config = null; | ||
this.dirty = true; | ||
this.elCount = 0; | ||
} | ||
Layer.prototype.initContext = function() { | ||
this.ctx = this.dom.getContext('2d'); | ||
if (devicePixelRatio != 1) { | ||
this.ctx.scale(devicePixelRatio, devicePixelRatio); | ||
} | ||
} | ||
Layer.prototype.createBackBuffer = function() { | ||
if (vmlCanvasManager) { // IE 8- should not support back buffer | ||
return; | ||
} | ||
this.domBack = createDom('back-' + this.id, 'canvas', this.painter); | ||
this.ctxBack = this.domBack.getContext('2d'); | ||
if (devicePixelRatio != 1) { | ||
this.ctxBack.scale(devicePixelRatio, devicePixelRatio); | ||
} | ||
}; | ||
Layer.prototype.resize = function(width, height) { | ||
this.dom.style.width = width + 'px'; | ||
this.dom.style.height = height + 'px'; | ||
this.dom.setAttribute('width', width * devicePixelRatio); | ||
this.dom.setAttribute('height', height * devicePixelRatio); | ||
if (devicePixelRatio != 1) { | ||
this.ctx.scale(devicePixelRatio, devicePixelRatio); | ||
} | ||
if (this.domBack) { | ||
this.domBack.setAttribute('width', width * devicePixelRatio); | ||
this.domBack.setAttribute('height', height * devicePixelRatio); | ||
if (devicePixelRatio != 1) { | ||
this.ctxBack.scale(devicePixelRatio, devicePixelRatio); | ||
} | ||
} | ||
}; | ||
Layer.prototype.clear = function() { | ||
var config = this.config; | ||
var dom = this.dom; | ||
var ctx = this.ctx; | ||
var width = dom.width; | ||
var height = dom.height; | ||
if (config) { | ||
var haveClearColor = | ||
typeof(config.clearColor) !== 'undefined' | ||
&& !vmlCanvasManager; | ||
var haveMotionBLur = config.motionBlur && !vmlCanvasManager; | ||
var lastFrameAlpha = config.lastFrameAlpha; | ||
if (typeof(lastFrameAlpha) == 'undefined') { | ||
lastFrameAlpha = 0.7; | ||
} | ||
if (haveMotionBLur) { | ||
if (!this.domBack) { | ||
this.createBackBuffer(); | ||
} | ||
this.ctxBack.globalCompositeOperation = 'copy'; | ||
this.ctxBack.drawImage( | ||
dom, 0, 0, | ||
width / devicePixelRatio, | ||
height / devicePixelRatio | ||
); | ||
} | ||
if (haveClearColor) { | ||
ctx.save(); | ||
ctx.fillStyle = this.config.clearColor; | ||
ctx.fillRect( | ||
0, 0, | ||
width / devicePixelRatio, | ||
height / devicePixelRatio | ||
); | ||
ctx.restore(); | ||
} | ||
else { | ||
ctx.clearRect( | ||
0, 0, | ||
width / devicePixelRatio, | ||
height / devicePixelRatio | ||
); | ||
} | ||
if (haveMotionBLur) { | ||
var domBack = this.domBack; | ||
ctx.save(); | ||
ctx.globalAlpha = lastFrameAlpha; | ||
ctx.drawImage( | ||
domBack, 0, 0, | ||
width / devicePixelRatio, | ||
height / devicePixelRatio | ||
); | ||
ctx.restore(); | ||
} | ||
} | ||
else { | ||
ctx.clearRect( | ||
0, 0, | ||
width / devicePixelRatio, | ||
height / devicePixelRatio | ||
); | ||
} | ||
}; | ||
return Painter; | ||
} | ||
); | ||
return Painter; | ||
}); |
/** | ||
* Storage内容仓库模块 | ||
* | ||
* @author Kener (@Kener-林峰, linzhifeng@baidu.com) | ||
* errorrik (errorrik@gmail.com) | ||
* @module zrender/Storage | ||
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com) | ||
* @author errorrik (errorrik@gmail.com) | ||
* @author pissang (https://github.com/pissang/) | ||
*/ | ||
define(function (require) { | ||
'use strict'; | ||
define( | ||
function (require) { | ||
var util = require('./core/util'); | ||
'use strict'; | ||
var Group = require('./container/Group'); | ||
var util = require('./tool/util'); | ||
var Group = require('./shape/Group'); | ||
var defaultIterateOption = { | ||
hover: false, | ||
normal: 'down', | ||
update: false | ||
}; | ||
function shapeCompareFunc(a, b) { | ||
if (a.zlevel == b.zlevel) { | ||
if (a.z == b.z) { | ||
function shapeCompareFunc(a, b) { | ||
if (a.zlevel === b.zlevel) { | ||
if (a.z === b.z) { | ||
if (a.z2 === b.z2) { | ||
return a.__renderidx - b.__renderidx; | ||
} | ||
return a.z - b.z; | ||
return a.z2 - b.z2; | ||
} | ||
return a.zlevel - b.zlevel; | ||
return a.z - b.z; | ||
} | ||
/** | ||
* 内容仓库 (M) | ||
* | ||
*/ | ||
function Storage() { | ||
// 所有常规形状,id索引的map | ||
this._elements = {}; | ||
return a.zlevel - b.zlevel; | ||
} | ||
/** | ||
* 内容仓库 (M) | ||
* @alias module:zrender/Storage | ||
* @constructor | ||
*/ | ||
var Storage = function () { | ||
// 所有常规形状,id索引的map | ||
this._elements = {}; | ||
// 高亮层形状,不稳定,动态增删,数组位置也是z轴方向,靠前显示在下方 | ||
this._hoverElements = []; | ||
this._roots = []; | ||
this._roots = []; | ||
this._displayList = []; | ||
this._shapeList = []; | ||
this._displayListLen = 0; | ||
}; | ||
this._shapeListOffset = 0; | ||
} | ||
Storage.prototype = { | ||
constructor: Storage, | ||
/** | ||
* 遍历迭代器 | ||
* | ||
* @param {Function} fun 迭代回调函数,return true终止迭代 | ||
* @param {Object=} option 迭代参数,缺省为仅降序遍历常规形状 | ||
* hover : true 是否迭代高亮层数据 | ||
* normal : 'down' | 'up' 是否迭代常规数据,迭代时是否指定及z轴顺序 | ||
* update : false 是否更新shapeList | ||
* 返回所有图形的绘制队列 | ||
* @param {boolean} [update=false] 是否在返回前更新该数组 | ||
* 详见{@link module:zrender/graphic/Displayable.prototype.updateDisplayList} | ||
* @return {Array.<module:zrender/graphic/Displayable>} | ||
*/ | ||
Storage.prototype.iterShape = function (fun, option) { | ||
if (!option) { | ||
option = defaultIterateOption; | ||
getDisplayList: function (update) { | ||
if (update) { | ||
this.updateDisplayList(); | ||
} | ||
return this._displayList; | ||
}, | ||
if (option.hover) { | ||
//高亮层数据遍历 | ||
for (var i = 0, l = this._hoverElements.length; i < l; i++) { | ||
var el = this._hoverElements[i]; | ||
el.updateTransform(); | ||
if (fun(el)) { | ||
return this; | ||
} | ||
} | ||
/** | ||
* 更新图形的绘制队列。 | ||
* 每次绘制前都会调用,该方法会先深度优先遍历整个树,更新所有Group和Shape的变换并且把所有可见的Shape保存到数组中, | ||
* 最后根据绘制的优先级(zlevel > z > 插入顺序)排序得到绘制队列 | ||
*/ | ||
updateDisplayList: function () { | ||
this._displayListLen = 0; | ||
var roots = this._roots; | ||
var displayList = this._displayList; | ||
for (var i = 0, len = roots.length; i < len; i++) { | ||
var root = roots[i]; | ||
this._updateAndAddDisplayable(root); | ||
} | ||
displayList.length = this._displayListLen; | ||
if (option.update) { | ||
this.updateShapeList(); | ||
for (var i = 0, len = displayList.length; i < len; i++) { | ||
displayList[i].__renderidx = i; | ||
} | ||
//遍历: 'down' | 'up' | ||
switch (option.normal) { | ||
case 'down': | ||
// 降序遍历,高层优先 | ||
var l = this._shapeList.length; | ||
while (l--) { | ||
if (fun(this._shapeList[l])) { | ||
return this; | ||
} | ||
} | ||
break; | ||
// case 'up': | ||
default: | ||
//升序遍历,底层优先 | ||
for (var i = 0, l = this._shapeList.length; i < l; i++) { | ||
if (fun(this._shapeList[i])) { | ||
return this; | ||
} | ||
} | ||
break; | ||
} | ||
displayList.sort(shapeCompareFunc); | ||
}, | ||
return this; | ||
}; | ||
_updateAndAddDisplayable: function (el, clipPaths) { | ||
Storage.prototype.getHoverShapes = function(update) { | ||
if (update) { | ||
for (var i = 0, l = this._hoverElements.length; i < l; i++) { | ||
this._hoverElements[i].updateTransform(); | ||
} | ||
if (el.ignore) { | ||
return; | ||
} | ||
return this._hoverElements; | ||
}; | ||
Storage.prototype.getShapeList = function(update) { | ||
if (update) { | ||
this.updateShapeList(); | ||
} | ||
return this._shapeList; | ||
}; | ||
el.beforeUpdate(); | ||
el.update(); | ||
Storage.prototype.updateShapeList = function() { | ||
this._shapeListOffset = 0; | ||
for (var i = 0, len = this._roots.length; i < len; i++) { | ||
var root = this._roots[i]; | ||
this._updateAndAddShape(root); | ||
} | ||
this._shapeList.length = this._shapeListOffset; | ||
el.afterUpdate(); | ||
for (var i = 0, len = this._shapeList.length; i < len; i++) { | ||
this._shapeList[i].__renderidx = i; | ||
} | ||
var clipPath = el.clipPath; | ||
if (clipPath) { | ||
// clipPath 的变换是基于 group 的变换 | ||
clipPath.parent = el; | ||
clipPath.updateTransform(); | ||
this._shapeList.sort(shapeCompareFunc); | ||
}; | ||
Storage.prototype._updateAndAddShape = function(el) { | ||
if (el.ignore) { | ||
return; | ||
// FIXME 效率影响 | ||
if (clipPaths) { | ||
clipPaths = clipPaths.slice(); | ||
clipPaths.push(clipPath); | ||
} | ||
else { | ||
clipPaths = [clipPath]; | ||
} | ||
} | ||
el.updateTransform(); | ||
if (el.type == 'group') { | ||
if (el.clipShape) { | ||
// clipShape 的变换是基于 group 的变换 | ||
el.clipShape.parent = el; | ||
el.clipShape.updateTransform(); | ||
var children = el._children; | ||
var startClipShape = el._children[0]; | ||
if (startClipShape) { | ||
startClipShape.__startClip = el.clipShape; | ||
} | ||
} | ||
for (var i = 0; i < children.length; i++) { | ||
var child = children[i]; | ||
for (var i = 0; i < el._children.length; i++) { | ||
var child = el._children[i]; | ||
// Force to mark as dirty if group is dirty | ||
// FIXME __dirtyPath ? | ||
child.__dirty = el.__dirty || child.__dirty; | ||
this._updateAndAddShape(child); | ||
this._updateAndAddDisplayable(child, clipPaths); | ||
} | ||
if (el.clipShape) { | ||
var stopClipShape = this._shapeList[this._shapeListOffset - 1]; | ||
if (stopClipShape) { | ||
stopClipShape.__stopClip = true; | ||
} | ||
} | ||
// Mark group clean here | ||
el.__dirty = false; | ||
} else { | ||
this._shapeList[this._shapeListOffset++] = el; | ||
} | ||
}; | ||
else { | ||
el.__clipPaths = clipPaths; | ||
/** | ||
* 修改 | ||
* | ||
* @param {string} idx 唯一标识 | ||
* @param {Object} [params] 参数 | ||
*/ | ||
Storage.prototype.mod = function (elId, params) { | ||
var el = this._elements[elId]; | ||
if (el) { | ||
if (!(el instanceof Group)) { | ||
el.style.__rect = null; | ||
} | ||
el.__dirty = true; | ||
if (params) { | ||
// 如果第二个参数直接使用 shape | ||
// parent, _storage, __startClip 三个属性会有循环引用 | ||
// 主要为了向 1.x 版本兼容,2.x 版本不建议使用第二个参数 | ||
if (params.parent || params._storage || params.__startClip) { | ||
var target = {}; | ||
for (var name in params) { | ||
if ( | ||
name == 'parent' | ||
|| name == '_storage' | ||
|| name == '__startClip' | ||
) { | ||
continue; | ||
} | ||
if (params.hasOwnProperty(name)) { | ||
target[name] = params[name]; | ||
} | ||
} | ||
util.merge(el, target, true); | ||
} else { | ||
util.merge(el, params, true); | ||
} | ||
} | ||
this._displayList[this._displayListLen++] = el; | ||
} | ||
}, | ||
return this; | ||
}; | ||
/** | ||
* 常规形状位置漂移,形状自身定义漂移函数 | ||
* | ||
* @param {string} idx 形状唯一标识 | ||
* 添加图形(Shape)或者组(Group)到根节点 | ||
* @param {module:zrender/Element} el | ||
*/ | ||
Storage.prototype.drift = function (shapeId, dx, dy) { | ||
var shape = this._elements[shapeId]; | ||
if (shape) { | ||
shape.needTransform = true; | ||
if (!shape.ondrift //ondrift | ||
//有onbrush并且调用执行返回false或undefined则继续 | ||
|| (shape.ondrift && !shape.ondrift(dx, dy)) | ||
) { | ||
shape.drift(dx, dy); | ||
} | ||
addRoot: function (el) { | ||
// Element has been added | ||
if (this._elements[el.id]) { | ||
return; | ||
} | ||
return this; | ||
}; | ||
/** | ||
* 添加高亮层数据 | ||
* | ||
* @param {Object} params 参数 | ||
*/ | ||
Storage.prototype.addHover = function (shape) { | ||
shape.updateNeedTransform(); | ||
this._hoverElements.push(shape); | ||
return this; | ||
}; | ||
/** | ||
* 删除高亮层数据 | ||
*/ | ||
Storage.prototype.delHover = function () { | ||
this._hoverElements = []; | ||
return this; | ||
}; | ||
Storage.prototype.hasHoverShape = function () { | ||
return this._hoverElements.length > 0; | ||
}; | ||
/** | ||
* 添加到根节点 | ||
* | ||
* @param {Shape|Group} el 参数 | ||
*/ | ||
Storage.prototype.addRoot = function (el) { | ||
if (el instanceof Group) { | ||
@@ -279,6 +151,10 @@ el.addChildrenToStorage(this); | ||
this._roots.push(el); | ||
}; | ||
}, | ||
Storage.prototype.delRoot = function (elId) { | ||
if (typeof(elId) == 'undefined') { | ||
/** | ||
* 删除指定的图形(Shape)或者组(Group) | ||
* @param {string|Array.<string>} [elId] 如果为空清空整个Storage | ||
*/ | ||
delRoot: function (elId) { | ||
if (elId == null) { | ||
// 不指定elId清空 | ||
@@ -293,4 +169,5 @@ for (var i = 0; i < this._roots.length; i++) { | ||
this._elements = {}; | ||
this._hoverElements = []; | ||
this._roots = []; | ||
this._displayList = []; | ||
this._displayListLen = 0; | ||
@@ -310,3 +187,4 @@ return; | ||
el = this._elements[elId]; | ||
} else { | ||
} | ||
else { | ||
el = elId; | ||
@@ -323,15 +201,9 @@ } | ||
} | ||
}; | ||
}, | ||
/** | ||
* 添加 | ||
* | ||
* @param {Shape|Group} el 参数 | ||
*/ | ||
Storage.prototype.addToMap = function (el) { | ||
addToMap: function (el) { | ||
if (el instanceof Group) { | ||
el._storage = this; | ||
} else { | ||
el.style.__rect = null; | ||
el.__storage = this; | ||
} | ||
el.dirty(); | ||
@@ -341,25 +213,15 @@ this._elements[el.id] = el; | ||
return this; | ||
}; | ||
}, | ||
/** | ||
* 根据指定的elId获取相应的shape属性 | ||
* | ||
* @param {string=} idx 唯一标识 | ||
*/ | ||
Storage.prototype.get = function (elId) { | ||
get: function (elId) { | ||
return this._elements[elId]; | ||
}; | ||
}, | ||
/** | ||
* 删除,elId不指定则全清空 | ||
* | ||
* @param {string} idx 唯一标识 | ||
*/ | ||
Storage.prototype.delFromMap = function (elId) { | ||
var el = this._elements[elId]; | ||
delFromMap: function (elId) { | ||
var elements = this._elements; | ||
var el = elements[elId]; | ||
if (el) { | ||
delete this._elements[elId]; | ||
delete elements[elId]; | ||
if (el instanceof Group) { | ||
el._storage = null; | ||
el.__storage = null; | ||
} | ||
@@ -369,17 +231,15 @@ } | ||
return this; | ||
}; | ||
}, | ||
/** | ||
* 释放 | ||
* 清空并且释放Storage | ||
*/ | ||
Storage.prototype.dispose = function () { | ||
this._elements = | ||
this._renderList = | ||
this._roots = | ||
this._hoverElements = null; | ||
}; | ||
dispose: function () { | ||
this._elements = | ||
this._renderList = | ||
this._roots = null; | ||
} | ||
}; | ||
return Storage; | ||
} | ||
); | ||
return Storage; | ||
}); |
/** | ||
* zrender : 颜色辅助类 | ||
* | ||
* author: CrossDo (chenhuaimu@baidu.com) | ||
* | ||
* getColor:获取色板颜色 | ||
* customPalette : 自定义调色板 | ||
* resetPalette : 重置调色板 | ||
* | ||
* getHighlightColor : 获取默认高亮颜色 | ||
* customHighlight : 自定义默认高亮颜色 | ||
* resetHighlight : 重置默认高亮颜色 | ||
* | ||
* getRadialGradient : 径向渐变 | ||
* getLinearGradient : 线性渐变 | ||
* getGradientColors : 获取颜色之间渐变颜色数组 | ||
* getStepColors : 获取两种颜色之间渐变颜色数组 | ||
* reverse : 颜色翻转 | ||
* mix : 颜色混合 | ||
* lift : 颜色升降 | ||
* trim : 清除空格 | ||
* random : 随机颜色 | ||
* toRGB : 转为RGB格式 | ||
* toRGBA : 转为RGBA格式 | ||
* toHex : 转为#RRGGBB格式 | ||
* toHSL : 转为HSL格式 | ||
* toHSLA : 转为HSLA格式 | ||
* toHSB : 转为HSB格式 | ||
* toHSBA : 转为HSBA格式 | ||
* toHSV : 转为HSV格式 | ||
* toHSVA : 转为HSVA格式 | ||
* toName : 转为颜色名字 | ||
* toColor: 颜色值数组转为指定格式颜色 | ||
* toArray: 返回颜色值数组 | ||
* alpha : 设置颜色的透明度 | ||
**/ | ||
define( function(require) { | ||
var util = require('../tool/util'); | ||
* @module zrender/tool/color | ||
*/ | ||
define(function(require) { | ||
var _ctx; | ||
// Color palette is an array containing the default colors for the chart's | ||
// series. | ||
// When all colors are used, new colors are selected from the start again. | ||
// Defaults to: | ||
// 默认色板 | ||
var palette = [ | ||
'#ff9277', ' #dddd00', ' #ffc877', ' #bbe3ff', ' #d5ffbb', | ||
'#bbbbff', ' #ddb000', ' #b0dd00', ' #e2bbff', ' #ffbbe3', | ||
'#ff7777', ' #ff9900', ' #83dd00', ' #77e3ff', ' #778fff', | ||
'#c877ff', ' #ff77ab', ' #ff6600', ' #aa8800', ' #77c7ff', | ||
'#ad77ff', ' #ff77ff', ' #dd0083', ' #777700', ' #00aa00', | ||
'#0088aa', ' #8400dd', ' #aa0088', ' #dd0000', ' #772e00' | ||
]; | ||
var _palette = palette; | ||
var highlightColor = 'rgba(255,255,0,0.5)'; | ||
var _highlightColor = highlightColor; | ||
// 颜色格式 | ||
/*jshint maxlen: 330 */ | ||
var colorRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i; | ||
var _nameColors = { | ||
aliceblue : '#f0f8ff', | ||
antiquewhite : '#faebd7', | ||
aqua : '#0ff', | ||
aquamarine : '#7fffd4', | ||
azure : '#f0ffff', | ||
beige : '#f5f5dc', | ||
bisque : '#ffe4c4', | ||
black : '#000', | ||
blanchedalmond : '#ffebcd', | ||
blue : '#00f', | ||
blueviolet : '#8a2be2', | ||
brown : '#a52a2a', | ||
burlywood : '#deb887', | ||
cadetblue : '#5f9ea0', | ||
chartreuse : '#7fff00', | ||
chocolate : '#d2691e', | ||
coral : '#ff7f50', | ||
cornflowerblue : '#6495ed', | ||
cornsilk : '#fff8dc', | ||
crimson : '#dc143c', | ||
cyan : '#0ff', | ||
darkblue : '#00008b', | ||
darkcyan : '#008b8b', | ||
darkgoldenrod : '#b8860b', | ||
darkgray : '#a9a9a9', | ||
darkgrey : '#a9a9a9', | ||
darkgreen : '#006400', | ||
darkkhaki : '#bdb76b', | ||
darkmagenta : '#8b008b', | ||
darkolivegreen : '#556b2f', | ||
darkorange : '#ff8c00', | ||
darkorchid : '#9932cc', | ||
darkred : '#8b0000', | ||
darksalmon : '#e9967a', | ||
darkseagreen : '#8fbc8f', | ||
darkslateblue : '#483d8b', | ||
darkslategray : '#2f4f4f', | ||
darkslategrey : '#2f4f4f', | ||
darkturquoise : '#00ced1', | ||
darkviolet : '#9400d3', | ||
deeppink : '#ff1493', | ||
deepskyblue : '#00bfff', | ||
dimgray : '#696969', | ||
dimgrey : '#696969', | ||
dodgerblue : '#1e90ff', | ||
firebrick : '#b22222', | ||
floralwhite : '#fffaf0', | ||
forestgreen : '#228b22', | ||
fuchsia : '#f0f', | ||
gainsboro : '#dcdcdc', | ||
ghostwhite : '#f8f8ff', | ||
gold : '#ffd700', | ||
goldenrod : '#daa520', | ||
gray : '#808080', | ||
grey : '#808080', | ||
green : '#008000', | ||
greenyellow : '#adff2f', | ||
honeydew : '#f0fff0', | ||
hotpink : '#ff69b4', | ||
indianred : '#cd5c5c', | ||
indigo : '#4b0082', | ||
ivory : '#fffff0', | ||
khaki : '#f0e68c', | ||
lavender : '#e6e6fa', | ||
lavenderblush : '#fff0f5', | ||
lawngreen : '#7cfc00', | ||
lemonchiffon : '#fffacd', | ||
lightblue : '#add8e6', | ||
lightcoral : '#f08080', | ||
lightcyan : '#e0ffff', | ||
lightgoldenrodyellow : '#fafad2', | ||
lightgray : '#d3d3d3', | ||
lightgrey : '#d3d3d3', | ||
lightgreen : '#90ee90', | ||
lightpink : '#ffb6c1', | ||
lightsalmon : '#ffa07a', | ||
lightseagreen : '#20b2aa', | ||
lightskyblue : '#87cefa', | ||
lightslategray : '#789', | ||
lightslategrey : '#789', | ||
lightsteelblue : '#b0c4de', | ||
lightyellow : '#ffffe0', | ||
lime : '#0f0', | ||
limegreen : '#32cd32', | ||
linen : '#faf0e6', | ||
magenta : '#f0f', | ||
maroon : '#800000', | ||
mediumaquamarine : '#66cdaa', | ||
mediumblue : '#0000cd', | ||
mediumorchid : '#ba55d3', | ||
mediumpurple : '#9370d8', | ||
mediumseagreen : '#3cb371', | ||
mediumslateblue : '#7b68ee', | ||
mediumspringgreen : '#00fa9a', | ||
mediumturquoise : '#48d1cc', | ||
mediumvioletred : '#c71585', | ||
midnightblue : '#191970', | ||
mintcream : '#f5fffa', | ||
mistyrose : '#ffe4e1', | ||
moccasin : '#ffe4b5', | ||
navajowhite : '#ffdead', | ||
navy : '#000080', | ||
oldlace : '#fdf5e6', | ||
olive : '#808000', | ||
olivedrab : '#6b8e23', | ||
orange : '#ffa500', | ||
orangered : '#ff4500', | ||
orchid : '#da70d6', | ||
palegoldenrod : '#eee8aa', | ||
palegreen : '#98fb98', | ||
paleturquoise : '#afeeee', | ||
palevioletred : '#d87093', | ||
papayawhip : '#ffefd5', | ||
peachpuff : '#ffdab9', | ||
peru : '#cd853f', | ||
pink : '#ffc0cb', | ||
plum : '#dda0dd', | ||
powderblue : '#b0e0e6', | ||
purple : '#800080', | ||
red : '#f00', | ||
rosybrown : '#bc8f8f', | ||
royalblue : '#4169e1', | ||
saddlebrown : '#8b4513', | ||
salmon : '#fa8072', | ||
sandybrown : '#f4a460', | ||
seagreen : '#2e8b57', | ||
seashell : '#fff5ee', | ||
sienna : '#a0522d', | ||
silver : '#c0c0c0', | ||
skyblue : '#87ceeb', | ||
slateblue : '#6a5acd', | ||
slategray : '#708090', | ||
slategrey : '#708090', | ||
snow : '#fffafa', | ||
springgreen : '#00ff7f', | ||
steelblue : '#4682b4', | ||
tan : '#d2b48c', | ||
teal : '#008080', | ||
thistle : '#d8bfd8', | ||
tomato : '#ff6347', | ||
turquoise : '#40e0d0', | ||
violet : '#ee82ee', | ||
wheat : '#f5deb3', | ||
white : '#fff', | ||
whitesmoke : '#f5f5f5', | ||
yellow : '#ff0', | ||
yellowgreen : '#9acd32' | ||
var kCSSColorTable = { | ||
'transparent': [0,0,0,0], 'aliceblue': [240,248,255,1], | ||
'antiquewhite': [250,235,215,1], 'aqua': [0,255,255,1], | ||
'aquamarine': [127,255,212,1], 'azure': [240,255,255,1], | ||
'beige': [245,245,220,1], 'bisque': [255,228,196,1], | ||
'black': [0,0,0,1], 'blanchedalmond': [255,235,205,1], | ||
'blue': [0,0,255,1], 'blueviolet': [138,43,226,1], | ||
'brown': [165,42,42,1], 'burlywood': [222,184,135,1], | ||
'cadetblue': [95,158,160,1], 'chartreuse': [127,255,0,1], | ||
'chocolate': [210,105,30,1], 'coral': [255,127,80,1], | ||
'cornflowerblue': [100,149,237,1], 'cornsilk': [255,248,220,1], | ||
'crimson': [220,20,60,1], 'cyan': [0,255,255,1], | ||
'darkblue': [0,0,139,1], 'darkcyan': [0,139,139,1], | ||
'darkgoldenrod': [184,134,11,1], 'darkgray': [169,169,169,1], | ||
'darkgreen': [0,100,0,1], 'darkgrey': [169,169,169,1], | ||
'darkkhaki': [189,183,107,1], 'darkmagenta': [139,0,139,1], | ||
'darkolivegreen': [85,107,47,1], 'darkorange': [255,140,0,1], | ||
'darkorchid': [153,50,204,1], 'darkred': [139,0,0,1], | ||
'darksalmon': [233,150,122,1], 'darkseagreen': [143,188,143,1], | ||
'darkslateblue': [72,61,139,1], 'darkslategray': [47,79,79,1], | ||
'darkslategrey': [47,79,79,1], 'darkturquoise': [0,206,209,1], | ||
'darkviolet': [148,0,211,1], 'deeppink': [255,20,147,1], | ||
'deepskyblue': [0,191,255,1], 'dimgray': [105,105,105,1], | ||
'dimgrey': [105,105,105,1], 'dodgerblue': [30,144,255,1], | ||
'firebrick': [178,34,34,1], 'floralwhite': [255,250,240,1], | ||
'forestgreen': [34,139,34,1], 'fuchsia': [255,0,255,1], | ||
'gainsboro': [220,220,220,1], 'ghostwhite': [248,248,255,1], | ||
'gold': [255,215,0,1], 'goldenrod': [218,165,32,1], | ||
'gray': [128,128,128,1], 'green': [0,128,0,1], | ||
'greenyellow': [173,255,47,1], 'grey': [128,128,128,1], | ||
'honeydew': [240,255,240,1], 'hotpink': [255,105,180,1], | ||
'indianred': [205,92,92,1], 'indigo': [75,0,130,1], | ||
'ivory': [255,255,240,1], 'khaki': [240,230,140,1], | ||
'lavender': [230,230,250,1], 'lavenderblush': [255,240,245,1], | ||
'lawngreen': [124,252,0,1], 'lemonchiffon': [255,250,205,1], | ||
'lightblue': [173,216,230,1], 'lightcoral': [240,128,128,1], | ||
'lightcyan': [224,255,255,1], 'lightgoldenrodyellow': [250,250,210,1], | ||
'lightgray': [211,211,211,1], 'lightgreen': [144,238,144,1], | ||
'lightgrey': [211,211,211,1], 'lightpink': [255,182,193,1], | ||
'lightsalmon': [255,160,122,1], 'lightseagreen': [32,178,170,1], | ||
'lightskyblue': [135,206,250,1], 'lightslategray': [119,136,153,1], | ||
'lightslategrey': [119,136,153,1], 'lightsteelblue': [176,196,222,1], | ||
'lightyellow': [255,255,224,1], 'lime': [0,255,0,1], | ||
'limegreen': [50,205,50,1], 'linen': [250,240,230,1], | ||
'magenta': [255,0,255,1], 'maroon': [128,0,0,1], | ||
'mediumaquamarine': [102,205,170,1], 'mediumblue': [0,0,205,1], | ||
'mediumorchid': [186,85,211,1], 'mediumpurple': [147,112,219,1], | ||
'mediumseagreen': [60,179,113,1], 'mediumslateblue': [123,104,238,1], | ||
'mediumspringgreen': [0,250,154,1], 'mediumturquoise': [72,209,204,1], | ||
'mediumvioletred': [199,21,133,1], 'midnightblue': [25,25,112,1], | ||
'mintcream': [245,255,250,1], 'mistyrose': [255,228,225,1], | ||
'moccasin': [255,228,181,1], 'navajowhite': [255,222,173,1], | ||
'navy': [0,0,128,1], 'oldlace': [253,245,230,1], | ||
'olive': [128,128,0,1], 'olivedrab': [107,142,35,1], | ||
'orange': [255,165,0,1], 'orangered': [255,69,0,1], | ||
'orchid': [218,112,214,1], 'palegoldenrod': [238,232,170,1], | ||
'palegreen': [152,251,152,1], 'paleturquoise': [175,238,238,1], | ||
'palevioletred': [219,112,147,1], 'papayawhip': [255,239,213,1], | ||
'peachpuff': [255,218,185,1], 'peru': [205,133,63,1], | ||
'pink': [255,192,203,1], 'plum': [221,160,221,1], | ||
'powderblue': [176,224,230,1], 'purple': [128,0,128,1], | ||
'red': [255,0,0,1], 'rosybrown': [188,143,143,1], | ||
'royalblue': [65,105,225,1], 'saddlebrown': [139,69,19,1], | ||
'salmon': [250,128,114,1], 'sandybrown': [244,164,96,1], | ||
'seagreen': [46,139,87,1], 'seashell': [255,245,238,1], | ||
'sienna': [160,82,45,1], 'silver': [192,192,192,1], | ||
'skyblue': [135,206,235,1], 'slateblue': [106,90,205,1], | ||
'slategray': [112,128,144,1], 'slategrey': [112,128,144,1], | ||
'snow': [255,250,250,1], 'springgreen': [0,255,127,1], | ||
'steelblue': [70,130,180,1], 'tan': [210,180,140,1], | ||
'teal': [0,128,128,1], 'thistle': [216,191,216,1], | ||
'tomato': [255,99,71,1], 'turquoise': [64,224,208,1], | ||
'violet': [238,130,238,1], 'wheat': [245,222,179,1], | ||
'white': [255,255,255,1], 'whitesmoke': [245,245,245,1], | ||
'yellow': [255,255,0,1], 'yellowgreen': [154,205,50,1] | ||
}; | ||
/** | ||
* 自定义调色板 | ||
*/ | ||
function customPalette(userPalete) { | ||
palette = userPalete; | ||
function clampCssByte(i) { // Clamp to integer 0 .. 255. | ||
i = Math.round(i); // Seems to be what Chrome does (vs truncation). | ||
return i < 0 ? 0 : i > 255 ? 255 : i; | ||
} | ||
/** | ||
* 复位默认色板 | ||
*/ | ||
function resetPalette() { | ||
palette = _palette; | ||
function clampCssAngle(i) { // Clamp to integer 0 .. 360. | ||
i = Math.round(i); // Seems to be what Chrome does (vs truncation). | ||
return i < 0 ? 0 : i > 360 ? 360 : i; | ||
} | ||
/** | ||
* 获取色板颜色 | ||
* | ||
* @param {number} idx : 色板位置 | ||
* @param {array} [userPalete] : 自定义色板 | ||
* | ||
* @return {color} 颜色#000000~#ffffff | ||
*/ | ||
function getColor(idx, userPalete) { | ||
idx = idx | 0; | ||
userPalete = userPalete || palette; | ||
return userPalete[idx % userPalete.length]; | ||
function clampCssFloat(f) { // Clamp to float 0.0 .. 1.0. | ||
return f < 0 ? 0 : f > 1 ? 1 : f; | ||
} | ||
/** | ||
* 自定义默认高亮颜色 | ||
*/ | ||
function customHighlight(userHighlightColor) { | ||
highlightColor = userHighlightColor; | ||
function parseCssInt(str) { // int or percentage. | ||
if (str.length && str.charAt(str.length - 1) === '%') { | ||
return clampCssByte(parseFloat(str) / 100 * 255); | ||
} | ||
return clampCssByte(parseInt(str, 10)); | ||
} | ||
/** | ||
* 重置默认高亮颜色 | ||
*/ | ||
function resetHighlight() { | ||
_highlightColor = highlightColor; | ||
function parseCssFloat(str) { // float or percentage. | ||
if (str.length && str.charAt(str.length - 1) === '%') { | ||
return clampCssFloat(parseFloat(str) / 100); | ||
} | ||
return clampCssFloat(parseFloat(str)); | ||
} | ||
/** | ||
* 获取默认高亮颜色 | ||
*/ | ||
function getHighlightColor() { | ||
return highlightColor; | ||
} | ||
/** | ||
* 径向渐变 | ||
* | ||
* @param {number} x0 渐变起点 | ||
* @param {number} y0 | ||
* @param {number} r0 | ||
* @param {number} x1 渐变终点 | ||
* @param {number} y1 | ||
* @param {number} r1 | ||
* @param {Array} colorList 颜色列表 | ||
*/ | ||
function getRadialGradient(x0, y0, r0, x1, y1, r1, colorList) { | ||
if (!_ctx) { | ||
_ctx = util.getContext(); | ||
function cssHueToRgb(m1, m2, h) { | ||
if (h < 0) { | ||
h += 1; | ||
} | ||
var gradient = _ctx.createRadialGradient(x0, y0, r0, x1, y1, r1); | ||
for ( var i = 0, l = colorList.length; i < l; i++) { | ||
gradient.addColorStop(colorList[i][0], colorList[i][1]); | ||
else if (h > 1) { | ||
h -= 1; | ||
} | ||
gradient.__nonRecursion = true; | ||
return gradient; | ||
} | ||
/** | ||
* 线性渐变 | ||
* @param {Object} x0 渐变起点 | ||
* @param {Object} y0 | ||
* @param {Object} x1 渐变终点 | ||
* @param {Object} y1 | ||
* @param {Array} colorList 颜色列表 | ||
*/ | ||
function getLinearGradient(x0, y0, x1, y1, colorList) { | ||
if (!_ctx) { | ||
_ctx = util.getContext(); | ||
if (h * 6 < 1) { | ||
return m1 + (m2 - m1) * h * 6; | ||
} | ||
var gradient = _ctx.createLinearGradient(x0, y0, x1, y1); | ||
for ( var i = 0, l = colorList.length; i < l; i++) { | ||
gradient.addColorStop(colorList[i][0], colorList[i][1]); | ||
if (h * 2 < 1) { | ||
return m2; | ||
} | ||
gradient.__nonRecursion = true; | ||
return gradient; | ||
if (h * 3 < 2) { | ||
return m1 + (m2 - m1) * (2/3 - h) * 6; | ||
} | ||
return m1; | ||
} | ||
/** | ||
* 获取两种颜色之间渐变颜色数组 | ||
* @param {color} start 起始颜色 | ||
* @param {color} end 结束颜色 | ||
* @param {number} step 渐变级数 | ||
* @return {Array} 颜色数组 | ||
*/ | ||
function getStepColors(start, end, step) { | ||
start = toRGBA(start); | ||
end = toRGBA(end); | ||
start = getData(start); | ||
end = getData(end); | ||
var colors = []; | ||
var stepR = (end[0] - start[0]) / step; | ||
var stepG = (end[1] - start[1]) / step; | ||
var stepB = (end[2] - start[2]) / step; | ||
// 生成颜色集合 | ||
// fix by linfeng 颜色堆积 | ||
for (var i = 0, r = start[0], g = start[1], b = start[2]; i < step; i++ | ||
) { | ||
colors[i] = toColor([ | ||
adjust(Math.floor(r), [0, 255]), | ||
adjust(Math.floor(g), [0, 255]), | ||
adjust(Math.floor(b), [0, 255]) | ||
]); | ||
r += stepR; | ||
g += stepG; | ||
b += stepB; | ||
} | ||
r = end[0]; | ||
g = end[1]; | ||
b = end[2]; | ||
colors[i] = toColor([r, g, b]); | ||
return colors; | ||
function lerp(a, b, p) { | ||
return a + (b - a) * p; | ||
} | ||
/** | ||
* 获取指定级数的渐变颜色数组 | ||
* @param {Array} colors 颜色组 | ||
* @param {number=20} step 渐变级数 | ||
* @return {Array} 颜色数组 | ||
* @param {string} colorStr | ||
* @return {Array.<number>} | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function getGradientColors(colors, step) { | ||
var ret = []; | ||
var len = colors.length; | ||
if (step === undefined) { | ||
step = 20; | ||
function parse(colorStr) { | ||
if (!colorStr) { | ||
return; | ||
} | ||
if (len === 1) { | ||
ret = getStepColors(colors[0], colors[0], step); | ||
} else if (len > 1) { | ||
for ( var i = 0, n = len - 1; i < n; i++) { | ||
var steps = getStepColors(colors[i], colors[i + 1], step); | ||
if (i < n - 1) { | ||
steps.pop(); | ||
} | ||
ret = ret.concat(steps); | ||
} | ||
// colorStr may be not string | ||
colorStr = colorStr + ''; | ||
// Remove all whitespace, not compliant, but should just be more accepting. | ||
var str = colorStr.replace(/ /g, '').toLowerCase(); | ||
// Color keywords (and transparent) lookup. | ||
if (str in kCSSColorTable) { | ||
return kCSSColorTable[str].slice(); // dup. | ||
} | ||
return ret; | ||
} | ||
/** | ||
* 颜色值数组转为指定格式颜色,例如:<br/> | ||
* data = [60,20,20,0.1] format = 'rgba' | ||
* 返回:rgba(60,20,20,0.1) | ||
* @param {Array} data 颜色值数组 | ||
* @param {string} format 格式,默认rgb | ||
* @return {string} 颜色 | ||
*/ | ||
function toColor(data, format) { | ||
format = format || 'rgb'; | ||
if (data && (data.length === 3 || data.length === 4)) { | ||
data = map(data, | ||
function(c) { | ||
return c > 1 ? Math.ceil(c) : c; | ||
}); | ||
if (format.indexOf('hex') > -1) { | ||
return '#' + ((1 << 24) + (data[0] << 16) + (data[1] << 8) + (+data[2])).toString(16).slice(1); | ||
} else if (format.indexOf('hs') > -1) { | ||
var sx = map(data.slice(1, 3), | ||
function(c) { | ||
return c + '%'; | ||
}); | ||
data[1] = sx[0]; | ||
data[2] = sx[1]; | ||
// #abc and #abc123 syntax. | ||
if (str.charAt(0) === '#') { | ||
if (str.length === 4) { | ||
var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. | ||
if (!(iv >= 0 && iv <= 0xfff)) { | ||
return; // Covers NaN. | ||
} | ||
return [ | ||
((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8), | ||
(iv & 0xf0) | ((iv & 0xf0) >> 4), | ||
(iv & 0xf) | ((iv & 0xf) << 4), | ||
1 | ||
]; | ||
} | ||
if (format.indexOf('a') > -1) { | ||
if (data.length === 3) { | ||
data.push(1); | ||
else if (str.length === 7) { | ||
var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. | ||
if (!(iv >= 0 && iv <= 0xffffff)) { | ||
return; // Covers NaN. | ||
} | ||
data[3] = adjust(data[3], [0, 1]); | ||
return format + '(' + data.slice(0, 4).join(',') + ')'; | ||
return [ | ||
(iv & 0xff0000) >> 16, | ||
(iv & 0xff00) >> 8, | ||
iv & 0xff, | ||
1 | ||
]; | ||
} | ||
return format + '(' + data.slice(0, 3).join(',') + ')'; | ||
return; | ||
} | ||
} | ||
/** | ||
* 返回颜色值数组 | ||
* | ||
* @param {color} color 颜色 | ||
* @return {Array} 颜色值数组 | ||
*/ | ||
function toArray(color) { | ||
color = trim(color); | ||
if (color.indexOf('rgba') < 0) { | ||
color = toRGBA(color); | ||
var op = str.indexOf('('), ep = str.indexOf(')'); | ||
if (op !== -1 && ep + 1 === str.length) { | ||
var fname = str.substr(0, op); | ||
var params = str.substr(op + 1, ep - (op + 1)).split(','); | ||
var alpha = 1; // To allow case fallthrough. | ||
switch (fname) { | ||
case 'rgba': | ||
if (params.length !== 4) { | ||
return; | ||
} | ||
alpha = parseCssFloat(params.pop()); // jshint ignore:line | ||
// Fall through. | ||
case 'rgb': | ||
if (params.length !== 3) { | ||
return; | ||
} | ||
return [ | ||
parseCssInt(params[0]), | ||
parseCssInt(params[1]), | ||
parseCssInt(params[2]), | ||
alpha | ||
]; | ||
case 'hsla': | ||
if (params.length !== 4) { | ||
return; | ||
} | ||
params[3] = parseCssFloat(params[3]); | ||
return hsla2rgba(params); | ||
case 'hsl': | ||
if (params.length !== 3) { | ||
return; | ||
} | ||
return hsla2rgba(params); | ||
default: | ||
return; | ||
} | ||
} | ||
var data = []; | ||
var i = 0; | ||
color.replace(/[\d.]+/g, function (n) { | ||
if (i < 3) { | ||
n = n | 0; | ||
} else { | ||
// Alpha | ||
n = +n; | ||
} | ||
data[i++] = n; | ||
}); | ||
return data; | ||
return; | ||
} | ||
/** | ||
* 颜色格式转化 | ||
* | ||
* @param {Array} data 颜色值数组 | ||
* @param {string} format 格式,默认rgb | ||
* @return {string} 颜色 | ||
* @param {Array.<number>} hsla | ||
* @return {Array.<number>} rgba | ||
*/ | ||
function convert(color, format) { | ||
var data = getData(color); | ||
var alpha = data[3]; | ||
if(typeof alpha === 'undefined') { | ||
alpha = 1; | ||
} | ||
function hsla2rgba(hsla) { | ||
var h = (((parseFloat(hsla[0]) % 360) + 360) % 360) / 360; // 0 .. 1 | ||
// NOTE(deanm): According to the CSS spec s/l should only be | ||
// percentages, but we don't bother and let float or percentage. | ||
var s = parseCssFloat(hsla[1]); | ||
var l = parseCssFloat(hsla[2]); | ||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; | ||
var m1 = l * 2 - m2; | ||
if (color.indexOf('hsb') > -1) { | ||
data = _HSV_2_RGB(data); | ||
} else if (color.indexOf('hsl') > -1) { | ||
data = _HSL_2_RGB(data); | ||
} | ||
var rgba = [ | ||
clampCssByte(cssHueToRgb(m1, m2, h + 1 / 3) * 255), | ||
clampCssByte(cssHueToRgb(m1, m2, h) * 255), | ||
clampCssByte(cssHueToRgb(m1, m2, h - 1 / 3) * 255) | ||
]; | ||
if (format.indexOf('hsb') > -1 || format.indexOf('hsv') > -1) { | ||
data = _RGB_2_HSB(data); | ||
} else if (format.indexOf('hsl') > -1) { | ||
data = _RGB_2_HSL(data); | ||
if (hsla.length === 4) { | ||
rgba[3] = hsla[3]; | ||
} | ||
data[3] = alpha; | ||
return toColor(data, format); | ||
return rgba; | ||
} | ||
/** | ||
* 转换为rgba格式的颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} rgba颜色,rgba(r,g,b,a) | ||
* @param {Array.<number>} rgba | ||
* @return {Array.<number>} hsla | ||
*/ | ||
function toRGBA(color) { | ||
return convert(color, 'rgba'); | ||
} | ||
function rgba2hsla(rgba) { | ||
if (!rgba) { | ||
return; | ||
} | ||
/** | ||
* 转换为rgb数字格式的颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} rgb颜色,rgb(0,0,0)格式 | ||
*/ | ||
function toRGB(color) { | ||
return convert(color, 'rgb'); | ||
} | ||
// RGB from 0 to 255 | ||
var R = rgba[0] / 255; | ||
var G = rgba[1] / 255; | ||
var B = rgba[2] / 255; | ||
/** | ||
* 转换为16进制颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} 16进制颜色,#rrggbb格式 | ||
*/ | ||
function toHex(color) { | ||
return convert(color, 'hex'); | ||
} | ||
var vMin = Math.min(R, G, B); // Min. value of RGB | ||
var vMax = Math.max(R, G, B); // Max. value of RGB | ||
var delta = vMax - vMin; // Delta RGB value | ||
/** | ||
* 转换为HSV颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} HSVA颜色,hsva(h,s,v,a) | ||
*/ | ||
function toHSVA(color) { | ||
return convert(color, 'hsva'); | ||
} | ||
var L = (vMax + vMin) / 2; | ||
var H; | ||
var S; | ||
// HSL results from 0 to 1 | ||
if (delta === 0) { | ||
H = 0; | ||
S = 0; | ||
} | ||
else { | ||
if (L < 0.5) { | ||
S = delta / (vMax + vMin); | ||
} | ||
else { | ||
S = delta / (2 - vMax - vMin); | ||
} | ||
/** | ||
* 转换为HSV颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} HSV颜色,hsv(h,s,v) | ||
*/ | ||
function toHSV(color) { | ||
return convert(color, 'hsv'); | ||
} | ||
var deltaR = (((vMax - R) / 6) + (delta / 2)) / delta; | ||
var deltaG = (((vMax - G) / 6) + (delta / 2)) / delta; | ||
var deltaB = (((vMax - B) / 6) + (delta / 2)) / delta; | ||
/** | ||
* 转换为HSBA颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} HSBA颜色,hsba(h,s,b,a) | ||
*/ | ||
function toHSBA(color) { | ||
return convert(color, 'hsba'); | ||
} | ||
if (R === vMax) { | ||
H = deltaB - deltaG; | ||
} | ||
else if (G === vMax) { | ||
H = (1 / 3) + deltaR - deltaB; | ||
} | ||
else if (B === vMax) { | ||
H = (2 / 3) + deltaG - deltaR; | ||
} | ||
/** | ||
* 转换为HSB颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} HSB颜色,hsb(h,s,b) | ||
*/ | ||
function toHSB(color) { | ||
return convert(color, 'hsb'); | ||
} | ||
if (H < 0) { | ||
H += 1; | ||
} | ||
/** | ||
* 转换为HSLA颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} HSLA颜色,hsla(h,s,l,a) | ||
*/ | ||
function toHSLA(color) { | ||
return convert(color, 'hsla'); | ||
} | ||
/** | ||
* 转换为HSL颜色 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} HSL颜色,hsl(h,s,l) | ||
*/ | ||
function toHSL(color) { | ||
return convert(color, 'hsl'); | ||
} | ||
/** | ||
* 转换颜色名 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} 颜色名 | ||
*/ | ||
function toName(color) { | ||
for ( var key in _nameColors) { | ||
if (toHex(_nameColors[key]) === toHex(color)) { | ||
return key; | ||
if (H > 1) { | ||
H -= 1; | ||
} | ||
} | ||
return null; | ||
} | ||
/** | ||
* 移除颜色中多余空格 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} 无空格颜色 | ||
*/ | ||
function trim(color) { | ||
return String(color).replace(/\s+/g, ''); | ||
} | ||
var hsla = [H * 360, S, L]; | ||
/** | ||
* 颜色规范化 | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} 规范化后的颜色 | ||
*/ | ||
function normalize(color) { | ||
// 颜色名 | ||
if (_nameColors[color]) { | ||
color = _nameColors[color]; | ||
if (rgba[3] != null) { | ||
hsla.push(rgba[3]); | ||
} | ||
// 去掉空格 | ||
color = trim(color); | ||
// hsv与hsb等价 | ||
color = color.replace(/hsv/i, 'hsb'); | ||
// rgb转为rrggbb | ||
if (/^#[\da-f]{3}$/i.test(color)) { | ||
color = parseInt(color.slice(1), 16); | ||
var r = (color & 0xf00) << 8; | ||
var g = (color & 0xf0) << 4; | ||
var b = color & 0xf; | ||
color = '#'+ ((1 << 24) + (r << 4) + r + (g << 4) + g + (b << 4) + b).toString(16).slice(1); | ||
} | ||
// 或者使用以下正则替换,不过 chrome 下性能相对差点 | ||
// color = color.replace(/^#([\da-f])([\da-f])([\da-f])$/i, '#$1$1$2$2$3$3'); | ||
return color; | ||
return hsla; | ||
} | ||
/** | ||
* 颜色加深或减淡,当level>0加深,当level<0减淡 | ||
* | ||
* @param {string} color 颜色 | ||
* @param {number} level 升降程度,取值区间[-1,1] | ||
* @return {string} 加深或减淡后颜色值 | ||
* @param {string} color | ||
* @param {number} level | ||
* @return {string} | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function lift(color, level) { | ||
var direct = level > 0 ? 1 : -1; | ||
if (typeof level === 'undefined') { | ||
level = 0; | ||
} | ||
level = Math.abs(level) > 1 ? 1 : Math.abs(level); | ||
color = toRGB(color); | ||
var data = getData(color); | ||
for ( var i = 0; i < 3; i++) { | ||
if (direct === 1) { | ||
data[i] = data[i] * (1 - level) | 0; | ||
} else { | ||
data[i] = ((255 - data[i]) * level + data[i]) | 0; | ||
var colorArr = parse(color); | ||
if (colorArr) { | ||
for (var i = 0; i < 3; i++) { | ||
if (level < 0) { | ||
colorArr[i] = colorArr[i] * (1 - level) | 0; | ||
} | ||
else { | ||
colorArr[i] = ((255 - colorArr[i]) * level + colorArr[i]) | 0; | ||
} | ||
} | ||
return stringify(colorArr, colorArr.length === 4 ? 'rgba' : 'rgb'); | ||
} | ||
return 'rgb(' + data.join(',') + ')'; | ||
} | ||
/** | ||
* 颜色翻转,[255-r,255-g,255-b,1-a] | ||
* | ||
* @param {string} color 颜色 | ||
* @return {string} 翻转颜色 | ||
* @param {string} color | ||
* @return {string} | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function reverse(color) { | ||
var data = getData(toRGBA(color)); | ||
data = map(data, | ||
function(c) { | ||
return 255 - c; | ||
}); | ||
return toColor(data, 'rgb'); | ||
function toHex(color, level) { | ||
var colorArr = parse(color); | ||
if (colorArr) { | ||
return ((1 << 24) + (colorArr[0] << 16) + (colorArr[1] << 8) + (+colorArr[2])).toString(16).slice(1); | ||
} | ||
} | ||
/** | ||
* 简单两种颜色混合 | ||
* | ||
* @param {string} color1 第一种颜色 | ||
* @param {string} color2 第二种颜色 | ||
* @param {string} weight 混合权重[0-1] | ||
* @return {string} 结果色,rgb(r,g,b)或rgba(r,g,b,a) | ||
* Map value to color. Faster than mapToColor methods because color is represented by rgba array | ||
* @param {number} normalizedValue A float between 0 and 1. | ||
* @param {Array.<Array.<number>>} colors List of rgba color array | ||
* @param {Array.<number>} [out] Mapped gba color array | ||
* @return {Array.<number>} | ||
*/ | ||
function mix(color1, color2, weight) { | ||
if(typeof weight === 'undefined') { | ||
weight = 0.5; | ||
function fastMapToColor(normalizedValue, colors, out) { | ||
if (!(colors && colors.length) | ||
|| !(normalizedValue >= 0 && normalizedValue <= 1) | ||
) { | ||
return; | ||
} | ||
weight = 1 - adjust(weight, [0, 1]); | ||
var w = weight * 2 - 1; | ||
var data1 = getData(toRGBA(color1)); | ||
var data2 = getData(toRGBA(color2)); | ||
var d = data1[3] - data2[3]; | ||
var weight1 = (((w * d === -1) ? w : (w + d) / (1 + w * d)) + 1) / 2; | ||
var weight2 = 1 - weight1; | ||
var data = []; | ||
for ( var i = 0; i < 3; i++) { | ||
data[i] = data1[i] * weight1 + data2[i] * weight2; | ||
} | ||
var alpha = data1[3] * weight + data2[3] * (1 - weight); | ||
alpha = Math.max(0, Math.min(1, alpha)); | ||
if (data1[3] === 1 && data2[3] === 1) {// 不考虑透明度 | ||
return toColor(data, 'rgb'); | ||
} | ||
data[3] = alpha; | ||
return toColor(data, 'rgba'); | ||
out = out || [0, 0, 0, 0]; | ||
var value = normalizedValue * (colors.length - 1); | ||
var leftIndex = Math.floor(value); | ||
var rightIndex = Math.ceil(value); | ||
var leftColor = colors[leftIndex]; | ||
var rightColor = colors[rightIndex]; | ||
var dv = value - leftIndex; | ||
out[0] = clampCssByte(lerp(leftColor[0], rightColor[0], dv)); | ||
out[1] = clampCssByte(lerp(leftColor[1], rightColor[1], dv)); | ||
out[2] = clampCssByte(lerp(leftColor[2], rightColor[2], dv)); | ||
out[3] = clampCssByte(lerp(leftColor[3], rightColor[3], dv)); | ||
return out; | ||
} | ||
/** | ||
* 随机颜色 | ||
* | ||
* @return {string} 颜色值,#rrggbb格式 | ||
* @param {number} normalizedValue A float between 0 and 1. | ||
* @param {Array.<string>} colors Color list. | ||
* @param {boolean=} fullOutput Default false. | ||
* @return {(string|Object)} Result color. If fullOutput, | ||
* return {color: ..., leftIndex: ..., rightIndex: ..., value: ...}, | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function random() { | ||
return '#' + Math.random().toString(16).slice(2, 8); | ||
} | ||
/** | ||
* 获取颜色值数组,返回值范围: <br/> | ||
* RGB 范围[0-255] <br/> | ||
* HSL/HSV/HSB 范围[0-1]<br/> | ||
* A透明度范围[0-1] | ||
* 支持格式: | ||
* #rgb | ||
* #rrggbb | ||
* rgb(r,g,b) | ||
* rgb(r%,g%,b%) | ||
* rgba(r,g,b,a) | ||
* hsb(h,s,b) // hsv与hsb等价 | ||
* hsb(h%,s%,b%) | ||
* hsba(h,s,b,a) | ||
* hsl(h,s,l) | ||
* hsl(h%,s%,l%) | ||
* hsla(h,s,l,a) | ||
* | ||
* @param {string} color 颜色 | ||
* @return {Array} 颜色值数组或null | ||
*/ | ||
function getData(color) { | ||
color = normalize(color); | ||
var r = color.match(colorRegExp); | ||
if (r === null) { | ||
throw new Error('The color format error'); // 颜色格式错误 | ||
function mapToColor(normalizedValue, colors, fullOutput) { | ||
if (!(colors && colors.length) | ||
|| !(normalizedValue >= 0 && normalizedValue <= 1) | ||
) { | ||
return; | ||
} | ||
var d; | ||
var a; | ||
var data = []; | ||
var rgb; | ||
if (r[2]) { | ||
// #rrggbb | ||
d = r[2].replace('#', '').split(''); | ||
rgb = [d[0] + d[1], d[2] + d[3], d[4] + d[5]]; | ||
data = map(rgb, | ||
function(c) { | ||
return adjust(parseInt(c, 16), [0, 255]); | ||
}); | ||
var value = normalizedValue * (colors.length - 1); | ||
var leftIndex = Math.floor(value); | ||
var rightIndex = Math.ceil(value); | ||
var leftColor = parse(colors[leftIndex]); | ||
var rightColor = parse(colors[rightIndex]); | ||
var dv = value - leftIndex; | ||
} | ||
else if (r[4]) { | ||
// rgb rgba | ||
var rgba = (r[4]).split(','); | ||
a = rgba[3]; | ||
rgb = rgba.slice(0, 3); | ||
data = map( | ||
rgb, | ||
function(c) { | ||
c = Math.floor( | ||
c.indexOf('%') > 0 ? parseInt(c, 0) * 2.55 : c | ||
); | ||
return adjust(c, [0, 255]); | ||
} | ||
); | ||
var color = stringify( | ||
[ | ||
clampCssByte(lerp(leftColor[0], rightColor[0], dv)), | ||
clampCssByte(lerp(leftColor[1], rightColor[1], dv)), | ||
clampCssByte(lerp(leftColor[2], rightColor[2], dv)), | ||
clampCssFloat(lerp(leftColor[3], rightColor[3], dv)) | ||
], | ||
'rgba' | ||
); | ||
if(typeof a !== 'undefined') { | ||
data.push(adjust(parseFloat(a), [0, 1])); | ||
return fullOutput | ||
? { | ||
color: color, | ||
leftIndex: leftIndex, | ||
rightIndex: rightIndex, | ||
value: value | ||
} | ||
} | ||
else if (r[5] || r[6]) { | ||
// hsb hsba hsl hsla | ||
var hsxa = (r[5] || r[6]).split(','); | ||
var h = parseInt(hsxa[0], 0) / 360; | ||
var s = hsxa[1]; | ||
var x = hsxa[2]; | ||
a = hsxa[3]; | ||
data = map([s, x], | ||
function(c) { | ||
return adjust(parseFloat(c) / 100, [0, 1]); | ||
}); | ||
data.unshift(h); | ||
if( typeof a !== 'undefined') { | ||
data.push(adjust(parseFloat(a), [0, 1])); | ||
} | ||
} | ||
return data; | ||
: color; | ||
} | ||
/** | ||
* 设置颜色透明度 | ||
* @param {string} color 颜色 | ||
* @param {number} alpha 透明度,区间[0,1] | ||
* @return {string} rgba颜色值 | ||
* @param {Array<number>} interval Array length === 2, | ||
* each item is normalized value ([0, 1]). | ||
* @param {Array.<string>} colors Color list. | ||
* @return {Array.<Object>} colors corresponding to the interval, | ||
* each item is {color: 'xxx', offset: ...} | ||
* where offset is between 0 and 1. | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function alpha(color, a) { | ||
if (a === null) { | ||
a = 1; | ||
function mapIntervalToColor(interval, colors) { | ||
if (interval.length !== 2 || interval[1] < interval[0]) { | ||
return; | ||
} | ||
var data = getData(toRGBA(color)); | ||
data[3] = adjust(Number(a).toFixed(4), [0, 1]); | ||
return toColor(data, 'rgba'); | ||
} | ||
var info0 = mapToColor(interval[0], colors, true); | ||
var info1 = mapToColor(interval[1], colors, true); | ||
// 数组映射 | ||
function map(array, fun) { | ||
if (typeof fun !== 'function') { | ||
throw new TypeError(); | ||
} | ||
var len = array ? array.length : 0; | ||
for ( var i = 0; i < len; i++) { | ||
array[i] = fun(array[i]); | ||
} | ||
return array; | ||
} | ||
var result = [{color: info0.color, offset: 0}]; | ||
// 调整值区间 | ||
function adjust(value, region) { | ||
// < to <= & > to >= | ||
// modify by linzhifeng 2014-05-25 because -0 == 0 | ||
if (value <= region[0]) { | ||
value = region[0]; | ||
var during = info1.value - info0.value; | ||
var start = Math.max(info0.value, info0.rightIndex); | ||
var end = Math.min(info1.value, info1.leftIndex); | ||
for (var i = start; during > 0 && i <= end; i++) { | ||
result.push({ | ||
color: colors[i], | ||
offset: (i - info0.value) / during | ||
}); | ||
} | ||
else if (value >= region[1]) { | ||
value = region[1]; | ||
} | ||
return value; | ||
} | ||
result.push({color: info1.color, offset: 1}); | ||
// 参见 http:// www.easyrgb.com/index.php?X=MATH | ||
function _HSV_2_RGB(data) { | ||
var H = data[0]; | ||
var S = data[1]; | ||
var V = data[2]; | ||
// HSV from 0 to 1 | ||
var R, G, B; | ||
if (S === 0) { | ||
R = V * 255; | ||
G = V * 255; | ||
B = V * 255; | ||
} else { | ||
var h = H * 6; | ||
if (h === 6) { | ||
h = 0; | ||
} | ||
var i = h | 0; | ||
var v1 = V * (1 - S); | ||
var v2 = V * (1 - S * (h - i)); | ||
var v3 = V * (1 - S * (1 - (h - i))); | ||
var r = 0; | ||
var g = 0; | ||
var b = 0; | ||
if (i === 0) { | ||
r = V; | ||
g = v3; | ||
b = v1; | ||
} else if (i === 1) { | ||
r = v2; | ||
g = V; | ||
b = v1; | ||
} else if (i === 2) { | ||
r = v1; | ||
g = V; | ||
b = v3; | ||
} else if (i === 3) { | ||
r = v1; | ||
g = v2; | ||
b = V; | ||
} else if (i === 4) { | ||
r = v3; | ||
g = v1; | ||
b = V; | ||
} else { | ||
r = V; | ||
g = v1; | ||
b = v2; | ||
} | ||
// RGB results from 0 to 255 | ||
R = r * 255; | ||
G = g * 255; | ||
B = b * 255; | ||
} | ||
return [ R, G, B ]; | ||
return result; | ||
} | ||
function _HSL_2_RGB(data) { | ||
var H = data[0]; | ||
var S = data[1]; | ||
var L = data[2]; | ||
// HSL from 0 to 1 | ||
var R, G, B; | ||
if (S === 0) { | ||
R = L * 255; | ||
G = L * 255; | ||
B = L * 255; | ||
} else { | ||
var v2; | ||
if (L < 0.5) { | ||
v2 = L * (1 + S); | ||
} else { | ||
v2 = (L + S) - (S * L); | ||
} | ||
/** | ||
* @param {string} color | ||
* @param {number=} h 0 ~ 360, ignore when null. | ||
* @param {number=} s 0 ~ 1, ignore when null. | ||
* @param {number=} l 0 ~ 1, ignore when null. | ||
* @return {string} Color string in rgba format. | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function modifyHSL(color, h, s, l) { | ||
color = parse(color); | ||
var v1 = 2 * L - v2; | ||
if (color) { | ||
color = rgba2hsla(color); | ||
h != null && (color[0] = clampCssAngle(h)); | ||
s != null && (color[1] = parseCssFloat(s)); | ||
l != null && (color[2] = parseCssFloat(l)); | ||
R = 255 * _HUE_2_RGB(v1, v2, H + (1 / 3)); | ||
G = 255 * _HUE_2_RGB(v1, v2, H); | ||
B = 255 * _HUE_2_RGB(v1, v2, H - (1 / 3)); | ||
return stringify(hsla2rgba(color), 'rgba'); | ||
} | ||
return [ R, G, B ]; | ||
} | ||
function _HUE_2_RGB(v1, v2, vH) { | ||
if (vH < 0) { | ||
vH += 1; | ||
} | ||
if (vH > 1) { | ||
vH -= 1; | ||
} | ||
if ((6 * vH) < 1) { | ||
return (v1 + (v2 - v1) * 6 * vH); | ||
} | ||
if ((2 * vH) < 1) { | ||
return (v2); | ||
} | ||
if ((3 * vH) < 2) { | ||
return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6); | ||
} | ||
return v1; | ||
} | ||
/** | ||
* @param {string} color | ||
* @param {number=} alpha 0 ~ 1 | ||
* @return {string} Color string in rgba format. | ||
* @memberOf module:zrender/util/color | ||
*/ | ||
function modifyAlpha(color, alpha) { | ||
color = parse(color); | ||
function _RGB_2_HSB(data) { | ||
// RGB from 0 to 255 | ||
var R = (data[0] / 255); | ||
var G = (data[1] / 255); | ||
var B = (data[2] / 255); | ||
var vMin = Math.min(R, G, B); // Min. value of RGB | ||
var vMax = Math.max(R, G, B); // Max. value of RGB | ||
var delta = vMax - vMin; // Delta RGB value | ||
var V = vMax; | ||
var H; | ||
var S; | ||
// HSV results from 0 to 1 | ||
if (delta === 0) { | ||
H = 0; | ||
S = 0; | ||
} else { | ||
S = delta / vMax; | ||
var deltaR = (((vMax - R) / 6) + (delta / 2)) / delta; | ||
var deltaG = (((vMax - G) / 6) + (delta / 2)) / delta; | ||
var deltaB = (((vMax - B) / 6) + (delta / 2)) / delta; | ||
if (R === vMax) { | ||
H = deltaB - deltaG; | ||
} else if (G === vMax) { | ||
H = (1 / 3) + deltaR - deltaB; | ||
} else if (B === vMax) { | ||
H = (2 / 3) + deltaG - deltaR; | ||
} | ||
if (H < 0) { | ||
H += 1; | ||
} | ||
if (H > 1) { | ||
H -= 1; | ||
} | ||
if (color && alpha != null) { | ||
color[3] = clampCssFloat(alpha); | ||
return stringify(color, 'rgba'); | ||
} | ||
H = H * 360; | ||
S = S * 100; | ||
V = V * 100; | ||
return [ H, S, V ]; | ||
} | ||
function _RGB_2_HSL(data) { | ||
// RGB from 0 to 255 | ||
var R = (data[0] / 255); | ||
var G = (data[1] / 255); | ||
var B = (data[2] / 255); | ||
var vMin = Math.min(R, G, B); // Min. value of RGB | ||
var vMax = Math.max(R, G, B); // Max. value of RGB | ||
var delta = vMax - vMin; // Delta RGB value | ||
var L = (vMax + vMin) / 2; | ||
var H; | ||
var S; | ||
// HSL results from 0 to 1 | ||
if (delta === 0) { | ||
H = 0; | ||
S = 0; | ||
} else { | ||
if (L < 0.5) { | ||
S = delta / (vMax + vMin); | ||
} else { | ||
S = delta / (2 - vMax - vMin); | ||
} | ||
var deltaR = (((vMax - R) / 6) + (delta / 2)) / delta; | ||
var deltaG = (((vMax - G) / 6) + (delta / 2)) / delta; | ||
var deltaB = (((vMax - B) / 6) + (delta / 2)) / delta; | ||
if (R === vMax) { | ||
H = deltaB - deltaG; | ||
} else if (G === vMax) { | ||
H = (1 / 3) + deltaR - deltaB; | ||
} else if (B === vMax) { | ||
H = (2 / 3) + deltaG - deltaR; | ||
} | ||
if (H < 0) { | ||
H += 1; | ||
} | ||
if (H > 1) { | ||
H -= 1; | ||
} | ||
/** | ||
* @param {Array.<string>} colors Color list. | ||
* @param {string} type 'rgba', 'hsva', ... | ||
* @return {string} Result color. | ||
*/ | ||
function stringify(arrColor, type) { | ||
if (type === 'rgb' || type === 'hsv' || type === 'hsl') { | ||
arrColor = arrColor.slice(0, 3); | ||
} | ||
H = H * 360; | ||
S = S * 100; | ||
L = L * 100; | ||
return [ H, S, L ]; | ||
return type + '(' + arrColor.join(',') + ')'; | ||
} | ||
return { | ||
customPalette : customPalette, | ||
resetPalette : resetPalette, | ||
getColor : getColor, | ||
getHighlightColor : getHighlightColor, | ||
customHighlight : customHighlight, | ||
resetHighlight : resetHighlight, | ||
getRadialGradient : getRadialGradient, | ||
getLinearGradient : getLinearGradient, | ||
getGradientColors : getGradientColors, | ||
getStepColors : getStepColors, | ||
reverse : reverse, | ||
mix : mix, | ||
lift : lift, | ||
trim : trim, | ||
random : random, | ||
toRGB : toRGB, | ||
toRGBA : toRGBA, | ||
toHex : toHex, | ||
toHSL : toHSL, | ||
toHSLA : toHSLA, | ||
toHSB : toHSB, | ||
toHSBA : toHSBA, | ||
toHSV : toHSV, | ||
toHSVA : toHSVA, | ||
toName : toName, | ||
toColor : toColor, | ||
toArray : toArray, | ||
alpha : alpha, | ||
getData : getData | ||
parse: parse, | ||
lift: lift, | ||
toHex: toHex, | ||
fastMapToColor: fastMapToColor, | ||
mapToColor: mapToColor, | ||
mapIntervalToColor: mapIntervalToColor, | ||
modifyHSL: modifyHSL, | ||
modifyAlpha: modifyAlpha, | ||
stringify: stringify | ||
}; | ||
}); | ||
/*! | ||
* ZRender, a lightweight canvas library with a MVC architecture, data-driven | ||
* and provides an event model like DOM. | ||
* | ||
* ZRender, a high performance 2d drawing library. | ||
* | ||
* Copyright (c) 2013, Baidu Inc. | ||
* All rights reserved. | ||
* | ||
* | ||
* LICENSE | ||
* https://github.com/ecomfe/zrender/blob/master/LICENSE.txt | ||
*/ | ||
// Global defines | ||
define(function(require) { | ||
var guid = require('./core/guid'); | ||
var env = require('./core/env'); | ||
/** | ||
* zrender: core核心类 | ||
* | ||
* @desc zrender是一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型。 | ||
* @author Kener (@Kener-林峰, linzhifeng@baidu.com) | ||
* | ||
*/ | ||
define( | ||
function(require) { | ||
/* | ||
* HTML5 Canvas for Internet Explorer! | ||
* Modern browsers like Firefox, Safari, Chrome and Opera support | ||
* the HTML5 canvas tag to allow 2D command-based drawing. | ||
* ExplorerCanvas brings the same functionality to Internet Explorer. | ||
* To use, web developers only need to include a single script tag | ||
* in their existing web pages. | ||
* | ||
* https://code.google.com/p/explorercanvas/ | ||
* http://explorercanvas.googlecode.com/svn/trunk/excanvas.js | ||
*/ | ||
// 核心代码会生成一个全局变量 G_vmlCanvasManager,模块改造后借用于快速判断canvas支持 | ||
require('./lib/excanvas'); | ||
var Handler = require('./Handler'); | ||
var Storage = require('./Storage'); | ||
var Animation = require('./animation/Animation'); | ||
var util = require('./tool/util'); | ||
var log = require('./tool/log'); | ||
var guid = require('./tool/guid'); | ||
var useVML = !env.canvasSupported; | ||
var Handler = require('./Handler'); | ||
var Painter = require('./Painter'); | ||
var Storage = require('./Storage'); | ||
var Animation = require('./animation/Animation'); | ||
var painterCtors = { | ||
canvas: require('./Painter') | ||
}; | ||
var _instances = {}; //ZRender实例map索引 | ||
var instances = {}; // ZRender实例map索引 | ||
var zrender = {}; | ||
zrender.version = '2.0.2'; | ||
var zrender = {}; | ||
/** | ||
* @type {string} | ||
*/ | ||
zrender.version = '3.0.0'; | ||
/** | ||
* zrender初始化 | ||
* 不让外部直接new ZRender实例,为啥? | ||
* 不为啥,提供全局可控同时减少全局污染和降低命名冲突的风险! | ||
* | ||
* @param {HTMLElement} dom dom对象,不帮你做document.getElementById了 | ||
* @param {Object=} params 个性化参数,如自定义shape集合,带进来就好 | ||
* | ||
* @return {ZRender} ZRender实例 | ||
*/ | ||
zrender.init = function(dom, params) { | ||
var zi = new ZRender(guid(), dom, params || {}); | ||
_instances[zi.id] = zi; | ||
return zi; | ||
}; | ||
/** | ||
* @param {HTMLElement} dom | ||
* @param {Object} opts | ||
* @param {string} [opts.renderer='canvas'] 'canvas' or 'svg' | ||
* @param {number} [opts.devicePixelRatio] | ||
* @return {module:zrender/ZRender} | ||
*/ | ||
zrender.init = function(dom, opts) { | ||
var zr = new ZRender(guid(), dom, opts); | ||
instances[zr.id] = zr; | ||
return zr; | ||
}; | ||
/** | ||
* zrender实例销毁,记在_instances里的索引也会删除了 | ||
* 管生就得管死,可以通过zrender.dispose(zi)销毁指定ZRender实例 | ||
* 当然也可以直接zi.dispose()自己销毁 | ||
* | ||
* @param {ZRender=} zi ZRender对象,不传则销毁全部 | ||
*/ | ||
zrender.dispose = function (zi) { | ||
if (zi) { | ||
zi.dispose(); | ||
/** | ||
* Dispose zrender instance | ||
* @param {module:zrender/ZRender} zr | ||
*/ | ||
zrender.dispose = function (zr) { | ||
if (zr) { | ||
zr.dispose(); | ||
} | ||
else { | ||
for (var key in instances) { | ||
instances[key].dispose(); | ||
} | ||
else { | ||
for (var key in _instances) { | ||
_instances[key].dispose(); | ||
} | ||
_instances = {}; | ||
} | ||
instances = {}; | ||
} | ||
return zrender; | ||
}; | ||
return zrender; | ||
}; | ||
/** | ||
* 获取zrender实例 | ||
* | ||
* @param {string} id ZRender对象索引 | ||
*/ | ||
zrender.getInstance = function (id) { | ||
return _instances[id]; | ||
}; | ||
/** | ||
* 获取zrender实例 | ||
* @param {string} id ZRender对象索引 | ||
* @return {module:zrender/ZRender} | ||
*/ | ||
zrender.getInstance = function (id) { | ||
return instances[id]; | ||
}; | ||
/** | ||
* 删除zrender实例,ZRender实例dispose时会调用, | ||
* 删除后getInstance则返回undefined | ||
* ps: 仅是删除,删除的实例不代表已经dispose了~~ | ||
* 这是一个摆脱全局zrender.dispose()自动销毁的后门, | ||
* take care of yourself~ | ||
* | ||
* @param {string} id ZRender对象索引 | ||
*/ | ||
zrender.delInstance = function (id) { | ||
delete _instances[id]; | ||
return zrender; | ||
}; | ||
zrender.registerPainter = function (name, Ctor) { | ||
painterCtors[name] = Ctor; | ||
}; | ||
function getFrameCallback(zrInstance) { | ||
return function(){ | ||
var animatingShapes = zrInstance.animatingShapes; | ||
for (var i = 0, l = animatingShapes.length; i < l; i++) { | ||
zrInstance.storage.mod(animatingShapes[i].id); | ||
} | ||
function delInstance(id) { | ||
delete instances[id]; | ||
} | ||
if (animatingShapes.length || zrInstance._needsRefreshNextFrame) { | ||
zrInstance.refresh(); | ||
} | ||
}; | ||
} | ||
/** | ||
* @module zrender/ZRender | ||
*/ | ||
/** | ||
* @constructor | ||
* @alias module:zrender/ZRender | ||
* @param {string} id | ||
* @param {HTMLDomElement} dom | ||
* @param {Object} opts | ||
* @param {string} [opts.renderer='canvas'] 'canvas' or 'svg' | ||
* @param {number} [opts.devicePixelRatio] | ||
*/ | ||
var ZRender = function(id, dom, opts) { | ||
opts = opts || {}; | ||
/** | ||
* ZRender接口类,对外可用的所有接口都在这里!! | ||
* storage(M)、painter(V)、handler(C)为内部私有类,外部接口不可见 | ||
* 非get接口统一返回支持链式调用~ | ||
* | ||
* @param {string} id 唯一标识 | ||
* @param {HTMLElement} dom dom对象,不帮你做document.getElementById | ||
* | ||
* @return {ZRender} ZRender实例 | ||
* @type {HTMLDomElement} | ||
*/ | ||
function ZRender(id, dom) { | ||
this.id = id; | ||
this.env = require('./tool/env'); | ||
this.dom = dom; | ||
this.storage = new Storage(); | ||
this.painter = new Painter(dom, this.storage); | ||
this.handler = new Handler(dom, this.storage, this.painter); | ||
/** | ||
* @type {string} | ||
*/ | ||
this.id = id; | ||
// 动画控制 | ||
this.animatingShapes = []; | ||
this.animation = new Animation({ | ||
stage : { | ||
update : getFrameCallback(this) | ||
} | ||
}); | ||
this.animation.start(); | ||
var self = this; | ||
var storage = new Storage(); | ||
this._needsRefreshNextFrame = false; | ||
var rendererType = opts.renderer; | ||
if (useVML) { | ||
if (!painterCtors.vml) { | ||
throw new Error('You need to require \'zrender/vml/vml\' to support IE8'); | ||
} | ||
rendererType = 'vml'; | ||
} | ||
else if (!rendererType || !painterCtors[rendererType]) { | ||
rendererType = 'canvas'; | ||
} | ||
var painter = new painterCtors[rendererType](dom, storage, opts); | ||
/** | ||
* 获取实例唯一标识 | ||
*/ | ||
ZRender.prototype.getId = function () { | ||
return this.id; | ||
}; | ||
this.storage = storage; | ||
this.painter = painter; | ||
// VML 下为了性能可能会直接操作 VMLRoot 的位置 | ||
// 因此鼠标的相对位置应该是相对于 VMLRoot | ||
// PENDING | ||
if (!env.node) { | ||
this.handler = new Handler(painter.getViewportRoot(), storage, painter); | ||
} | ||
/** | ||
* 添加图形形状到根节点 | ||
* | ||
* @param {zrender.shape.Base} shape 形状对象,可用属性全集,详见各shape | ||
* @type {module:zrender/animation/Animation} | ||
*/ | ||
ZRender.prototype.addShape = function (shape) { | ||
this.storage.addRoot(shape); | ||
return this; | ||
}; | ||
this.animation = new Animation({ | ||
stage: { | ||
update: function () { | ||
if (self._needsRefresh) { | ||
self.refreshImmediately(); | ||
} | ||
} | ||
} | ||
}); | ||
this.animation.start(); | ||
/** | ||
* 添加组到根节点 | ||
* | ||
* @param {zrender.shape.Group} group | ||
* @type {boolean} | ||
* @private | ||
*/ | ||
ZRender.prototype.addGroup = function(group) { | ||
this.storage.addRoot(group); | ||
return this; | ||
}; | ||
this._needsRefresh; | ||
/** | ||
* 从根节点删除图形形状 | ||
* | ||
* @param {string} shapeId 形状对象唯一标识 | ||
*/ | ||
ZRender.prototype.delShape = function (shapeId) { | ||
this.storage.delRoot(shapeId); | ||
return this; | ||
}; | ||
// 修改 storage.delFromMap, 每次删除元素之前删除动画 | ||
// FIXME 有点ugly | ||
var oldDelFromMap = storage.delFromMap; | ||
var oldAddToMap = storage.addToMap; | ||
/** | ||
* 从根节点删除组 | ||
* | ||
* @param {string} groupId | ||
*/ | ||
ZRender.prototype.delGroup = function (groupId) { | ||
this.storage.delRoot(groupId); | ||
return this; | ||
storage.delFromMap = function (elId) { | ||
var el = storage.get(elId); | ||
oldDelFromMap.call(storage, elId); | ||
el && el.removeSelfFromZr(self); | ||
}; | ||
/** | ||
* 修改图形形状 | ||
* | ||
* @param {string} shapeId 形状对象唯一标识 | ||
* @param {Object} shape 形状对象 | ||
*/ | ||
ZRender.prototype.modShape = function (shapeId, shape) { | ||
this.storage.mod(shapeId, shape); | ||
return this; | ||
storage.addToMap = function (el) { | ||
oldAddToMap.call(storage, el); | ||
el.addSelfToZr(self); | ||
}; | ||
}; | ||
ZRender.prototype = { | ||
constructor: ZRender, | ||
/** | ||
* 修改组 | ||
* | ||
* @param {string} shapeId | ||
* @param {Object} group | ||
* 获取实例唯一标识 | ||
* @return {string} | ||
*/ | ||
ZRender.prototype.modGroup = function (groupId, group) { | ||
this.storage.mod(groupId, group); | ||
return this; | ||
}; | ||
getId: function () { | ||
return this.id; | ||
}, | ||
/** | ||
* 修改指定zlevel的绘制配置项,例如clearColor | ||
* | ||
* @param {string} zLevel | ||
* @param {Object} config 配置对象, 目前支持clearColor | ||
* 添加元素 | ||
* @param {string|module:zrender/Element} el | ||
*/ | ||
ZRender.prototype.modLayer = function (zLevel, config) { | ||
this.painter.modLayer(zLevel, config); | ||
return this; | ||
}; | ||
add: function (el) { | ||
this.storage.addRoot(el); | ||
this._needsRefresh = true; | ||
}, | ||
/** | ||
* 添加额外高亮层显示,仅提供添加方法,每次刷新后高亮层图形均被清空 | ||
* | ||
* @param {Object} shape 形状对象 | ||
* 删除元素 | ||
* @param {string|module:zrender/Element} el | ||
*/ | ||
ZRender.prototype.addHoverShape = function (shape) { | ||
this.storage.addHover(shape); | ||
return this; | ||
}; | ||
remove: function (el) { | ||
this.storage.delRoot(el); | ||
this._needsRefresh = true; | ||
}, | ||
/** | ||
* 渲染 | ||
* | ||
* @param {Function} callback 渲染结束后回调函数 | ||
* todo:增加缓动函数 | ||
*/ | ||
ZRender.prototype.render = function (callback) { | ||
this.painter.render(callback); | ||
this._needsRefreshNextFrame = false; | ||
return this; | ||
}; | ||
* 修改指定zlevel的绘制配置项 | ||
* | ||
* @param {string} zLevel | ||
* @param {Object} config 配置对象 | ||
* @param {string} [config.clearColor=0] 每次清空画布的颜色 | ||
* @param {string} [config.motionBlur=false] 是否开启动态模糊 | ||
* @param {number} [config.lastFrameAlpha=0.7] | ||
* 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显 | ||
*/ | ||
configLayer: function (zLevel, config) { | ||
this.painter.configLayer(zLevel, config); | ||
this._needsRefresh = true; | ||
}, | ||
/** | ||
* 视图更新 | ||
* | ||
* @param {Function} callback 视图更新后回调函数 | ||
*/ | ||
ZRender.prototype.refresh = function (callback) { | ||
this.painter.refresh(callback); | ||
this._needsRefreshNextFrame = false; | ||
return this; | ||
}; | ||
refreshImmediately: function () { | ||
// Clear needsRefresh ahead to avoid something wrong happens in refresh | ||
// Or it will cause zrender refreshes again and again. | ||
this._needsRefresh = false; | ||
this.painter.refresh(); | ||
/** | ||
* Avoid trigger zr.refresh in Element#beforeUpdate hook | ||
*/ | ||
this._needsRefresh = false; | ||
}, | ||
// TODO | ||
// 好像会有奇怪的问题 | ||
ZRender.prototype.refreshNextFrame = function() { | ||
this._needsRefreshNextFrame = true; | ||
return this; | ||
}; | ||
/** | ||
* 高亮层更新 | ||
* | ||
* @param {Function} callback 视图更新后回调函数 | ||
* 标记视图在浏览器下一帧需要绘制 | ||
*/ | ||
ZRender.prototype.refreshHover = function (callback) { | ||
this.painter.refreshHover(callback); | ||
return this; | ||
}; | ||
refresh: function() { | ||
this._needsRefresh = true; | ||
}, | ||
/** | ||
* 视图更新 | ||
* | ||
* @param {Array} shapeList 需要更新的图形元素列表 | ||
* @param {Function} callback 视图更新后回调函数 | ||
* 调整视图大小 | ||
*/ | ||
ZRender.prototype.update = function (shapeList, callback) { | ||
this.painter.update(shapeList, callback); | ||
return this; | ||
}; | ||
ZRender.prototype.resize = function() { | ||
resize: function() { | ||
this.painter.resize(); | ||
return this; | ||
}; | ||
this.handler && this.handler.resize(); | ||
}, | ||
/** | ||
* 动画 | ||
* | ||
* @param {string} shapeId 形状对象唯一标识 | ||
* @param {string} path 需要添加动画的属性获取路径,可以通过a.b.c来获取深层的属性 | ||
* @param {boolean} loop 动画是否循环 | ||
* @return {Object} 动画的Deferred对象 | ||
* Example: | ||
* zr.animate(circleId, 'style', false) | ||
* .when(1000, { x: 10} ) | ||
* .done(function(){ console.log('Animation done')}) | ||
* .start() | ||
*/ | ||
ZRender.prototype.animate = function (shapeId, path, loop) { | ||
var shape = this.storage.get(shapeId); | ||
if (shape) { | ||
var target; | ||
if (path) { | ||
var pathSplitted = path.split('.'); | ||
var prop = shape; | ||
for (var i = 0, l = pathSplitted.length; i < l; i++) { | ||
if (!prop) { | ||
continue; | ||
} | ||
prop = prop[pathSplitted[i]]; | ||
} | ||
if (prop) { | ||
target = prop; | ||
} | ||
} | ||
else { | ||
target = shape; | ||
} | ||
if (!target) { | ||
log( | ||
'Property "' | ||
+ path | ||
+ '" is not existed in shape ' | ||
+ shapeId | ||
); | ||
return; | ||
} | ||
var animatingShapes = this.animatingShapes; | ||
if (typeof shape.__aniCount === 'undefined') { | ||
// 正在进行的动画记数 | ||
shape.__aniCount = 0; | ||
} | ||
if (shape.__aniCount === 0) { | ||
animatingShapes.push(shape); | ||
} | ||
shape.__aniCount++; | ||
return this.animation.animate(target, {loop : loop}) | ||
.done(function() { | ||
shape.__aniCount --; | ||
if (shape.__aniCount === 0) { | ||
// 从animatingShapes里移除 | ||
var idx = util.indexOf(animatingShapes, shape); | ||
animatingShapes.splice(idx, 1); | ||
} | ||
}); | ||
} | ||
else { | ||
log('Shape "'+ shapeId + '" not existed'); | ||
} | ||
}; | ||
/** | ||
* 停止所有动画 | ||
*/ | ||
ZRender.prototype.clearAnimation = function () { | ||
clearAnimation: function () { | ||
this.animation.clear(); | ||
}; | ||
}, | ||
/** | ||
* loading显示 | ||
* | ||
* @param {Object=} loadingEffect loading效果对象 | ||
*/ | ||
ZRender.prototype.showLoading = function (loadingEffect) { | ||
this.painter.showLoading(loadingEffect); | ||
return this; | ||
}; | ||
/** | ||
* loading结束 | ||
*/ | ||
ZRender.prototype.hideLoading = function () { | ||
this.painter.hideLoading(); | ||
return this; | ||
}; | ||
/** | ||
* 获取视图宽度 | ||
*/ | ||
ZRender.prototype.getWidth = function() { | ||
getWidth: function() { | ||
return this.painter.getWidth(); | ||
}; | ||
}, | ||
@@ -402,63 +261,74 @@ /** | ||
*/ | ||
ZRender.prototype.getHeight = function() { | ||
getHeight: function() { | ||
return this.painter.getHeight(); | ||
}; | ||
}, | ||
/** | ||
* 图像导出 | ||
* 图像导出 | ||
* @param {string} type | ||
* @param {string} [backgroundColor='#fff'] 背景色 | ||
* @return {string} 图片的Base64 url | ||
*/ | ||
ZRender.prototype.toDataURL = function(type, backgroundColor, args) { | ||
toDataURL: function(type, backgroundColor, args) { | ||
return this.painter.toDataURL(type, backgroundColor, args); | ||
}; | ||
}, | ||
/** | ||
* 将常规shape转成image shape | ||
* @param {module:zrender/shape/Base} e | ||
* @param {number} width | ||
* @param {number} height | ||
*/ | ||
ZRender.prototype.shapeToImage = function(e, width, height) { | ||
pathToImage: function(e, width, height) { | ||
var id = guid(); | ||
return this.painter.shapeToImage(id, e, width, height); | ||
}; | ||
return this.painter.pathToImage(id, e, width, height); | ||
}, | ||
/** | ||
* 设置默认的cursor style | ||
* @param {string} cursorStyle 例如 crosshair | ||
*/ | ||
setDefaultCursorStyle: function (cursorStyle) { | ||
this.handler.setDefaultCursorStyle(cursorStyle); | ||
}, | ||
/** | ||
* 事件绑定 | ||
* | ||
* | ||
* @param {string} eventName 事件名称 | ||
* @param {Function} eventHandler 响应函数 | ||
* @param {Object} [context] 响应函数 | ||
*/ | ||
ZRender.prototype.on = function(eventName, eventHandler) { | ||
this.handler.on(eventName, eventHandler); | ||
return this; | ||
}; | ||
on: function(eventName, eventHandler, context) { | ||
this.handler && this.handler.on(eventName, eventHandler, context); | ||
}, | ||
/** | ||
* 事件解绑定,参数为空则解绑所有自定义事件 | ||
* | ||
* | ||
* @param {string} eventName 事件名称 | ||
* @param {Function} eventHandler 响应函数 | ||
*/ | ||
ZRender.prototype.un = function(eventName, eventHandler) { | ||
this.handler.un(eventName, eventHandler); | ||
return this; | ||
}; | ||
off: function(eventName, eventHandler) { | ||
this.handler && this.handler.off(eventName, eventHandler); | ||
}, | ||
/** | ||
* 事件触发 | ||
* | ||
* @param {string} event 事件名称,resize,hover,drag,etc~ | ||
* | ||
* @param {string} eventName 事件名称,resize,hover,drag,etc | ||
* @param {event=} event event dom事件对象 | ||
*/ | ||
ZRender.prototype.trigger = function (eventName, event) { | ||
this.handler.trigger(eventName, event); | ||
return this; | ||
}; | ||
trigger: function (eventName, event) { | ||
this.handler && this.handler.trigger(eventName, event); | ||
}, | ||
/** | ||
* 清除当前ZRender下所有类图的数据和显示,clear后MVC和已绑定事件均还存在在,ZRender可用 | ||
*/ | ||
ZRender.prototype.clear = function () { | ||
clear: function () { | ||
this.storage.delRoot(); | ||
this.painter.clear(); | ||
return this; | ||
}; | ||
}, | ||
@@ -468,22 +338,20 @@ /** | ||
*/ | ||
ZRender.prototype.dispose = function () { | ||
dispose: function () { | ||
this.animation.stop(); | ||
this.clear(); | ||
this.storage.dispose(); | ||
this.painter.dispose(); | ||
this.handler.dispose(); | ||
this.handler && this.handler.dispose(); | ||
this.animation = | ||
this.animatingShapes = | ||
this.storage = | ||
this.painter = | ||
this.animation = | ||
this.storage = | ||
this.painter = | ||
this.handler = null; | ||
//释放后告诉全局删除对自己的索引,没想到啥好方法 | ||
zrender.delInstance(this.id); | ||
}; | ||
delInstance(this.id); | ||
} | ||
}; | ||
return zrender; | ||
} | ||
); | ||
return zrender; | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
High entropy strings
Supply chain riskContains high entropy strings. This could be a sign of encrypted data, leaked secrets or obfuscated code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No License Found
License(Experimental) License information could not be found.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
1877161
87
47241
1
32
31
16