| (function(_global){ |
| |
| /** |
| * almond 0.2.5 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved. |
| * Available via the MIT or new BSD license. |
| * see: http://github.com/jrburke/almond for details |
| */ |
| //Going sloppy to avoid 'use strict' string cost, but strict practices should |
| //be followed. |
| /*jslint sloppy: true */ |
| /*global setTimeout: false */ |
| |
| var requirejs, require, define; |
| (function (undef) { |
| var main, req, makeMap, handlers, |
| defined = {}, |
| waiting = {}, |
| config = {}, |
| defining = {}, |
| hasOwn = Object.prototype.hasOwnProperty, |
| aps = [].slice; |
| |
| function hasProp(obj, prop) { |
| return hasOwn.call(obj, prop); |
| } |
| |
| /** |
| * Given a relative module name, like ./something, normalize it to |
| * a real name that can be mapped to a path. |
| * @param {String} name the relative name |
| * @param {String} baseName a real name that the name arg is relative |
| * to. |
| * @returns {String} normalized name |
| */ |
| function normalize(name, baseName) { |
| var nameParts, nameSegment, mapValue, foundMap, |
| foundI, foundStarMap, starI, i, j, part, |
| baseParts = baseName && baseName.split("/"), |
| map = config.map, |
| starMap = (map && map['*']) || {}; |
| |
| //Adjust any relative paths. |
| if (name && name.charAt(0) === ".") { |
| //If have a base name, try to normalize against it, |
| //otherwise, assume it is a top-level require that will |
| //be relative to baseUrl in the end. |
| if (baseName) { |
| //Convert baseName to array, and lop off the last part, |
| //so that . matches that "directory" and not name of the baseName's |
| //module. For instance, baseName of "one/two/three", maps to |
| //"one/two/three.js", but we want the directory, "one/two" for |
| //this normalization. |
| baseParts = baseParts.slice(0, baseParts.length - 1); |
| |
| name = baseParts.concat(name.split("/")); |
| |
| //start trimDots |
| for (i = 0; i < name.length; i += 1) { |
| part = name[i]; |
| if (part === ".") { |
| name.splice(i, 1); |
| i -= 1; |
| } else if (part === "..") { |
| if (i === 1 && (name[2] === '..' || name[0] === '..')) { |
| //End of the line. Keep at least one non-dot |
| //path segment at the front so it can be mapped |
| //correctly to disk. Otherwise, there is likely |
| //no path mapping for a path starting with '..'. |
| //This can still fail, but catches the most reasonable |
| //uses of .. |
| break; |
| } else if (i > 0) { |
| name.splice(i - 1, 2); |
| i -= 2; |
| } |
| } |
| } |
| //end trimDots |
| |
| name = name.join("/"); |
| } else if (name.indexOf('./') === 0) { |
| // No baseName, so this is ID is resolved relative |
| // to baseUrl, pull off the leading dot. |
| name = name.substring(2); |
| } |
| } |
| |
| //Apply map config if available. |
| if ((baseParts || starMap) && map) { |
| nameParts = name.split('/'); |
| |
| for (i = nameParts.length; i > 0; i -= 1) { |
| nameSegment = nameParts.slice(0, i).join("/"); |
| |
| if (baseParts) { |
| //Find the longest baseName segment match in the config. |
| //So, do joins on the biggest to smallest lengths of baseParts. |
| for (j = baseParts.length; j > 0; j -= 1) { |
| mapValue = map[baseParts.slice(0, j).join('/')]; |
| |
| //baseName segment has config, find if it has one for |
| //this name. |
| if (mapValue) { |
| mapValue = mapValue[nameSegment]; |
| if (mapValue) { |
| //Match, update name to the new value. |
| foundMap = mapValue; |
| foundI = i; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (foundMap) { |
| break; |
| } |
| |
| //Check for a star map match, but just hold on to it, |
| //if there is a shorter segment match later in a matching |
| //config, then favor over this star map. |
| if (!foundStarMap && starMap && starMap[nameSegment]) { |
| foundStarMap = starMap[nameSegment]; |
| starI = i; |
| } |
| } |
| |
| if (!foundMap && foundStarMap) { |
| foundMap = foundStarMap; |
| foundI = starI; |
| } |
| |
| if (foundMap) { |
| nameParts.splice(0, foundI, foundMap); |
| name = nameParts.join('/'); |
| } |
| } |
| |
| return name; |
| } |
| |
| function makeRequire(relName, forceSync) { |
| return function () { |
| //A version of a require function that passes a moduleName |
| //value for items that may need to |
| //look up paths relative to the moduleName |
| return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); |
| }; |
| } |
| |
| function makeNormalize(relName) { |
| return function (name) { |
| return normalize(name, relName); |
| }; |
| } |
| |
| function makeLoad(depName) { |
| return function (value) { |
| defined[depName] = value; |
| }; |
| } |
| |
| function callDep(name) { |
| if (hasProp(waiting, name)) { |
| var args = waiting[name]; |
| delete waiting[name]; |
| defining[name] = true; |
| main.apply(undef, args); |
| } |
| |
| if (!hasProp(defined, name) && !hasProp(defining, name)) { |
| throw new Error('No ' + name); |
| } |
| return defined[name]; |
| } |
| |
| //Turns a plugin!resource to [plugin, resource] |
| //with the plugin being undefined if the name |
| //did not have a plugin prefix. |
| function splitPrefix(name) { |
| var prefix, |
| index = name ? name.indexOf('!') : -1; |
| if (index > -1) { |
| prefix = name.substring(0, index); |
| name = name.substring(index + 1, name.length); |
| } |
| return [prefix, name]; |
| } |
| |
| /** |
| * Makes a name map, normalizing the name, and using a plugin |
| * for normalization if necessary. Grabs a ref to plugin |
| * too, as an optimization. |
| */ |
| makeMap = function (name, relName) { |
| var plugin, |
| parts = splitPrefix(name), |
| prefix = parts[0]; |
| |
| name = parts[1]; |
| |
| if (prefix) { |
| prefix = normalize(prefix, relName); |
| plugin = callDep(prefix); |
| } |
| |
| //Normalize according |
| if (prefix) { |
| if (plugin && plugin.normalize) { |
| name = plugin.normalize(name, makeNormalize(relName)); |
| } else { |
| name = normalize(name, relName); |
| } |
| } else { |
| name = normalize(name, relName); |
| parts = splitPrefix(name); |
| prefix = parts[0]; |
| name = parts[1]; |
| if (prefix) { |
| plugin = callDep(prefix); |
| } |
| } |
| |
| //Using ridiculous property names for space reasons |
| return { |
| f: prefix ? prefix + '!' + name : name, //fullName |
| n: name, |
| pr: prefix, |
| p: plugin |
| }; |
| }; |
| |
| function makeConfig(name) { |
| return function () { |
| return (config && config.config && config.config[name]) || {}; |
| }; |
| } |
| |
| handlers = { |
| require: function (name) { |
| return makeRequire(name); |
| }, |
| exports: function (name) { |
| var e = defined[name]; |
| if (typeof e !== 'undefined') { |
| return e; |
| } else { |
| return (defined[name] = {}); |
| } |
| }, |
| module: function (name) { |
| return { |
| id: name, |
| uri: '', |
| exports: defined[name], |
| config: makeConfig(name) |
| }; |
| } |
| }; |
| |
| main = function (name, deps, callback, relName) { |
| var cjsModule, depName, ret, map, i, |
| args = [], |
| usingExports; |
| |
| //Use name if no relName |
| relName = relName || name; |
| |
| //Call the callback to define the module, if necessary. |
| if (typeof callback === 'function') { |
| |
| //Pull out the defined dependencies and pass the ordered |
| //values to the callback. |
| //Default to [require, exports, module] if no deps |
| deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; |
| for (i = 0; i < deps.length; i += 1) { |
| map = makeMap(deps[i], relName); |
| depName = map.f; |
| |
| //Fast path CommonJS standard dependencies. |
| if (depName === "require") { |
| args[i] = handlers.require(name); |
| } else if (depName === "exports") { |
| //CommonJS module spec 1.1 |
| args[i] = handlers.exports(name); |
| usingExports = true; |
| } else if (depName === "module") { |
| //CommonJS module spec 1.1 |
| cjsModule = args[i] = handlers.module(name); |
| } else if (hasProp(defined, depName) || |
| hasProp(waiting, depName) || |
| hasProp(defining, depName)) { |
| args[i] = callDep(depName); |
| } else if (map.p) { |
| map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); |
| args[i] = defined[depName]; |
| } else { |
| throw new Error(name + ' missing ' + depName); |
| } |
| } |
| |
| ret = callback.apply(defined[name], args); |
| |
| if (name) { |
| //If setting exports via "module" is in play, |
| //favor that over return value and exports. After that, |
| //favor a non-undefined return value over exports use. |
| if (cjsModule && cjsModule.exports !== undef && |
| cjsModule.exports !== defined[name]) { |
| defined[name] = cjsModule.exports; |
| } else if (ret !== undef || !usingExports) { |
| //Use the return value from the function. |
| defined[name] = ret; |
| } |
| } |
| } else if (name) { |
| //May just be an object definition for the module. Only |
| //worry about defining if have a module name. |
| defined[name] = callback; |
| } |
| }; |
| |
| requirejs = require = req = function (deps, callback, relName, forceSync, alt) { |
| if (typeof deps === "string") { |
| if (handlers[deps]) { |
| //callback in this case is really relName |
| return handlers[deps](callback); |
| } |
| //Just return the module wanted. In this scenario, the |
| //deps arg is the module name, and second arg (if passed) |
| //is just the relName. |
| //Normalize module name, if it contains . or .. |
| return callDep(makeMap(deps, callback).f); |
| } else if (!deps.splice) { |
| //deps is a config object, not an array. |
| config = deps; |
| if (callback.splice) { |
| //callback is an array, which means it is a dependency list. |
| //Adjust args if there are dependencies |
| deps = callback; |
| callback = relName; |
| relName = null; |
| } else { |
| deps = undef; |
| } |
| } |
| |
| //Support require(['a']) |
| callback = callback || function () {}; |
| |
| //If relName is a function, it is an errback handler, |
| //so remove it. |
| if (typeof relName === 'function') { |
| relName = forceSync; |
| forceSync = alt; |
| } |
| |
| //Simulate async callback; |
| if (forceSync) { |
| main(undef, deps, callback, relName); |
| } else { |
| //Using a non-zero value because of concern for what old browsers |
| //do, and latest browsers "upgrade" to 4 if lower value is used: |
| //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: |
| //If want a value immediately, use require('id') instead -- something |
| //that works in almond on the global level, but not guaranteed and |
| //unlikely to work in other AMD implementations. |
| setTimeout(function () { |
| main(undef, deps, callback, relName); |
| }, 4); |
| } |
| |
| return req; |
| }; |
| |
| /** |
| * Just drops the config on the floor, but returns req in case |
| * the config return value is used. |
| */ |
| req.config = function (cfg) { |
| config = cfg; |
| if (config.deps) { |
| req(config.deps, config.callback); |
| } |
| return req; |
| }; |
| |
| define = function (name, deps, callback) { |
| |
| //This module may not have dependencies |
| if (!deps.splice) { |
| //deps is not an array, so probably means |
| //an object literal or factory function for |
| //the value. Adjust args. |
| callback = deps; |
| deps = []; |
| } |
| |
| if (!hasProp(defined, name) && !hasProp(waiting, name)) { |
| waiting[name] = [name, deps, callback]; |
| } |
| }; |
| |
| define.amd = { |
| jQuery: true |
| }; |
| }()); |
| |
| /** |
| * echarts默认配置项 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/config',[],function() { |
| // 请原谅我这样写,这显然可以直接返回个对象,但那样的话outline就显示不出来了~~ |
| var config = { |
| // 图表类型 |
| CHART_TYPE_LINE: 'line', |
| CHART_TYPE_BAR: 'bar', |
| CHART_TYPE_SCATTER: 'scatter', |
| CHART_TYPE_PIE: 'pie', |
| CHART_TYPE_RADAR: 'radar', |
| CHART_TYPE_MAP: 'map', |
| CHART_TYPE_K: 'k', |
| CHART_TYPE_ISLAND: 'island', |
| CHART_TYPE_FORCE : 'force', |
| CHART_TYPE_CHORD : 'chord', |
| CHART_TYPE_GAUGE : 'gauge', |
| CHART_TYPE_FUNNEL : 'funnel', |
| |
| // 组件类型 |
| COMPONENT_TYPE_TITLE: 'title', |
| COMPONENT_TYPE_LEGEND: 'legend', |
| COMPONENT_TYPE_DATARANGE: 'dataRange', |
| COMPONENT_TYPE_DATAVIEW: 'dataView', |
| COMPONENT_TYPE_DATAZOOM: 'dataZoom', |
| COMPONENT_TYPE_TOOLBOX: 'toolbox', |
| COMPONENT_TYPE_TOOLTIP: 'tooltip', |
| COMPONENT_TYPE_GRID: 'grid', |
| COMPONENT_TYPE_AXIS: 'axis', |
| COMPONENT_TYPE_POLAR: 'polar', |
| COMPONENT_TYPE_X_AXIS: 'xAxis', |
| COMPONENT_TYPE_Y_AXIS: 'yAxis', |
| COMPONENT_TYPE_AXIS_CATEGORY: 'categoryAxis', |
| COMPONENT_TYPE_AXIS_VALUE: 'valueAxis', |
| COMPONENT_TYPE_TIMELINE: 'timeline', |
| |
| // 全图默认背景 |
| backgroundColor: 'rgba(0,0,0,0)', |
| |
| // 默认色板 |
| color: ['#ff7f50','#87cefa','#da70d6','#32cd32','#6495ed', |
| '#ff69b4','#ba55d3','#cd5c5c','#ffa500','#40e0d0', |
| '#1e90ff','#ff6347','#7b68ee','#00fa9a','#ffd700', |
| '#6699FF','#ff6666','#3cb371','#b8860b','#30e0e0'], |
| |
| // 图表标题 |
| title: { |
| text: '', |
| //link: null, // 超链接跳转 |
| //target: null, // 仅支持self | blank |
| subtext: '', |
| //sublink: null, // 超链接跳转 |
| //subtarget: null, // 仅支持self | blank |
| x: 'left', // 水平安放位置,默认为左对齐,可选为: |
| // 'center' ¦ 'left' ¦ 'right' |
| // ¦ {number}(x坐标,单位px) |
| y: 'top', // 垂直安放位置,默认为全图顶端,可选为: |
| // 'top' ¦ 'bottom' ¦ 'center' |
| // ¦ {number}(y坐标,单位px) |
| //textAlign: null // 水平对齐方式,默认根据x设置自动调整 |
| backgroundColor: 'rgba(0,0,0,0)', |
| borderColor: '#ccc', // 标题边框颜色 |
| borderWidth: 0, // 标题边框线宽,单位px,默认为0(无边框) |
| padding: 5, // 标题内边距,单位px,默认各方向内边距为5, |
| // 接受数组分别设定上右下左边距,同css |
| itemGap: 5, // 主副标题纵向间隔,单位px,默认为10, |
| textStyle: { |
| fontSize: 18, |
| fontWeight: 'bolder', |
| color: '#333' // 主标题文字颜色 |
| }, |
| subtextStyle: { |
| color: '#aaa' // 副标题文字颜色 |
| } |
| }, |
| |
| // 图例 |
| legend: { |
| orient: 'horizontal', // 布局方式,默认为水平布局,可选为: |
| // 'horizontal' ¦ 'vertical' |
| x: 'center', // 水平安放位置,默认为全图居中,可选为: |
| // 'center' ¦ 'left' ¦ 'right' |
| // ¦ {number}(x坐标,单位px) |
| y: 'top', // 垂直安放位置,默认为全图顶端,可选为: |
| // 'top' ¦ 'bottom' ¦ 'center' |
| // ¦ {number}(y坐标,单位px) |
| backgroundColor: 'rgba(0,0,0,0)', |
| borderColor: '#ccc', // 图例边框颜色 |
| borderWidth: 0, // 图例边框线宽,单位px,默认为0(无边框) |
| padding: 5, // 图例内边距,单位px,默认各方向内边距为5, |
| // 接受数组分别设定上右下左边距,同css |
| itemGap: 10, // 各个item之间的间隔,单位px,默认为10, |
| // 横向布局时为水平间隔,纵向布局时为纵向间隔 |
| itemWidth: 20, // 图例图形宽度 |
| itemHeight: 14, // 图例图形高度 |
| textStyle: { |
| color: '#333' // 图例文字颜色 |
| }, |
| selectedMode: true // 选择模式,默认开启图例开关 |
| // selected: null, // 配置默认选中状态,可配合LEGEND.SELECTED事件做动态数据载入 |
| // data: [], // 图例内容(详见legend.data,数组中每一项代表一个item |
| }, |
| |
| // 值域 |
| dataRange: { |
| orient: 'vertical', // 布局方式,默认为垂直布局,可选为: |
| // 'horizontal' ¦ 'vertical' |
| x: 'left', // 水平安放位置,默认为全图左对齐,可选为: |
| // 'center' ¦ 'left' ¦ 'right' |
| // ¦ {number}(x坐标,单位px) |
| y: 'bottom', // 垂直安放位置,默认为全图底部,可选为: |
| // 'top' ¦ 'bottom' ¦ 'center' |
| // ¦ {number}(y坐标,单位px) |
| backgroundColor: 'rgba(0,0,0,0)', |
| borderColor: '#ccc', // 值域边框颜色 |
| borderWidth: 0, // 值域边框线宽,单位px,默认为0(无边框) |
| padding: 5, // 值域内边距,单位px,默认各方向内边距为5, |
| // 接受数组分别设定上右下左边距,同css |
| itemGap: 10, // 各个item之间的间隔,单位px,默认为10, |
| // 横向布局时为水平间隔,纵向布局时为纵向间隔 |
| itemWidth: 20, // 值域图形宽度,线性渐变水平布局宽度为该值 * 10 |
| itemHeight: 14, // 值域图形高度,线性渐变垂直布局高度为该值 * 10 |
| // min: null, // 最小值 |
| // max: null, // 最大值 |
| precision: 0, // 小数精度,默认为0,无小数点 |
| splitNumber: 5, // 分割段数,默认为5,为0时为线性渐变 |
| calculable: false, // 是否值域漫游,启用后无视splitNumber,线性渐变 |
| realtime: true, |
| color:['#006edd','#e0ffff'],//颜色 |
| //formatter: null, |
| //text:['高','低'], // 文本,默认为数值文本 |
| textStyle: { |
| color: '#333' // 值域文字颜色 |
| } |
| }, |
| |
| toolbox: { |
| show : false, |
| orient: 'horizontal', // 布局方式,默认为水平布局,可选为: |
| // 'horizontal' ¦ 'vertical' |
| x: 'right', // 水平安放位置,默认为全图右对齐,可选为: |
| // 'center' ¦ 'left' ¦ 'right' |
| // ¦ {number}(x坐标,单位px) |
| y: 'top', // 垂直安放位置,默认为全图顶端,可选为: |
| // 'top' ¦ 'bottom' ¦ 'center' |
| // ¦ {number}(y坐标,单位px) |
| color : ['#1e90ff','#22bb22','#4b0082','#d2691e'], |
| disableColor : '#ddd', |
| effectiveColor : 'red', |
| backgroundColor: 'rgba(0,0,0,0)', // 工具箱背景颜色 |
| borderColor: '#ccc', // 工具箱边框颜色 |
| borderWidth: 0, // 工具箱边框线宽,单位px,默认为0(无边框) |
| padding: 5, // 工具箱内边距,单位px,默认各方向内边距为5, |
| // 接受数组分别设定上右下左边距,同css |
| itemGap: 10, // 各个item之间的间隔,单位px,默认为10, |
| // 横向布局时为水平间隔,纵向布局时为纵向间隔 |
| itemSize: 16, // 工具箱图形宽度 |
| showTitle : true, |
| //textStyle : {}, |
| feature : { |
| mark : { |
| show : false, |
| title : { |
| mark : '辅助线开关', |
| markUndo : '删除辅助线', |
| markClear : '清空辅助线' |
| }, |
| lineStyle : { |
| width : 1, |
| color : '#1e90ff', |
| type : 'dashed' |
| } |
| }, |
| dataZoom : { |
| show : false, |
| title : { |
| dataZoom : '区域缩放', |
| dataZoomReset : '区域缩放后退' |
| } |
| }, |
| dataView : { |
| show : false, |
| title : '数据视图', |
| readOnly: false, |
| lang : ['Data View', 'close', 'refresh'] |
| }, |
| magicType: { |
| show : false, |
| title : { |
| line : '折线图切换', |
| bar : '柱形图切换', |
| stack : '堆积', |
| tiled : '平铺' |
| }, |
| type : [] // 'line', 'bar', 'stack', 'tiled' |
| }, |
| restore : { |
| show : false, |
| title : '还原' |
| }, |
| saveAsImage : { |
| show : false, |
| title : '保存为图片', |
| type : 'png', |
| lang : ['点击保存'] |
| } |
| } |
| }, |
| |
| // 提示框 |
| tooltip: { |
| show: true, |
| showContent: true, // tooltip主体内容 |
| trigger: 'item', // 触发类型,默认数据触发,见下图,可选为:'item' ¦ 'axis' |
| // position : null // 位置 {Array} | {Function} |
| // formatter: null // 内容格式器:{string}(Template) ¦ {Function} |
| islandFormatter: '{a} <br/>{b} : {c}', // 数据孤岛内容格式器 |
| showDelay: 20, // 显示延迟,添加显示延迟可以避免频繁切换,单位ms |
| hideDelay: 100, // 隐藏延迟,单位ms |
| transitionDuration : 0.4, // 动画变换时间,单位s |
| backgroundColor: 'rgba(0,0,0,0.7)', // 提示背景颜色,默认为透明度为0.7的黑色 |
| borderColor: '#333', // 提示边框颜色 |
| borderRadius: 4, // 提示边框圆角,单位px,默认为4 |
| borderWidth: 0, // 提示边框线宽,单位px,默认为0(无边框) |
| padding: 5, // 提示内边距,单位px,默认各方向内边距为5, |
| // 接受数组分别设定上右下左边距,同css |
| axisPointer : { // 坐标轴指示器,坐标轴触发有效 |
| type : 'line', // 默认为直线,可选为:'line' | 'shadow' | 'cross' |
| lineStyle : { // 直线指示器样式设置 |
| color: '#48b', |
| width: 2, |
| type: 'solid' |
| }, |
| crossStyle: { |
| color: '#1e90ff', |
| width: 1, |
| type: 'dashed' |
| }, |
| shadowStyle : { // 阴影指示器样式设置 |
| color: 'rgba(150,150,150,0.3)', // 阴影颜色 |
| width: 'auto', // 阴影大小 |
| type: 'default' |
| } |
| }, |
| textStyle: { |
| color: '#fff' |
| } |
| }, |
| |
| // 区域缩放控制器 |
| dataZoom: { |
| show: false, |
| orient: 'horizontal', // 布局方式,默认为水平布局,可选为: |
| // 'horizontal' ¦ 'vertical' |
| // x: {number}, // 水平安放位置,默认为根据grid参数适配,可选为: |
| // {number}(x坐标,单位px) |
| // y: {number}, // 垂直安放位置,默认为根据grid参数适配,可选为: |
| // {number}(y坐标,单位px) |
| // width: {number}, // 指定宽度,横向布局时默认为根据grid参数适配 |
| // height: {number}, // 指定高度,纵向布局时默认为根据grid参数适配 |
| backgroundColor: 'rgba(0,0,0,0)', // 背景颜色 |
| dataBackgroundColor: '#eee', // 数据背景颜色 |
| fillerColor: 'rgba(144,197,237,0.2)', // 填充颜色 |
| handleColor: 'rgba(70,130,180,0.8)', // 手柄颜色 |
| // xAxisIndex: [], // 默认控制所有横向类目 |
| // yAxisIndex: [], // 默认控制所有横向类目 |
| // start: 0, // 默认为0 |
| // end: 100, // 默认为全部 100% |
| realtime: true |
| // zoomLock: false // 是否锁定选择区域大小 |
| }, |
| |
| // 网格 |
| grid: { |
| x: 80, |
| y: 60, |
| x2: 80, |
| y2: 60, |
| // width: {totalWidth} - x - x2, |
| // height: {totalHeight} - y - y2, |
| backgroundColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| borderColor: '#ccc' |
| }, |
| |
| // 类目轴 |
| categoryAxis: { |
| position: 'bottom', // 位置 |
| name: '', // 坐标轴名字,默认为空 |
| nameLocation: 'end', // 坐标轴名字位置,支持'start' | 'end' |
| nameTextStyle: {}, // 坐标轴文字样式,默认取全局样式 |
| boundaryGap: true, // 类目起始和结束两端空白策略 |
| axisLine: { // 坐标轴线 |
| show: true, // 默认显示,属性show控制显示与否 |
| onZero: true, |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#48b', |
| width: 2, |
| type: 'solid' |
| } |
| }, |
| axisTick: { // 坐标轴小标记 |
| show: true, // 属性show控制显示与否,默认不显示 |
| interval: 'auto', |
| inside : false, // 控制小标记是否在grid里 |
| // onGap: null, |
| length :5, // 属性length控制线长 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#333', |
| width: 1 |
| } |
| }, |
| axisLabel: { // 坐标轴文本标签,详见axis.axisLabel |
| show: true, |
| interval: 'auto', |
| rotate: 0, |
| margin: 8, |
| // clickable: false, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333' |
| } |
| }, |
| splitLine: { // 分隔线 |
| show: true, // 默认显示,属性show控制显示与否 |
| // onGap: null, |
| lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式 |
| color: ['#ccc'], |
| width: 1, |
| type: 'solid' |
| } |
| }, |
| splitArea: { // 分隔区域 |
| show: false, // 默认不显示,属性show控制显示与否 |
| // onGap: null, |
| areaStyle: { // 属性areaStyle(详见areaStyle)控制区域样式 |
| color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)'] |
| } |
| } |
| }, |
| |
| // 数值型坐标轴默认参数 |
| valueAxis: { |
| position: 'left', // 位置 |
| name: '', // 坐标轴名字,默认为空 |
| nameLocation: 'end', // 坐标轴名字位置,支持'start' | 'end' |
| nameTextStyle: {}, // 坐标轴文字样式,默认取全局样式 |
| boundaryGap: [0, 0], // 数值起始和结束两端空白策略 |
| // min: null, // 最小值 |
| // max: null, // 最大值 |
| // scale: false, // 脱离0值比例,放大聚焦到最终_min,_max区间 |
| precision: 0, // 小数精度,默认为0,无小数点 |
| power: 100, // 整数精度,默认为100,个位和百位为0 |
| splitNumber: 5, // 分割段数,默认为5 |
| axisLine: { // 坐标轴线 |
| show: true, // 默认显示,属性show控制显示与否 |
| onZero: true, |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#48b', |
| width: 2, |
| type: 'solid' |
| } |
| }, |
| axisTick: { // 坐标轴小标记 |
| show: false, // 属性show控制显示与否,默认不显示 |
| inside : false, // 控制小标记是否在grid里 |
| length :5, // 属性length控制线长 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#333', |
| width: 1 |
| } |
| }, |
| axisLabel: { // 坐标轴文本标签,详见axis.axisLabel |
| show: true, |
| rotate: 0, |
| margin: 8, |
| // clickable: false, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333' |
| } |
| }, |
| splitLine: { // 分隔线 |
| show: true, // 默认显示,属性show控制显示与否 |
| lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式 |
| color: ['#ccc'], |
| width: 1, |
| type: 'solid' |
| } |
| }, |
| splitArea: { // 分隔区域 |
| show: false, // 默认不显示,属性show控制显示与否 |
| areaStyle: { // 属性areaStyle(详见areaStyle)控制区域样式 |
| color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)'] |
| } |
| } |
| }, |
| |
| polar : { |
| center : ['50%', '50%'], // 默认全局居中 |
| radius : '75%', |
| startAngle : 90, |
| splitNumber : 5, |
| name : { |
| show: true, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333' |
| } |
| }, |
| axisLine: { // 坐标轴线 |
| show: true, // 默认显示,属性show控制显示与否 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#ccc', |
| width: 1, |
| type: 'solid' |
| } |
| }, |
| axisLabel: { // 坐标轴文本标签,详见axis.axisLabel |
| show: false, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333' |
| } |
| }, |
| splitArea : { |
| show : true, |
| areaStyle : { |
| color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)'] |
| } |
| }, |
| splitLine : { |
| show : true, |
| lineStyle : { |
| width : 1, |
| color : '#ccc' |
| } |
| }, |
| type: 'polygon' |
| //indicator : [] |
| }, |
| |
| timeline : { |
| show: true, |
| type : 'time', // 模式是时间类型,支持 number |
| notMerge : false, |
| realtime : true, |
| x: 80, |
| // y: {number}, |
| x2: 80, |
| y2: 0, |
| // width: {totalWidth} - x - x2, |
| height: 50, |
| backgroundColor: 'rgba(0,0,0,0)', // 时间轴背景颜色 |
| borderColor : '#ccc', // 时间轴边框颜色 |
| borderWidth : 0, // 时间轴边框线宽,单位px,默认为0(无边框) |
| padding : 5, // 时间轴内边距,单位px,默认各方向内边距为5, |
| controlPosition : 'left', // 'right' | 'none' |
| autoPlay : false, |
| loop : true, |
| playInterval : 2000, // 播放时间间隔,单位ms |
| lineStyle : { |
| width : 1, |
| color : '#666', |
| type : 'dashed' |
| }, |
| label: { // 文本标签 |
| show: true, |
| interval: 'auto', |
| rotate: 0, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333' |
| } |
| }, |
| checkpointStyle : { |
| symbol : 'auto', |
| symbolSize : 'auto', |
| color : 'auto', |
| borderColor : 'auto', |
| borderWidth : 'auto', |
| label: { // 文本标签 |
| show: false, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: 'auto' |
| } |
| } |
| }, |
| controlStyle : { |
| normal : { color : '#333'}, |
| emphasis : { color : '#1e90ff'} |
| }, |
| symbol : 'emptyDiamond', |
| symbolSize : 4, |
| currentIndex : 0 |
| // data : [] |
| }, |
| |
| // 柱形图默认参数 |
| bar: { |
| // stack: null |
| xAxisIndex: 0, |
| yAxisIndex: 0, |
| barMinHeight: 0, // 最小高度改为0 |
| // barWidth: null, // 默认自适应 |
| barGap: '30%', // 柱间距离,默认为柱形宽度的30%,可设固定值 |
| barCategoryGap : '20%', // 类目间柱形距离,默认为类目间距的20%,可设固定值 |
| itemStyle: { |
| normal: { |
| // color: '各异', |
| borderColor: '#fff', // 柱条边线 |
| borderRadius: 0, // 柱条边线圆角,单位px,默认为0 |
| borderWidth: 0, // 柱条边线线宽,单位px,默认为1 |
| label: { |
| show: false |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 |
| // 'inside'|'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| }, |
| emphasis: { |
| // color: '各异', |
| borderColor: '#fff', // 柱条边线 |
| borderRadius: 0, // 柱条边线圆角,单位px,默认为0 |
| borderWidth: 0, // 柱条边线线宽,单位px,默认为1 |
| label: { |
| show: false |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 |
| // 'inside'|'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| } |
| } |
| }, |
| |
| // 折线图默认参数 |
| line: { |
| // stack: null |
| xAxisIndex: 0, |
| yAxisIndex: 0, |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| label: { |
| show: false |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 |
| // 'inside'|'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| lineStyle: { |
| width: 2, |
| type: 'solid', |
| shadowColor : 'rgba(0,0,0,0)', //默认透明 |
| shadowBlur: 0, |
| shadowOffsetX: 0, |
| shadowOffsetY: 0 |
| } |
| }, |
| emphasis: { |
| // color: 各异, |
| label: { |
| show: false |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 |
| // 'inside'|'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| } |
| }, |
| //smooth : false, |
| //symbol: null, // 拐点图形类型 |
| symbolSize: 2, // 拐点图形大小 |
| //symbolRotate : null, // 拐点图形旋转控制 |
| showAllSymbol: false // 标志图形默认只有主轴显示(随主轴标签间隔隐藏策略) |
| }, |
| |
| // K线图默认参数 |
| k: { |
| xAxisIndex: 0, |
| yAxisIndex: 0, |
| // barWidth : null // 默认自适应 |
| // barMaxWidth : null // 默认自适应 |
| itemStyle: { |
| normal: { |
| color: '#fff', // 阳线填充颜色 |
| color0: '#00aa11', // 阴线填充颜色 |
| lineStyle: { |
| width: 1, |
| color: '#ff3200', // 阳线边框颜色 |
| color0: '#00aa11' // 阴线边框颜色 |
| } |
| }, |
| emphasis: { |
| // color: 各异, |
| // color0: 各异 |
| } |
| } |
| }, |
| |
| // 散点图默认参数 |
| scatter: { |
| xAxisIndex: 0, |
| yAxisIndex: 0, |
| //symbol: null, // 图形类型 |
| symbolSize: 4, // 图形大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2 |
| //symbolRotate : null, // 图形旋转控制 |
| large: false, // 大规模散点图 |
| largeThreshold: 2000,// 大规模阀值,large为true且数据量>largeThreshold才启用大规模模式 |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| label: { |
| show: false, |
| // 标签文本格式器,同Tooltip.formatter,不支持回调 |
| formatter : function (a, b, c) { |
| if (typeof c[2] != 'undefined') { |
| return c[2]; |
| } |
| else { |
| return c[0] + ' , ' + c[1]; |
| } |
| } |
| // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 |
| // 'inside'|'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| }, |
| emphasis: { |
| // color: '各异' |
| label: { |
| show: false, |
| // 标签文本格式器,同Tooltip.formatter,不支持回调 |
| formatter : function (a, b, c) { |
| if (typeof c[2] != 'undefined') { |
| return c[2]; |
| } |
| else { |
| return c[0] + ' , ' + c[1]; |
| } |
| } |
| // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 |
| // 'inside'|'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| } |
| } |
| }, |
| |
| // 雷达图默认参数 |
| radar : { |
| polarIndex: 0, |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| label: { |
| show: false |
| }, |
| lineStyle: { |
| width: 2, |
| type: 'solid' |
| } |
| }, |
| emphasis: { |
| // color: 各异, |
| label: { |
| show: false |
| } |
| } |
| }, |
| //symbol: null, // 拐点图形类型 |
| symbolSize: 2 // 可计算特性参数,空数据拖拽提示图形大小 |
| //symbolRotate : null, // 图形旋转控制 |
| }, |
| |
| // 饼图默认参数 |
| pie: { |
| center : ['50%', '50%'], // 默认全局居中 |
| radius : [0, '75%'], |
| clockWise : true, // 默认顺时针 |
| startAngle: 90, |
| minAngle: 0, // 最小角度改为0 |
| selectedOffset: 10, // 选中是扇区偏移量 |
| // selectedMode: false, // 选择模式,默认关闭,可选single,multiple |
| // roseType : null, // 南丁格尔玫瑰图模式,'radius'(半径) | 'area'(面积) |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| borderColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| label: { |
| show: true, |
| position: 'outer' |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| labelLine: { |
| show: true, |
| length: 20, |
| lineStyle: { |
| // color: 各异, |
| width: 1, |
| type: 'solid' |
| } |
| } |
| }, |
| emphasis: { |
| // color: 各异, |
| borderColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| label: { |
| show: false |
| // position: 'outer' |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| labelLine: { |
| show: false, |
| length: 20, |
| lineStyle: { |
| // color: 各异, |
| width: 1, |
| type: 'solid' |
| } |
| } |
| } |
| } |
| }, |
| |
| map: { |
| mapType: 'china', // 各省的mapType暂时都用中文 |
| //mapLocation: { |
| // x : 'center' | 'left' | 'right' | 'x%' | {number}, |
| // y : 'center' | 'top' | 'bottom' | 'x%' | {number} |
| // width // 自适应 |
| // height // 自适应 |
| //}, |
| // mapValueCalculation: 'sum', // 数值合并方式,默认加和,可选为: |
| // 'sum' | 'average' | 'max' | 'min' |
| mapValuePrecision : 0, // 地图数值计算结果小数精度 |
| showLegendSymbol : true, // 显示图例颜色标识(系列标识的小圆点),存在legend时生效 |
| // selectedMode: false, // 选择模式,默认关闭,可选single,multiple |
| hoverable: true, |
| // roam : false, // 是否开启缩放及漫游模式 |
| // scaleLimit : null, |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| borderColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| areaStyle: { |
| color: '#ccc' |
| }, |
| label: { |
| show: false, |
| textStyle: { |
| color: 'rgb(139,69,19)' |
| } |
| } |
| }, |
| emphasis: { // 也是选中样式 |
| // color: 各异, |
| borderColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| areaStyle: { |
| color: 'rgba(255,215,0,0.8)' |
| }, |
| label: { |
| show: false, |
| textStyle: { |
| color: 'rgb(100,0,0)' |
| } |
| } |
| } |
| } |
| }, |
| |
| force : { |
| // 布局中心 |
| center: ['50%', '50%'], |
| |
| // 布局大小 |
| size: '100%', |
| |
| // 布局冷却因子,值越小结束时间越短,值越大时间越长但是结果也越收敛 |
| coolDown: 0.99, |
| |
| // 数据映射到圆的半径的最小值和最大值 |
| minRadius : 10, |
| maxRadius : 20, |
| |
| // 在 500+ 顶点的图上建议设置 large 为 true, 会使用 Barnes-Hut simulation |
| // 同时开启 useWorker 并且把 steps 值调大 |
| // 关于Barnes-Hut simulation: http://en.wikipedia.org/wiki/Barnes–Hut_simulation |
| large: false, |
| |
| // 为 false 的时候强制关闭 web worker |
| useWorker: true, |
| // 每一帧 force 迭代的次数,仅在启用webworker的情况下有用 |
| steps: 1, |
| |
| // 布局缩放因子,并不完全精确, 效果跟布局大小类似 |
| scaling : 1.0, |
| |
| // 向心力因子,越大向心力越大( 所有顶点会往 center 的位置收拢 ) |
| gravity : 1, |
| |
| symbol: 'circle', |
| // symbolSize 为 0 的话使用映射到minRadius-maxRadius后的值 |
| symbolSize: 0, |
| |
| linkSymbol: null, |
| linkSymbolSize: [10, 15], |
| draggable: true, |
| |
| // 分类里如果有样式会覆盖节点默认样式 |
| categories : [{ |
| // itemStyle |
| // symbol |
| // symbolSize |
| // name |
| }], |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| label: { |
| show: false |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| nodeStyle : { |
| brushType : 'both', |
| color : '#f08c2e', |
| strokeColor : '#5182ab', |
| lineWidth: 1 |
| }, |
| linkStyle : { |
| strokeColor : '#5182ab' |
| } |
| }, |
| emphasis: { |
| // color: 各异, |
| label: { |
| show: false |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| nodeStyle : {}, |
| linkStyle : { |
| opacity: 0 |
| } |
| } |
| } |
| // nodes: [{ |
| // name: 'xxx', |
| // value: 1, |
| // itemStyle: {}, |
| // initial: [0, 0], |
| // fixX: false, |
| // fixY: false, |
| // ignore: false, |
| // symbol: 'circle', |
| // symbolSize: 0 |
| // }] |
| // links: [{ |
| // source: 1, |
| // target: 2, |
| // weight: 1, |
| // itemStyle: {} |
| // }, { |
| // source: 'xxx', |
| // target: 'ooo' |
| // }] |
| }, |
| |
| chord : { |
| radius : ['65%', '75%'], |
| center : ['50%', '50%'], |
| padding : 2, |
| sort : 'none', // can be 'none', 'ascending', 'descending' |
| sortSub : 'none', // can be 'none', 'ascending', 'descending' |
| startAngle : 90, |
| clockWise : true, |
| showScale : false, |
| showScaleText : false, |
| itemStyle : { |
| normal : { |
| label : { |
| show : true, |
| rotate: false, |
| distance: 10 |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| lineStyle : { |
| width : 0, |
| color : '#000' |
| }, |
| chordStyle : { |
| lineStyle : { |
| width : 1, |
| color : '#999' |
| } |
| } |
| }, |
| emphasis : { |
| lineStyle : { |
| width : 0, |
| color : '#000' |
| }, |
| chordStyle : { |
| lineStyle : { |
| width : 1, |
| color : '#666' |
| } |
| } |
| } |
| }, |
| // Source data matrix |
| /** |
| * target |
| * -1--2--3--4--5- |
| * 1| x x x x x |
| * 2| x x x x x |
| * 3| x x x x x source |
| * 4| x x x x x |
| * 5| x x x x x |
| * |
| * Relation ship from source to target |
| * https://github.com/mbostock/d3/wiki/Chord-Layout#wiki-chord |
| * |
| * Row based |
| */ |
| matrix : [] |
| }, |
| |
| gauge : { |
| center : ['50%', '50%'], // 默认全局居中 |
| radius : '75%', |
| startAngle: 225, |
| endAngle : -45, |
| min: 0, // 最小值 |
| max: 100, // 最大值 |
| precision: 0, // 小数精度,默认为0,无小数点 |
| splitNumber: 10, // 分割段数,默认为10 |
| axisLine: { // 坐标轴线 |
| show: true, // 默认显示,属性show控制显示与否 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: [[0.2, '#228b22'],[0.8, '#48b'],[1, '#ff4500']], |
| width: 30 |
| } |
| }, |
| axisTick: { // 坐标轴小标记 |
| show: true, // 属性show控制显示与否,默认不显示 |
| splitNumber: 5, // 每份split细分多少段 |
| length :8, // 属性length控制线长 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#eee', |
| width: 1, |
| type: 'solid' |
| } |
| }, |
| axisLabel: { // 坐标轴文本标签,详见axis.axisLabel |
| show: true, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: 'auto' |
| } |
| }, |
| splitLine: { // 分隔线 |
| show: true, // 默认显示,属性show控制显示与否 |
| length :30, // 属性length控制线长 |
| lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式 |
| color: '#eee', |
| width: 2, |
| type: 'solid' |
| } |
| }, |
| pointer : { |
| show : true, |
| length : '80%', |
| width : 8, |
| color : 'auto' |
| }, |
| title : { |
| show : true, |
| offsetCenter: [0, '-40%'], // x, y,单位px |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333', |
| fontSize : 15 |
| } |
| }, |
| detail : { |
| show : true, |
| backgroundColor: 'rgba(0,0,0,0)', |
| borderWidth: 0, |
| borderColor: '#ccc', |
| width: 100, |
| height: 40, |
| offsetCenter: [0, '40%'], // x, y,单位px |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: 'auto', |
| fontSize : 30 |
| } |
| } |
| }, |
| |
| funnel : { |
| x: 80, |
| y: 60, |
| x2: 80, |
| y2: 60, |
| // width: {totalWidth} - x - x2, |
| // height: {totalHeight} - y - y2, |
| min: 0, |
| max: 100, |
| minSize: '0%', |
| maxSize: '100%', |
| sort : 'descending', // 'ascending', 'descending' |
| gap : 0, |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| borderColor: '#fff', |
| borderWidth: 1, |
| label: { |
| show: true, |
| position: 'outer' |
| // formatter: 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| labelLine: { |
| show: true, |
| length: 10, |
| lineStyle: { |
| // color: 各异, |
| width: 1, |
| type: 'solid' |
| } |
| } |
| }, |
| emphasis: { |
| // color: 各异, |
| borderColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| label: { |
| show: true |
| }, |
| labelLine: { |
| show: true |
| } |
| } |
| } |
| }, |
| |
| island: { |
| r: 15, |
| calculateStep: 0.1 // 滚轮可计算步长 0.1 = 10% |
| }, |
| |
| markPoint : { |
| symbol: 'pin', // 标注类型 |
| symbolSize: 10, // 标注大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2 |
| //symbolRotate : null, // 标注旋转控制 |
| large : false, |
| effect : { |
| show: false, |
| loop: true, |
| period: 15, // 运动周期,无单位,值越大越慢 |
| scaleSize : 2 // 放大倍数,以markPoint点size为基准 |
| // color : 'gold', |
| // shadowColor : 'rgba(255,215,0,0.8)', |
| // shadowBlur : 0 // 炫光模糊 |
| }, |
| itemStyle: { |
| normal: { |
| // color: 各异, |
| // borderColor: 各异, // 标注边线颜色,优先于color |
| borderWidth: 2, // 标注边线线宽,单位px,默认为1 |
| label: { |
| show: true, |
| // 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // formatter : null, |
| position: 'inside' // 可选为'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| }, |
| emphasis: { |
| // color: 各异 |
| label: { |
| show: true |
| // 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // formatter : null, |
| // position: 'inside' // 'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| } |
| } |
| } |
| }, |
| |
| markLine : { |
| // 标线起始和结束的symbol介绍类型,如果都一样,可以直接传string |
| symbol: ['circle', 'arrow'], |
| // 标线起始和结束的symbol大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2 |
| symbolSize: [2, 4], |
| // 标线起始和结束的symbol旋转控制 |
| //symbolRotate : null, |
| //smooth : false, |
| large : false, |
| effect : { |
| show: false, |
| loop: true, |
| period: 15, // 运动周期,无单位,值越大越慢 |
| scaleSize : 2 // 放大倍数,以markLine线lineWidth为基准 |
| // color : 'gold', |
| // shadowColor : 'rgba(255,215,0,0.8)', |
| // shadowBlur : lineWidth * 2 // 炫光模糊,默认等于scaleSize计算所得 |
| }, |
| itemStyle: { |
| normal: { |
| // color: 各异, // 标线主色,线色,symbol主色 |
| // borderColor: 随color, // 标线symbol边框颜色,优先于color |
| borderWidth: 1.5, // 标线symbol边框线宽,单位px,默认为2 |
| label: { |
| show: true, |
| // 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // formatter : null, |
| // 可选为 'start'|'end'|'left'|'right'|'top'|'bottom' |
| position: 'end' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| lineStyle: { |
| // color: 随borderColor, // 主色,线色,优先级高于borderColor和color |
| // width: 随borderWidth, // 优先于borderWidth |
| type: 'dashed' |
| // shadowColor : 'rgba(0,0,0,0)', //默认透明 |
| // shadowBlur: 0, |
| // shadowOffsetX: 0, |
| // shadowOffsetY: 0 |
| } |
| }, |
| emphasis: { |
| // color: 各异 |
| label: { |
| show: false |
| // 标签文本格式器,同Tooltip.formatter,不支持回调 |
| // formatter : null, |
| // position: 'inside' // 'left'|'right'|'top'|'bottom' |
| // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE |
| }, |
| lineStyle : {} |
| } |
| } |
| }, |
| |
| // 主题,主题 |
| textStyle: { |
| decoration: 'none', |
| fontFamily: 'Arial, Verdana, sans-serif', |
| fontFamily2: '微软雅黑', // IE8- 字体模糊并且,不支持不同字体混排,额外指定一份 |
| fontSize: 12, |
| fontStyle: 'normal', |
| fontWeight: 'normal' |
| }, |
| |
| EVENT: { |
| // -------全局通用 |
| REFRESH: 'refresh', |
| RESTORE: 'restore', |
| RESIZE: 'resize', |
| CLICK: 'click', |
| HOVER: 'hover', |
| //MOUSEWHEEL: 'mousewheel', |
| // -------业务交互逻辑 |
| DATA_CHANGED: 'dataChanged', |
| DATA_ZOOM: 'dataZoom', |
| DATA_RANGE: 'dataRange', |
| LEGEND_SELECTED: 'legendSelected', |
| MAP_SELECTED: 'mapSelected', |
| PIE_SELECTED: 'pieSelected', |
| MAGIC_TYPE_CHANGED: 'magicTypeChanged', |
| DATA_VIEW_CHANGED: 'dataViewChanged', |
| TIMELINE_CHANGED: 'timelineChanged', |
| MAP_ROAM : 'mapRoam', |
| // -------内部通信 |
| TOOLTIP_HOVER: 'tooltipHover', |
| TOOLTIP_IN_GRID: 'tooltipInGrid', |
| TOOLTIP_OUT_GRID: 'tooltipOutGrid' |
| }, |
| DRAG_ENABLE_TIME : 120, // 降低图表内元素拖拽敏感度,单位ms,不建议外部干预 |
| EFFECT_ZLEVEL : 7, |
| // 主题,默认标志图形类型列表 |
| symbolList : [ |
| 'circle', 'rectangle', 'triangle', 'diamond', |
| 'emptyCircle', 'emptyRectangle', 'emptyTriangle', 'emptyDiamond' |
| ], |
| loadingText : 'Loading...', |
| // 可计算特性配置,孤岛,提示颜色 |
| calculable: false, // 默认关闭可计算特性 |
| calculableColor: 'rgba(255,165,0,0.6)', // 拖拽提示边框颜色 |
| calculableHolderColor: '#ccc', // 可计算占位提示颜色 |
| nameConnector: ' & ', |
| valueConnector: ' : ', |
| animation: true, |
| addDataAnimation: true, // 动态数据接口是否开启动画效果 |
| animationThreshold: 2000, // 动画元素阀值,产生的图形原素超过2000不出动画 |
| animationDuration: 2000, |
| animationEasing: 'ExponentialOut' //BounceOut |
| }; |
| |
| return config; |
| }); |
| // Copyright 2006 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| |
| // Known Issues: |
| // |
| // * Patterns only support repeat. |
| // * Radial gradient are not implemented. The VML version of these look very |
| // different from the canvas one. |
| // * Clipping paths are not implemented. |
| // * Coordsize. The width and height attribute have higher priority than the |
| // width and height style values which isn't correct. |
| // * Painting mode isn't implemented. |
| // * Canvas width/height should is using content-box by default. IE in |
| // Quirks mode will draw the canvas using border-box. Either change your |
| // doctype to HTML5 |
| // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) |
| // or use Box Sizing Behavior from WebFX |
| // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) |
| // * Non uniform scaling does not correctly scale strokes. |
| // * Optimize. There is always room for speed improvements. |
| |
| // AMD by kener.linfeng@gmail.com |
| define('zrender/lib/excanvas',['require'],function(require) { |
| |
| // Only add this code if we do not already have a canvas implementation |
| if (!document.createElement('canvas').getContext) { |
| |
| (function() { |
| |
| // alias some functions to make (compiled) code shorter |
| var m = Math; |
| var mr = m.round; |
| var ms = m.sin; |
| var mc = m.cos; |
| var abs = m.abs; |
| var sqrt = m.sqrt; |
| |
| // this is used for sub pixel precision |
| var Z = 10; |
| var Z2 = Z / 2; |
| |
| var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; |
| |
| /** |
| * This funtion is assigned to the <canvas> elements as element.getContext(). |
| * @this {HTMLElement} |
| * @return {CanvasRenderingContext2D_} |
| */ |
| function getContext() { |
| return this.context_ || |
| (this.context_ = new CanvasRenderingContext2D_(this)); |
| } |
| |
| var slice = Array.prototype.slice; |
| |
| /** |
| * Binds a function to an object. The returned function will always use the |
| * passed in {@code obj} as {@code this}. |
| * |
| * Example: |
| * |
| * g = bind(f, obj, a, b) |
| * g(c, d) // will do f.call(obj, a, b, c, d) |
| * |
| * @param {Function} f The function to bind the object to |
| * @param {Object} obj The object that should act as this when the function |
| * is called |
| * @param {*} var_args Rest arguments that will be used as the initial |
| * arguments when the function is called |
| * @return {Function} A new function that has bound this |
| */ |
| function bind(f, obj, var_args) { |
| var a = slice.call(arguments, 2); |
| return function() { |
| return f.apply(obj, a.concat(slice.call(arguments))); |
| }; |
| } |
| |
| function encodeHtmlAttribute(s) { |
| return String(s).replace(/&/g, '&').replace(/"/g, '"'); |
| } |
| |
| function addNamespace(doc, prefix, urn) { |
| if (!doc.namespaces[prefix]) { |
| doc.namespaces.add(prefix, urn, '#default#VML'); |
| } |
| } |
| |
| function addNamespacesAndStylesheet(doc) { |
| addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); |
| addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); |
| |
| // Setup default CSS. Only add one style sheet per document |
| if (!doc.styleSheets['ex_canvas_']) { |
| var ss = doc.createStyleSheet(); |
| ss.owningElement.id = 'ex_canvas_'; |
| ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + |
| // default size is 300x150 in Gecko and Opera |
| 'text-align:left;width:300px;height:150px}'; |
| } |
| } |
| |
| // Add namespaces and stylesheet at startup. |
| addNamespacesAndStylesheet(document); |
| |
| var G_vmlCanvasManager_ = { |
| init: function(opt_doc) { |
| var doc = opt_doc || document; |
| // Create a dummy element so that IE will allow canvas elements to be |
| // recognized. |
| doc.createElement('canvas'); |
| doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); |
| }, |
| |
| init_: function(doc) { |
| // find all canvas elements |
| var els = doc.getElementsByTagName('canvas'); |
| for (var i = 0; i < els.length; i++) { |
| this.initElement(els[i]); |
| } |
| }, |
| |
| /** |
| * Public initializes a canvas element so that it can be used as canvas |
| * element from now on. This is called automatically before the page is |
| * loaded but if you are creating elements using createElement you need to |
| * make sure this is called on the element. |
| * @param {HTMLElement} el The canvas element to initialize. |
| * @return {HTMLElement} the element that was created. |
| */ |
| initElement: function(el) { |
| if (!el.getContext) { |
| el.getContext = getContext; |
| |
| // Add namespaces and stylesheet to document of the element. |
| addNamespacesAndStylesheet(el.ownerDocument); |
| |
| // Remove fallback content. There is no way to hide text nodes so we |
| // just remove all childNodes. We could hide all elements and remove |
| // text nodes but who really cares about the fallback content. |
| el.innerHTML = ''; |
| |
| // do not use inline function because that will leak memory |
| el.attachEvent('onpropertychange', onPropertyChange); |
| el.attachEvent('onresize', onResize); |
| |
| var attrs = el.attributes; |
| if (attrs.width && attrs.width.specified) { |
| // TODO: use runtimeStyle and coordsize |
| // el.getContext().setWidth_(attrs.width.nodeValue); |
| el.style.width = attrs.width.nodeValue + 'px'; |
| } else { |
| el.width = el.clientWidth; |
| } |
| if (attrs.height && attrs.height.specified) { |
| // TODO: use runtimeStyle and coordsize |
| // el.getContext().setHeight_(attrs.height.nodeValue); |
| el.style.height = attrs.height.nodeValue + 'px'; |
| } else { |
| el.height = el.clientHeight; |
| } |
| //el.getContext().setCoordsize_() |
| } |
| return el; |
| } |
| }; |
| |
| function onPropertyChange(e) { |
| var el = e.srcElement; |
| |
| switch (e.propertyName) { |
| case 'width': |
| el.getContext().clearRect(); |
| el.style.width = el.attributes.width.nodeValue + 'px'; |
| // In IE8 this does not trigger onresize. |
| el.firstChild.style.width = el.clientWidth + 'px'; |
| break; |
| case 'height': |
| el.getContext().clearRect(); |
| el.style.height = el.attributes.height.nodeValue + 'px'; |
| el.firstChild.style.height = el.clientHeight + 'px'; |
| break; |
| } |
| } |
| |
| function onResize(e) { |
| var el = e.srcElement; |
| if (el.firstChild) { |
| el.firstChild.style.width = el.clientWidth + 'px'; |
| el.firstChild.style.height = el.clientHeight + 'px'; |
| } |
| } |
| |
| G_vmlCanvasManager_.init(); |
| |
| // precompute "00" to "FF" |
| var decToHex = []; |
| for (var i = 0; i < 16; i++) { |
| for (var j = 0; j < 16; j++) { |
| decToHex[i * 16 + j] = i.toString(16) + j.toString(16); |
| } |
| } |
| |
| function createMatrixIdentity() { |
| return [ |
| [1, 0, 0], |
| [0, 1, 0], |
| [0, 0, 1] |
| ]; |
| } |
| |
| function matrixMultiply(m1, m2) { |
| var result = createMatrixIdentity(); |
| |
| for (var x = 0; x < 3; x++) { |
| for (var y = 0; y < 3; y++) { |
| var sum = 0; |
| |
| for (var z = 0; z < 3; z++) { |
| sum += m1[x][z] * m2[z][y]; |
| } |
| |
| result[x][y] = sum; |
| } |
| } |
| return result; |
| } |
| |
| function copyState(o1, o2) { |
| o2.fillStyle = o1.fillStyle; |
| o2.lineCap = o1.lineCap; |
| o2.lineJoin = o1.lineJoin; |
| o2.lineWidth = o1.lineWidth; |
| o2.miterLimit = o1.miterLimit; |
| o2.shadowBlur = o1.shadowBlur; |
| o2.shadowColor = o1.shadowColor; |
| o2.shadowOffsetX = o1.shadowOffsetX; |
| o2.shadowOffsetY = o1.shadowOffsetY; |
| o2.strokeStyle = o1.strokeStyle; |
| o2.globalAlpha = o1.globalAlpha; |
| o2.font = o1.font; |
| o2.textAlign = o1.textAlign; |
| o2.textBaseline = o1.textBaseline; |
| o2.arcScaleX_ = o1.arcScaleX_; |
| o2.arcScaleY_ = o1.arcScaleY_; |
| o2.lineScale_ = o1.lineScale_; |
| } |
| |
| var colorData = { |
| aliceblue: '#F0F8FF', |
| antiquewhite: '#FAEBD7', |
| aquamarine: '#7FFFD4', |
| azure: '#F0FFFF', |
| beige: '#F5F5DC', |
| bisque: '#FFE4C4', |
| black: '#000000', |
| blanchedalmond: '#FFEBCD', |
| blueviolet: '#8A2BE2', |
| brown: '#A52A2A', |
| burlywood: '#DEB887', |
| cadetblue: '#5F9EA0', |
| chartreuse: '#7FFF00', |
| chocolate: '#D2691E', |
| coral: '#FF7F50', |
| cornflowerblue: '#6495ED', |
| cornsilk: '#FFF8DC', |
| crimson: '#DC143C', |
| cyan: '#00FFFF', |
| darkblue: '#00008B', |
| darkcyan: '#008B8B', |
| darkgoldenrod: '#B8860B', |
| darkgray: '#A9A9A9', |
| darkgreen: '#006400', |
| darkgrey: '#A9A9A9', |
| 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', |
| gainsboro: '#DCDCDC', |
| ghostwhite: '#F8F8FF', |
| gold: '#FFD700', |
| goldenrod: '#DAA520', |
| grey: '#808080', |
| 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', |
| lightgreen: '#90EE90', |
| lightgrey: '#D3D3D3', |
| lightpink: '#FFB6C1', |
| lightsalmon: '#FFA07A', |
| lightseagreen: '#20B2AA', |
| lightskyblue: '#87CEFA', |
| lightslategray: '#778899', |
| lightslategrey: '#778899', |
| lightsteelblue: '#B0C4DE', |
| lightyellow: '#FFFFE0', |
| limegreen: '#32CD32', |
| linen: '#FAF0E6', |
| magenta: '#FF00FF', |
| mediumaquamarine: '#66CDAA', |
| mediumblue: '#0000CD', |
| mediumorchid: '#BA55D3', |
| mediumpurple: '#9370DB', |
| mediumseagreen: '#3CB371', |
| mediumslateblue: '#7B68EE', |
| mediumspringgreen: '#00FA9A', |
| mediumturquoise: '#48D1CC', |
| mediumvioletred: '#C71585', |
| midnightblue: '#191970', |
| mintcream: '#F5FFFA', |
| mistyrose: '#FFE4E1', |
| moccasin: '#FFE4B5', |
| navajowhite: '#FFDEAD', |
| oldlace: '#FDF5E6', |
| olivedrab: '#6B8E23', |
| orange: '#FFA500', |
| orangered: '#FF4500', |
| orchid: '#DA70D6', |
| palegoldenrod: '#EEE8AA', |
| palegreen: '#98FB98', |
| paleturquoise: '#AFEEEE', |
| palevioletred: '#DB7093', |
| papayawhip: '#FFEFD5', |
| peachpuff: '#FFDAB9', |
| peru: '#CD853F', |
| pink: '#FFC0CB', |
| plum: '#DDA0DD', |
| powderblue: '#B0E0E6', |
| rosybrown: '#BC8F8F', |
| royalblue: '#4169E1', |
| saddlebrown: '#8B4513', |
| salmon: '#FA8072', |
| sandybrown: '#F4A460', |
| seagreen: '#2E8B57', |
| seashell: '#FFF5EE', |
| sienna: '#A0522D', |
| skyblue: '#87CEEB', |
| slateblue: '#6A5ACD', |
| slategray: '#708090', |
| slategrey: '#708090', |
| snow: '#FFFAFA', |
| springgreen: '#00FF7F', |
| steelblue: '#4682B4', |
| tan: '#D2B48C', |
| thistle: '#D8BFD8', |
| tomato: '#FF6347', |
| turquoise: '#40E0D0', |
| violet: '#EE82EE', |
| wheat: '#F5DEB3', |
| whitesmoke: '#F5F5F5', |
| yellowgreen: '#9ACD32' |
| }; |
| |
| |
| function getRgbHslContent(styleString) { |
| var start = styleString.indexOf('(', 3); |
| var end = styleString.indexOf(')', start + 1); |
| var parts = styleString.substring(start + 1, end).split(','); |
| // add alpha if needed |
| if (parts.length != 4 || styleString.charAt(3) != 'a') { |
| parts[3] = 1; |
| } |
| return parts; |
| } |
| |
| function percent(s) { |
| return parseFloat(s) / 100; |
| } |
| |
| function clamp(v, min, max) { |
| return Math.min(max, Math.max(min, v)); |
| } |
| |
| function hslToRgb(parts){ |
| var r, g, b, h, s, l; |
| h = parseFloat(parts[0]) / 360 % 360; |
| if (h < 0) |
| h++; |
| s = clamp(percent(parts[1]), 0, 1); |
| l = clamp(percent(parts[2]), 0, 1); |
| if (s == 0) { |
| r = g = b = l; // achromatic |
| } else { |
| var q = l < 0.5 ? l * (1 + s) : l + s - l * s; |
| var p = 2 * l - q; |
| r = hueToRgb(p, q, h + 1 / 3); |
| g = hueToRgb(p, q, h); |
| b = hueToRgb(p, q, h - 1 / 3); |
| } |
| |
| return '#' + decToHex[Math.floor(r * 255)] + |
| decToHex[Math.floor(g * 255)] + |
| decToHex[Math.floor(b * 255)]; |
| } |
| |
| function hueToRgb(m1, m2, h) { |
| if (h < 0) |
| h++; |
| if (h > 1) |
| h--; |
| |
| if (6 * h < 1) |
| return m1 + (m2 - m1) * 6 * h; |
| else if (2 * h < 1) |
| return m2; |
| else if (3 * h < 2) |
| return m1 + (m2 - m1) * (2 / 3 - h) * 6; |
| else |
| return m1; |
| } |
| |
| var processStyleCache = {}; |
| |
| function processStyle(styleString) { |
| if (styleString in processStyleCache) { |
| return processStyleCache[styleString]; |
| } |
| |
| var str, alpha = 1; |
| |
| styleString = String(styleString); |
| if (styleString.charAt(0) == '#') { |
| str = styleString; |
| } else if (/^rgb/.test(styleString)) { |
| var parts = getRgbHslContent(styleString); |
| var str = '#', n; |
| for (var i = 0; i < 3; i++) { |
| if (parts[i].indexOf('%') != -1) { |
| n = Math.floor(percent(parts[i]) * 255); |
| } else { |
| n = +parts[i]; |
| } |
| str += decToHex[clamp(n, 0, 255)]; |
| } |
| alpha = +parts[3]; |
| } else if (/^hsl/.test(styleString)) { |
| var parts = getRgbHslContent(styleString); |
| str = hslToRgb(parts); |
| alpha = parts[3]; |
| } else { |
| str = colorData[styleString] || styleString; |
| } |
| return processStyleCache[styleString] = {color: str, alpha: alpha}; |
| } |
| |
| var DEFAULT_STYLE = { |
| style: 'normal', |
| variant: 'normal', |
| weight: 'normal', |
| size: 12, //10 |
| family: '微软雅黑' //'sans-serif' |
| }; |
| |
| // Internal text style cache |
| var fontStyleCache = {}; |
| |
| function processFontStyle(styleString) { |
| if (fontStyleCache[styleString]) { |
| return fontStyleCache[styleString]; |
| } |
| |
| var el = document.createElement('div'); |
| var style = el.style; |
| var fontFamily; |
| try { |
| style.font = styleString; |
| fontFamily = style.fontFamily.split(',')[0]; |
| } catch (ex) { |
| // Ignore failures to set to invalid font. |
| } |
| |
| return fontStyleCache[styleString] = { |
| style: style.fontStyle || DEFAULT_STYLE.style, |
| variant: style.fontVariant || DEFAULT_STYLE.variant, |
| weight: style.fontWeight || DEFAULT_STYLE.weight, |
| size: style.fontSize || DEFAULT_STYLE.size, |
| family: fontFamily || DEFAULT_STYLE.family |
| }; |
| } |
| |
| function getComputedStyle(style, element) { |
| var computedStyle = {}; |
| |
| for (var p in style) { |
| computedStyle[p] = style[p]; |
| } |
| |
| // Compute the size |
| var canvasFontSize = parseFloat(element.currentStyle.fontSize), |
| fontSize = parseFloat(style.size); |
| |
| if (typeof style.size == 'number') { |
| computedStyle.size = style.size; |
| } else if (style.size.indexOf('px') != -1) { |
| computedStyle.size = fontSize; |
| } else if (style.size.indexOf('em') != -1) { |
| computedStyle.size = canvasFontSize * fontSize; |
| } else if(style.size.indexOf('%') != -1) { |
| computedStyle.size = (canvasFontSize / 100) * fontSize; |
| } else if (style.size.indexOf('pt') != -1) { |
| computedStyle.size = fontSize / .75; |
| } else { |
| computedStyle.size = canvasFontSize; |
| } |
| |
| // Different scaling between normal text and VML text. This was found using |
| // trial and error to get the same size as non VML text. |
| //computedStyle.size *= 0.981; |
| |
| return computedStyle; |
| } |
| |
| function buildStyle(style) { |
| return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + |
| style.size + "px '" + style.family + "'"; |
| } |
| |
| var lineCapMap = { |
| 'butt': 'flat', |
| 'round': 'round' |
| }; |
| |
| function processLineCap(lineCap) { |
| return lineCapMap[lineCap] || 'square'; |
| } |
| |
| /** |
| * This class implements CanvasRenderingContext2D interface as described by |
| * the WHATWG. |
| * @param {HTMLElement} canvasElement The element that the 2D context should |
| * be associated with |
| */ |
| function CanvasRenderingContext2D_(canvasElement) { |
| this.m_ = createMatrixIdentity(); |
| |
| this.mStack_ = []; |
| this.aStack_ = []; |
| this.currentPath_ = []; |
| |
| // Canvas context properties |
| this.strokeStyle = '#000'; |
| this.fillStyle = '#000'; |
| |
| this.lineWidth = 1; |
| this.lineJoin = 'miter'; |
| this.lineCap = 'butt'; |
| this.miterLimit = Z * 1; |
| this.globalAlpha = 1; |
| // this.font = '10px sans-serif'; |
| this.font = '12px 微软雅黑'; // 决定还是改这吧,影响代价最小 |
| this.textAlign = 'left'; |
| this.textBaseline = 'alphabetic'; |
| this.canvas = canvasElement; |
| |
| var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + |
| canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; |
| var el = canvasElement.ownerDocument.createElement('div'); |
| el.style.cssText = cssText; |
| canvasElement.appendChild(el); |
| |
| var overlayEl = el.cloneNode(false); |
| // Use a non transparent background. |
| overlayEl.style.backgroundColor = '#fff'; //red, I don't know why, it work! |
| overlayEl.style.filter = 'alpha(opacity=0)'; |
| canvasElement.appendChild(overlayEl); |
| |
| this.element_ = el; |
| this.arcScaleX_ = 1; |
| this.arcScaleY_ = 1; |
| this.lineScale_ = 1; |
| } |
| |
| var contextPrototype = CanvasRenderingContext2D_.prototype; |
| contextPrototype.clearRect = function() { |
| if (this.textMeasureEl_) { |
| this.textMeasureEl_.removeNode(true); |
| this.textMeasureEl_ = null; |
| } |
| this.element_.innerHTML = ''; |
| }; |
| |
| contextPrototype.beginPath = function() { |
| // TODO: Branch current matrix so that save/restore has no effect |
| // as per safari docs. |
| this.currentPath_ = []; |
| }; |
| |
| contextPrototype.moveTo = function(aX, aY) { |
| var p = getCoords(this, aX, aY); |
| this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); |
| this.currentX_ = p.x; |
| this.currentY_ = p.y; |
| }; |
| |
| contextPrototype.lineTo = function(aX, aY) { |
| var p = getCoords(this, aX, aY); |
| this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); |
| |
| this.currentX_ = p.x; |
| this.currentY_ = p.y; |
| }; |
| |
| contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, |
| aCP2x, aCP2y, |
| aX, aY) { |
| var p = getCoords(this, aX, aY); |
| var cp1 = getCoords(this, aCP1x, aCP1y); |
| var cp2 = getCoords(this, aCP2x, aCP2y); |
| bezierCurveTo(this, cp1, cp2, p); |
| }; |
| |
| // Helper function that takes the already fixed cordinates. |
| function bezierCurveTo(self, cp1, cp2, p) { |
| self.currentPath_.push({ |
| type: 'bezierCurveTo', |
| cp1x: cp1.x, |
| cp1y: cp1.y, |
| cp2x: cp2.x, |
| cp2y: cp2.y, |
| x: p.x, |
| y: p.y |
| }); |
| self.currentX_ = p.x; |
| self.currentY_ = p.y; |
| } |
| |
| contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { |
| // the following is lifted almost directly from |
| // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes |
| |
| var cp = getCoords(this, aCPx, aCPy); |
| var p = getCoords(this, aX, aY); |
| |
| var cp1 = { |
| x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), |
| y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) |
| }; |
| var cp2 = { |
| x: cp1.x + (p.x - this.currentX_) / 3.0, |
| y: cp1.y + (p.y - this.currentY_) / 3.0 |
| }; |
| |
| bezierCurveTo(this, cp1, cp2, p); |
| }; |
| |
| contextPrototype.arc = function(aX, aY, aRadius, |
| aStartAngle, aEndAngle, aClockwise) { |
| aRadius *= Z; |
| var arcType = aClockwise ? 'at' : 'wa'; |
| |
| var xStart = aX + mc(aStartAngle) * aRadius - Z2; |
| var yStart = aY + ms(aStartAngle) * aRadius - Z2; |
| |
| var xEnd = aX + mc(aEndAngle) * aRadius - Z2; |
| var yEnd = aY + ms(aEndAngle) * aRadius - Z2; |
| |
| // IE won't render arches drawn counter clockwise if xStart == xEnd. |
| if (xStart == xEnd && !aClockwise) { |
| xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something |
| // that can be represented in binary |
| } |
| |
| var p = getCoords(this, aX, aY); |
| var pStart = getCoords(this, xStart, yStart); |
| var pEnd = getCoords(this, xEnd, yEnd); |
| |
| this.currentPath_.push({type: arcType, |
| x: p.x, |
| y: p.y, |
| radius: aRadius, |
| xStart: pStart.x, |
| yStart: pStart.y, |
| xEnd: pEnd.x, |
| yEnd: pEnd.y}); |
| |
| }; |
| |
| contextPrototype.rect = function(aX, aY, aWidth, aHeight) { |
| this.moveTo(aX, aY); |
| this.lineTo(aX + aWidth, aY); |
| this.lineTo(aX + aWidth, aY + aHeight); |
| this.lineTo(aX, aY + aHeight); |
| this.closePath(); |
| }; |
| |
| contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { |
| var oldPath = this.currentPath_; |
| this.beginPath(); |
| |
| this.moveTo(aX, aY); |
| this.lineTo(aX + aWidth, aY); |
| this.lineTo(aX + aWidth, aY + aHeight); |
| this.lineTo(aX, aY + aHeight); |
| this.closePath(); |
| this.stroke(); |
| |
| this.currentPath_ = oldPath; |
| }; |
| |
| contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { |
| var oldPath = this.currentPath_; |
| this.beginPath(); |
| |
| this.moveTo(aX, aY); |
| this.lineTo(aX + aWidth, aY); |
| this.lineTo(aX + aWidth, aY + aHeight); |
| this.lineTo(aX, aY + aHeight); |
| this.closePath(); |
| this.fill(); |
| |
| this.currentPath_ = oldPath; |
| }; |
| |
| contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { |
| var gradient = new CanvasGradient_('gradient'); |
| gradient.x0_ = aX0; |
| gradient.y0_ = aY0; |
| gradient.x1_ = aX1; |
| gradient.y1_ = aY1; |
| return gradient; |
| }; |
| |
| contextPrototype.createRadialGradient = function(aX0, aY0, aR0, |
| aX1, aY1, aR1) { |
| var gradient = new CanvasGradient_('gradientradial'); |
| gradient.x0_ = aX0; |
| gradient.y0_ = aY0; |
| gradient.r0_ = aR0; |
| gradient.x1_ = aX1; |
| gradient.y1_ = aY1; |
| gradient.r1_ = aR1; |
| return gradient; |
| }; |
| |
| contextPrototype.drawImage = function(image, var_args) { |
| var dx, dy, dw, dh, sx, sy, sw, sh; |
| |
| // to find the original width we overide the width and height |
| var oldRuntimeWidth = image.runtimeStyle.width; |
| var oldRuntimeHeight = image.runtimeStyle.height; |
| image.runtimeStyle.width = 'auto'; |
| image.runtimeStyle.height = 'auto'; |
| |
| // get the original size |
| var w = image.width; |
| var h = image.height; |
| |
| // and remove overides |
| image.runtimeStyle.width = oldRuntimeWidth; |
| image.runtimeStyle.height = oldRuntimeHeight; |
| |
| if (arguments.length == 3) { |
| dx = arguments[1]; |
| dy = arguments[2]; |
| sx = sy = 0; |
| sw = dw = w; |
| sh = dh = h; |
| } else if (arguments.length == 5) { |
| dx = arguments[1]; |
| dy = arguments[2]; |
| dw = arguments[3]; |
| dh = arguments[4]; |
| sx = sy = 0; |
| sw = w; |
| sh = h; |
| } else if (arguments.length == 9) { |
| sx = arguments[1]; |
| sy = arguments[2]; |
| sw = arguments[3]; |
| sh = arguments[4]; |
| dx = arguments[5]; |
| dy = arguments[6]; |
| dw = arguments[7]; |
| dh = arguments[8]; |
| } else { |
| throw Error('Invalid number of arguments'); |
| } |
| |
| var d = getCoords(this, dx, dy); |
| |
| var w2 = sw / 2; |
| var h2 = sh / 2; |
| |
| var vmlStr = []; |
| |
| var W = 10; |
| var H = 10; |
| |
| var scaleX = scaleY = 1; |
| |
| // For some reason that I've now forgotten, using divs didn't work |
| vmlStr.push(' <g_vml_:group', |
| ' coordsize="', Z * W, ',', Z * H, '"', |
| ' coordorigin="0,0"' , |
| ' style="width:', W, 'px;height:', H, 'px;position:absolute;'); |
| |
| // If filters are necessary (rotation exists), create them |
| // filters are bog-slow, so only create them if abbsolutely necessary |
| // The following check doesn't account for skews (which don't exist |
| // in the canvas spec (yet) anyway. |
| |
| if (this.m_[0][0] != 1 || this.m_[0][1] || |
| this.m_[1][1] != 1 || this.m_[1][0]) { |
| var filter = []; |
| |
| scaleX = Math.sqrt(this.m_[0][0] * this.m_[0][0] + this.m_[0][1] * this.m_[0][1]); |
| scaleY = Math.sqrt(this.m_[1][0] * this.m_[1][0] + this.m_[1][1] * this.m_[1][1]); |
| |
| // Note the 12/21 reversal |
| filter.push('M11=', this.m_[0][0] / scaleX, ',', |
| 'M12=', this.m_[1][0] / scaleY, ',', |
| 'M21=', this.m_[0][1] / scaleX, ',', |
| 'M22=', this.m_[1][1] / scaleY, ',', |
| 'Dx=', mr(d.x / Z), ',', |
| 'Dy=', mr(d.y / Z), ''); |
| |
| // Bounding box calculation (need to minimize displayed area so that |
| // filters don't waste time on unused pixels. |
| var max = d; |
| var c2 = getCoords(this, dx + dw, dy); |
| var c3 = getCoords(this, dx, dy + dh); |
| var c4 = getCoords(this, dx + dw, dy + dh); |
| |
| max.x = m.max(max.x, c2.x, c3.x, c4.x); |
| max.y = m.max(max.y, c2.y, c3.y, c4.y); |
| |
| vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z), |
| 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(', |
| filter.join(''), ", sizingmethod='clip');"); |
| |
| } else { |
| vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;'); |
| } |
| |
| vmlStr.push(' ">'); |
| |
| // Draw a special cropping div if needed |
| if (sx || sy) { |
| // Apply scales to width and height |
| vmlStr.push('<div style="overflow: hidden; width:', Math.ceil((dw + sx * dw / sw) * scaleX), 'px;', |
| ' height:', Math.ceil((dh + sy * dh / sh) * scaleY), 'px;', |
| ' filter:progid:DxImageTransform.Microsoft.Matrix(Dx=', |
| -sx * dw / sw * scaleX, ',Dy=', -sy * dh / sh * scaleY, ');">'); |
| } |
| |
| |
| // Apply scales to width and height |
| vmlStr.push('<div style="width:', Math.round(scaleX * w * dw / sw), 'px;', |
| ' height:', Math.round(scaleY * h * dh / sh), 'px;', |
| ' filter:'); |
| |
| // If there is a globalAlpha, apply it to image |
| if(this.globalAlpha < 1) { |
| vmlStr.push(' progid:DXImageTransform.Microsoft.Alpha(opacity=' + (this.globalAlpha * 100) + ')'); |
| } |
| |
| vmlStr.push(' progid:DXImageTransform.Microsoft.AlphaImageLoader(src=', image.src, ',sizingMethod=scale)">'); |
| |
| // Close the crop div if necessary |
| if (sx || sy) vmlStr.push('</div>'); |
| |
| vmlStr.push('</div></div>'); |
| |
| this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); |
| }; |
| |
| contextPrototype.stroke = function(aFill) { |
| var lineStr = []; |
| var lineOpen = false; |
| |
| var W = 10; |
| var H = 10; |
| |
| lineStr.push('<g_vml_:shape', |
| ' filled="', !!aFill, '"', |
| ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', |
| ' coordorigin="0,0"', |
| ' coordsize="', Z * W, ',', Z * H, '"', |
| ' stroked="', !aFill, '"', |
| ' path="'); |
| |
| var newSeq = false; |
| var min = {x: null, y: null}; |
| var max = {x: null, y: null}; |
| |
| for (var i = 0; i < this.currentPath_.length; i++) { |
| var p = this.currentPath_[i]; |
| var c; |
| |
| switch (p.type) { |
| case 'moveTo': |
| c = p; |
| lineStr.push(' m ', mr(p.x), ',', mr(p.y)); |
| break; |
| case 'lineTo': |
| lineStr.push(' l ', mr(p.x), ',', mr(p.y)); |
| break; |
| case 'close': |
| lineStr.push(' x '); |
| p = null; |
| break; |
| case 'bezierCurveTo': |
| lineStr.push(' c ', |
| mr(p.cp1x), ',', mr(p.cp1y), ',', |
| mr(p.cp2x), ',', mr(p.cp2y), ',', |
| mr(p.x), ',', mr(p.y)); |
| break; |
| case 'at': |
| case 'wa': |
| lineStr.push(' ', p.type, ' ', |
| mr(p.x - this.arcScaleX_ * p.radius), ',', |
| mr(p.y - this.arcScaleY_ * p.radius), ' ', |
| mr(p.x + this.arcScaleX_ * p.radius), ',', |
| mr(p.y + this.arcScaleY_ * p.radius), ' ', |
| mr(p.xStart), ',', mr(p.yStart), ' ', |
| mr(p.xEnd), ',', mr(p.yEnd)); |
| break; |
| } |
| |
| |
| // TODO: Following is broken for curves due to |
| // move to proper paths. |
| |
| // Figure out dimensions so we can do gradient fills |
| // properly |
| if (p) { |
| if (min.x == null || p.x < min.x) { |
| min.x = p.x; |
| } |
| if (max.x == null || p.x > max.x) { |
| max.x = p.x; |
| } |
| if (min.y == null || p.y < min.y) { |
| min.y = p.y; |
| } |
| if (max.y == null || p.y > max.y) { |
| max.y = p.y; |
| } |
| } |
| } |
| lineStr.push(' ">'); |
| |
| if (!aFill) { |
| appendStroke(this, lineStr); |
| } else { |
| appendFill(this, lineStr, min, max); |
| } |
| |
| lineStr.push('</g_vml_:shape>'); |
| |
| this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); |
| }; |
| |
| function appendStroke(ctx, lineStr) { |
| var a = processStyle(ctx.strokeStyle); |
| var color = a.color; |
| var opacity = a.alpha * ctx.globalAlpha; |
| var lineWidth = ctx.lineScale_ * ctx.lineWidth; |
| |
| // VML cannot correctly render a line if the width is less than 1px. |
| // In that case, we dilute the color to make the line look thinner. |
| if (lineWidth < 1) { |
| opacity *= lineWidth; |
| } |
| |
| lineStr.push( |
| '<g_vml_:stroke', |
| ' opacity="', opacity, '"', |
| ' joinstyle="', ctx.lineJoin, '"', |
| ' miterlimit="', ctx.miterLimit, '"', |
| ' endcap="', processLineCap(ctx.lineCap), '"', |
| ' weight="', lineWidth, 'px"', |
| ' color="', color, '" />' |
| ); |
| } |
| |
| function appendFill(ctx, lineStr, min, max) { |
| var fillStyle = ctx.fillStyle; |
| var arcScaleX = ctx.arcScaleX_; |
| var arcScaleY = ctx.arcScaleY_; |
| var width = max.x - min.x; |
| var height = max.y - min.y; |
| if (fillStyle instanceof CanvasGradient_) { |
| // TODO: Gradients transformed with the transformation matrix. |
| var angle = 0; |
| var focus = {x: 0, y: 0}; |
| |
| // additional offset |
| var shift = 0; |
| // scale factor for offset |
| var expansion = 1; |
| |
| if (fillStyle.type_ == 'gradient') { |
| var x0 = fillStyle.x0_ / arcScaleX; |
| var y0 = fillStyle.y0_ / arcScaleY; |
| var x1 = fillStyle.x1_ / arcScaleX; |
| var y1 = fillStyle.y1_ / arcScaleY; |
| var p0 = getCoords(ctx, x0, y0); |
| var p1 = getCoords(ctx, x1, y1); |
| var dx = p1.x - p0.x; |
| var dy = p1.y - p0.y; |
| angle = Math.atan2(dx, dy) * 180 / Math.PI; |
| |
| // The angle should be a non-negative number. |
| if (angle < 0) { |
| angle += 360; |
| } |
| |
| // Very small angles produce an unexpected result because they are |
| // converted to a scientific notation string. |
| if (angle < 1e-6) { |
| angle = 0; |
| } |
| } else { |
| var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); |
| focus = { |
| x: (p0.x - min.x) / width, |
| y: (p0.y - min.y) / height |
| }; |
| |
| width /= arcScaleX * Z; |
| height /= arcScaleY * Z; |
| var dimension = m.max(width, height); |
| shift = 2 * fillStyle.r0_ / dimension; |
| expansion = 2 * fillStyle.r1_ / dimension - shift; |
| } |
| |
| // We need to sort the color stops in ascending order by offset, |
| // otherwise IE won't interpret it correctly. |
| var stops = fillStyle.colors_; |
| stops.sort(function(cs1, cs2) { |
| return cs1.offset - cs2.offset; |
| }); |
| |
| var length = stops.length; |
| var color1 = stops[0].color; |
| var color2 = stops[length - 1].color; |
| var opacity1 = stops[0].alpha * ctx.globalAlpha; |
| var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; |
| |
| var colors = []; |
| for (var i = 0; i < length; i++) { |
| var stop = stops[i]; |
| colors.push(stop.offset * expansion + shift + ' ' + stop.color); |
| } |
| |
| // When colors attribute is used, the meanings of opacity and o:opacity2 |
| // are reversed. |
| lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', |
| ' method="none" focus="100%"', |
| ' color="', color1, '"', |
| ' color2="', color2, '"', |
| ' colors="', colors.join(','), '"', |
| ' opacity="', opacity2, '"', |
| ' g_o_:opacity2="', opacity1, '"', |
| ' angle="', angle, '"', |
| ' focusposition="', focus.x, ',', focus.y, '" />'); |
| } else if (fillStyle instanceof CanvasPattern_) { |
| if (width && height) { |
| var deltaLeft = -min.x; |
| var deltaTop = -min.y; |
| lineStr.push('<g_vml_:fill', |
| ' position="', |
| deltaLeft / width * arcScaleX * arcScaleX, ',', |
| deltaTop / height * arcScaleY * arcScaleY, '"', |
| ' type="tile"', |
| // TODO: Figure out the correct size to fit the scale. |
| //' size="', w, 'px ', h, 'px"', |
| ' src="', fillStyle.src_, '" />'); |
| } |
| } else { |
| var a = processStyle(ctx.fillStyle); |
| var color = a.color; |
| var opacity = a.alpha * ctx.globalAlpha; |
| lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, |
| '" />'); |
| } |
| } |
| |
| contextPrototype.fill = function() { |
| this.stroke(true); |
| }; |
| |
| contextPrototype.closePath = function() { |
| this.currentPath_.push({type: 'close'}); |
| }; |
| |
| function getCoords(ctx, aX, aY) { |
| var m = ctx.m_; |
| return { |
| x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, |
| y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 |
| }; |
| }; |
| |
| contextPrototype.save = function() { |
| var o = {}; |
| copyState(this, o); |
| this.aStack_.push(o); |
| this.mStack_.push(this.m_); |
| this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); |
| }; |
| |
| contextPrototype.restore = function() { |
| if (this.aStack_.length) { |
| copyState(this.aStack_.pop(), this); |
| this.m_ = this.mStack_.pop(); |
| } |
| }; |
| |
| function matrixIsFinite(m) { |
| return isFinite(m[0][0]) && isFinite(m[0][1]) && |
| isFinite(m[1][0]) && isFinite(m[1][1]) && |
| isFinite(m[2][0]) && isFinite(m[2][1]); |
| } |
| |
| function setM(ctx, m, updateLineScale) { |
| if (!matrixIsFinite(m)) { |
| return; |
| } |
| ctx.m_ = m; |
| |
| if (updateLineScale) { |
| // Get the line scale. |
| // Determinant of this.m_ means how much the area is enlarged by the |
| // transformation. So its square root can be used as a scale factor |
| // for width. |
| var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; |
| ctx.lineScale_ = sqrt(abs(det)); |
| } |
| } |
| |
| contextPrototype.translate = function(aX, aY) { |
| var m1 = [ |
| [1, 0, 0], |
| [0, 1, 0], |
| [aX, aY, 1] |
| ]; |
| |
| setM(this, matrixMultiply(m1, this.m_), false); |
| }; |
| |
| contextPrototype.rotate = function(aRot) { |
| var c = mc(aRot); |
| var s = ms(aRot); |
| |
| var m1 = [ |
| [c, s, 0], |
| [-s, c, 0], |
| [0, 0, 1] |
| ]; |
| |
| setM(this, matrixMultiply(m1, this.m_), false); |
| }; |
| |
| contextPrototype.scale = function(aX, aY) { |
| this.arcScaleX_ *= aX; |
| this.arcScaleY_ *= aY; |
| var m1 = [ |
| [aX, 0, 0], |
| [0, aY, 0], |
| [0, 0, 1] |
| ]; |
| |
| setM(this, matrixMultiply(m1, this.m_), true); |
| }; |
| |
| contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { |
| var m1 = [ |
| [m11, m12, 0], |
| [m21, m22, 0], |
| [dx, dy, 1] |
| ]; |
| |
| setM(this, matrixMultiply(m1, this.m_), true); |
| }; |
| |
| contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { |
| var m = [ |
| [m11, m12, 0], |
| [m21, m22, 0], |
| [dx, dy, 1] |
| ]; |
| |
| setM(this, m, true); |
| }; |
| |
| /** |
| * The text drawing function. |
| * The maxWidth argument isn't taken in account, since no browser supports |
| * it yet. |
| */ |
| contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { |
| var m = this.m_, |
| delta = 1000, |
| left = 0, |
| right = delta, |
| offset = {x: 0, y: 0}, |
| lineStr = []; |
| |
| var fontStyle = getComputedStyle(processFontStyle(this.font), |
| this.element_); |
| |
| var fontStyleString = buildStyle(fontStyle); |
| |
| var elementStyle = this.element_.currentStyle; |
| var textAlign = this.textAlign.toLowerCase(); |
| switch (textAlign) { |
| case 'left': |
| case 'center': |
| case 'right': |
| break; |
| case 'end': |
| textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; |
| break; |
| case 'start': |
| textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; |
| break; |
| default: |
| textAlign = 'left'; |
| } |
| |
| // 1.75 is an arbitrary number, as there is no info about the text baseline |
| switch (this.textBaseline) { |
| case 'hanging': |
| case 'top': |
| offset.y = fontStyle.size / 1.75; |
| break; |
| case 'middle': |
| break; |
| default: |
| case null: |
| case 'alphabetic': |
| case 'ideographic': |
| case 'bottom': |
| offset.y = -fontStyle.size / 2.25; |
| break; |
| } |
| |
| switch(textAlign) { |
| case 'right': |
| left = delta; |
| right = 0.05; |
| break; |
| case 'center': |
| left = right = delta / 2; |
| break; |
| } |
| |
| var d = getCoords(this, x + offset.x, y + offset.y); |
| |
| lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ', |
| ' coordsize="100 100" coordorigin="0 0"', |
| ' filled="', !stroke, '" stroked="', !!stroke, |
| '" style="position:absolute;width:1px;height:1px;">'); |
| |
| if (stroke) { |
| appendStroke(this, lineStr); |
| } else { |
| // TODO: Fix the min and max params. |
| appendFill(this, lineStr, {x: -left, y: 0}, |
| {x: right, y: fontStyle.size}); |
| } |
| |
| var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + |
| m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; |
| |
| var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); |
| |
| lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ', |
| ' offset="', skewOffset, '" origin="', left ,' 0" />', |
| '<g_vml_:path textpathok="true" />', |
| '<g_vml_:textpath on="true" string="', |
| encodeHtmlAttribute(text), |
| '" style="v-text-align:', textAlign, |
| ';font:', encodeHtmlAttribute(fontStyleString), |
| '" /></g_vml_:line>'); |
| |
| this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); |
| }; |
| |
| contextPrototype.fillText = function(text, x, y, maxWidth) { |
| this.drawText_(text, x, y, maxWidth, false); |
| }; |
| |
| contextPrototype.strokeText = function(text, x, y, maxWidth) { |
| this.drawText_(text, x, y, maxWidth, true); |
| }; |
| |
| contextPrototype.measureText = function(text) { |
| if (!this.textMeasureEl_) { |
| var s = '<span style="position:absolute;' + |
| 'top:-20000px;left:0;padding:0;margin:0;border:none;' + |
| 'white-space:pre;"></span>'; |
| this.element_.insertAdjacentHTML('beforeEnd', s); |
| this.textMeasureEl_ = this.element_.lastChild; |
| } |
| var doc = this.element_.ownerDocument; |
| this.textMeasureEl_.innerHTML = ''; |
| this.textMeasureEl_.style.font = this.font; |
| // Don't use innerHTML or innerText because they allow markup/whitespace. |
| this.textMeasureEl_.appendChild(doc.createTextNode(text)); |
| return {width: this.textMeasureEl_.offsetWidth}; |
| }; |
| |
| /******** STUBS ********/ |
| contextPrototype.clip = function() { |
| // TODO: Implement |
| }; |
| |
| contextPrototype.arcTo = function() { |
| // TODO: Implement |
| }; |
| |
| contextPrototype.createPattern = function(image, repetition) { |
| return new CanvasPattern_(image, repetition); |
| }; |
| |
| // Gradient / Pattern Stubs |
| function CanvasGradient_(aType) { |
| this.type_ = aType; |
| this.x0_ = 0; |
| this.y0_ = 0; |
| this.r0_ = 0; |
| this.x1_ = 0; |
| this.y1_ = 0; |
| this.r1_ = 0; |
| this.colors_ = []; |
| } |
| |
| CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { |
| aColor = processStyle(aColor); |
| this.colors_.push({offset: aOffset, |
| color: aColor.color, |
| alpha: aColor.alpha}); |
| }; |
| |
| function CanvasPattern_(image, repetition) { |
| assertImageIsValid(image); |
| switch (repetition) { |
| case 'repeat': |
| case null: |
| case '': |
| this.repetition_ = 'repeat'; |
| break |
| case 'repeat-x': |
| case 'repeat-y': |
| case 'no-repeat': |
| this.repetition_ = repetition; |
| break; |
| default: |
| throwException('SYNTAX_ERR'); |
| } |
| |
| this.src_ = image.src; |
| this.width_ = image.width; |
| this.height_ = image.height; |
| } |
| |
| function throwException(s) { |
| throw new DOMException_(s); |
| } |
| |
| function assertImageIsValid(img) { |
| if (!img || img.nodeType != 1 || img.tagName != 'IMG') { |
| throwException('TYPE_MISMATCH_ERR'); |
| } |
| if (img.readyState != 'complete') { |
| throwException('INVALID_STATE_ERR'); |
| } |
| } |
| |
| function DOMException_(s) { |
| this.code = this[s]; |
| this.message = s +': DOM Exception ' + this.code; |
| } |
| var p = DOMException_.prototype = new Error; |
| p.INDEX_SIZE_ERR = 1; |
| p.DOMSTRING_SIZE_ERR = 2; |
| p.HIERARCHY_REQUEST_ERR = 3; |
| p.WRONG_DOCUMENT_ERR = 4; |
| p.INVALID_CHARACTER_ERR = 5; |
| p.NO_DATA_ALLOWED_ERR = 6; |
| p.NO_MODIFICATION_ALLOWED_ERR = 7; |
| p.NOT_FOUND_ERR = 8; |
| p.NOT_SUPPORTED_ERR = 9; |
| p.INUSE_ATTRIBUTE_ERR = 10; |
| p.INVALID_STATE_ERR = 11; |
| p.SYNTAX_ERR = 12; |
| p.INVALID_MODIFICATION_ERR = 13; |
| p.NAMESPACE_ERR = 14; |
| p.INVALID_ACCESS_ERR = 15; |
| p.VALIDATION_ERR = 16; |
| p.TYPE_MISMATCH_ERR = 17; |
| |
| // set up externs |
| G_vmlCanvasManager = G_vmlCanvasManager_; |
| CanvasRenderingContext2D = CanvasRenderingContext2D_; |
| CanvasGradient = CanvasGradient_; |
| CanvasPattern = CanvasPattern_; |
| DOMException = DOMException_; |
| })(); |
| |
| } // if |
| else { // make the canvas test simple by kener.linfeng@gmail.com |
| G_vmlCanvasManager = false; |
| } |
| return G_vmlCanvasManager; |
| }); // define; |
| /** |
| * zrender: 公共辅助函数 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * clone:深度克隆 |
| * merge:合并源对象的属性到目标对象 |
| * getContext:获取一个自由使用的canvas 2D context,使用原生方法,如isPointInPath,measureText等 |
| */ |
| define( |
| 'zrender/tool/util',['require','../lib/excanvas'],function(require) { |
| // 用于处理merge时无法遍历Date等对象的问题 |
| var BUILTIN_OBJECT = { |
| '[object Function]': 1, |
| '[object RegExp]': 1, |
| '[object Date]': 1, |
| '[object Error]': 1, |
| '[object CanvasGradient]': 1 |
| }; |
| |
| /** |
| * 对一个object进行深度拷贝 |
| * |
| * @param {Any} source 需要进行拷贝的对象 |
| * @return {Any} 拷贝后的新对象 |
| */ |
| function clone(source) { |
| if (typeof source == 'object' && source !== null) { |
| var result = source; |
| if (source instanceof Array) { |
| result = []; |
| for (var i = 0, len = source.length; i < len; i++) { |
| result[i] = clone(source[i]); |
| } |
| } |
| else if (!BUILTIN_OBJECT[Object.prototype.toString.call(source)]) { |
| result = {}; |
| for (var key in source) { |
| if (source.hasOwnProperty(key)) { |
| result[key] = clone(source[key]); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| return source; |
| } |
| |
| function mergeItem(target, source, key, overwrite) { |
| if (source.hasOwnProperty(key)) { |
| if (typeof target[key] == 'object' |
| && !BUILTIN_OBJECT[ Object.prototype.toString.call(target[key]) ] |
| ) { |
| // 如果需要递归覆盖,就递归调用merge |
| merge( |
| target[key], |
| source[key], |
| overwrite |
| ); |
| } |
| else if (overwrite || !(key in target)) { |
| // 否则只处理overwrite为true,或者在目标对象中没有此属性的情况 |
| target[key] = source[key]; |
| } |
| } |
| } |
| |
| /** |
| * 合并源对象的属性到目标对象 |
| * modify from Tangram |
| * @param {*} target 目标对象 |
| * @param {*} source 源对象 |
| * @param {boolean} overwrite 是否覆盖 |
| */ |
| function merge(target, source, overwrite) { |
| for (var i in source) { |
| mergeItem(target, source, i, overwrite); |
| } |
| |
| return target; |
| } |
| |
| var _ctx; |
| |
| function getContext() { |
| if (!_ctx) { |
| require('../lib/excanvas'); |
| if (G_vmlCanvasManager) { |
| var _div = document.createElement('div'); |
| _div.style.position = 'absolute'; |
| _div.style.top = '-1000px'; |
| document.body.appendChild(_div); |
| |
| _ctx = G_vmlCanvasManager.initElement(_div) |
| .getContext('2d'); |
| } |
| else { |
| _ctx = document.createElement('canvas').getContext('2d'); |
| } |
| } |
| return _ctx; |
| } |
| |
| var _canvas; |
| var _pixelCtx; |
| var _width; |
| var _height; |
| var _offsetX = 0; |
| var _offsetY = 0; |
| |
| /** |
| * 获取像素拾取专用的上下文 |
| * @return {Object} 上下文 |
| */ |
| function getPixelContext() { |
| if (!_pixelCtx) { |
| _canvas = document.createElement('canvas'); |
| _width = _canvas.width; |
| _height = _canvas.height; |
| _pixelCtx = _canvas.getContext('2d'); |
| } |
| return _pixelCtx; |
| } |
| |
| /** |
| * 如果坐标处在_canvas外部,改变_canvas的大小 |
| * @param {number} x : 横坐标 |
| * @param {number} y : 纵坐标 |
| * 注意 修改canvas的大小 需要重新设置translate |
| */ |
| function adjustCanvasSize(x, y) { |
| // 每次加的长度 |
| var _v = 100; |
| var _flag; |
| |
| if (x + _offsetX > _width) { |
| _width = x + _offsetX + _v; |
| _canvas.width = _width; |
| _flag = true; |
| } |
| |
| if (y + _offsetY > _height) { |
| _height = y + _offsetY + _v; |
| _canvas.height = _height; |
| _flag = true; |
| } |
| |
| if (x < -_offsetX) { |
| _offsetX = Math.ceil(-x / _v) * _v; |
| _width += _offsetX; |
| _canvas.width = _width; |
| _flag = true; |
| } |
| |
| if (y < -_offsetY) { |
| _offsetY = Math.ceil(-y / _v) * _v; |
| _height += _offsetY; |
| _canvas.height = _height; |
| _flag = true; |
| } |
| |
| if (_flag) { |
| _pixelCtx.translate(_offsetX, _offsetY); |
| } |
| } |
| |
| /** |
| * 获取像素canvas的偏移量 |
| * @return {Object} 偏移量 |
| */ |
| function getPixelOffset() { |
| return { |
| x : _offsetX, |
| y : _offsetY |
| }; |
| } |
| |
| /** |
| * 查询数组中元素的index |
| */ |
| function indexOf(array, value){ |
| if (array.indexOf) { |
| return array.indexOf(value); |
| } |
| for(var i = 0, len=array.length; i<len; i++) { |
| if (array[i] === value) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * 构造类继承关系 |
| * |
| * @param {Function} clazz 源类 |
| * @param {Function} baseClazz 基类 |
| */ |
| function inherits(clazz, baseClazz) { |
| var clazzPrototype = clazz.prototype; |
| function F() {} |
| F.prototype = baseClazz.prototype; |
| clazz.prototype = new F(); |
| |
| for (var prop in clazzPrototype) { |
| clazz.prototype[prop] = clazzPrototype[prop]; |
| } |
| clazz.constructor = clazz; |
| } |
| |
| return { |
| inherits: inherits, |
| clone : clone, |
| merge : merge, |
| getContext : getContext, |
| getPixelContext : getPixelContext, |
| getPixelOffset : getPixelOffset, |
| adjustCanvasSize : adjustCanvasSize, |
| indexOf : indexOf |
| }; |
| } |
| ); |
| |
| /** |
| * zrender: 事件辅助类 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * getX:获取事件横坐标 |
| * getY:或者事件纵坐标 |
| * getDelta:或者鼠标滚轮变化 |
| * stop:停止事件传播 |
| * Dispatcher:事件分发器 |
| */ |
| define( |
| 'zrender/tool/event',[],function() { |
| |
| |
| |
| /** |
| * 提取鼠标(手指)x坐标 |
| * |
| * @param {Event} e 事件. |
| * @return {number} 鼠标(手指)x坐标. |
| */ |
| function getX(e) { |
| return typeof e.zrenderX != 'undefined' && e.zrenderX |
| || typeof e.offsetX != 'undefined' && e.offsetX |
| || typeof e.layerX != 'undefined' && e.layerX |
| || typeof e.clientX != 'undefined' && e.clientX; |
| } |
| |
| /** |
| * 提取鼠标y坐标 |
| * |
| * @param {Event} e 事件. |
| * @return {number} 鼠标(手指)y坐标. |
| */ |
| function getY(e) { |
| return typeof e.zrenderY != 'undefined' && e.zrenderY |
| || typeof e.offsetY != 'undefined' && e.offsetY |
| || typeof e.layerY != 'undefined' && e.layerY |
| || typeof e.clientY != 'undefined' && e.clientY; |
| } |
| |
| /** |
| * 提取鼠标滚轮变化 |
| * |
| * @param {Event} e 事件. |
| * @return {number} 滚轮变化,正值说明滚轮是向上滚动,如果是负值说明滚轮是向下滚动 |
| */ |
| function getDelta(e) { |
| return typeof e.wheelDelta != 'undefined' && e.wheelDelta |
| || typeof e.detail != 'undefined' && -e.detail; |
| } |
| |
| /** |
| * 停止冒泡和阻止默认行为 |
| * |
| * @type {Function} |
| * @param {Event} e : event对象 |
| */ |
| var stop = window.Event && window.Event.prototype.preventDefault |
| ? function (e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| : function (e) { |
| e.returnValue = false; |
| e.cancelBubble = true; |
| }; |
| |
| /** |
| * 事件分发器 |
| */ |
| function Dispatcher() { |
| this._handlers = {}; |
| } |
| /** |
| * 单次触发绑定,dispatch后销毁 |
| * |
| * @param {string} event 事件字符串 |
| * @param {Function} handler 响应函数 |
| * @param {Object} [context] |
| */ |
| Dispatcher.prototype.one = function(event, handler, context) { |
| |
| var _h = this._handlers; |
| |
| if(!handler || !event) { |
| return this; |
| } |
| |
| if(!_h[event]) { |
| _h[event] = []; |
| } |
| |
| _h[event].push({ |
| h : handler, |
| one : true, |
| ctx: context || this |
| }); |
| |
| return this; |
| }; |
| |
| /** |
| * 事件绑定 |
| * |
| * @param {string} event 事件字符串 |
| * @param {Function} handler : 响应函数 |
| * @param {Object} [context] |
| */ |
| Dispatcher.prototype.bind = function(event, handler, context) { |
| |
| var _h = this._handlers; |
| |
| if(!handler || !event) { |
| return this; |
| } |
| |
| if(!_h[event]) { |
| _h[event] = []; |
| } |
| |
| _h[event].push({ |
| h : handler, |
| one : false, |
| ctx: context || this |
| }); |
| |
| return this; |
| }; |
| |
| /** |
| * 事件解绑定 |
| * |
| * @param {string} event 事件字符串 |
| * @param {Function} handler : 响应函数 |
| */ |
| Dispatcher.prototype.unbind = function(event, handler) { |
| |
| var _h = this._handlers; |
| |
| if(!event) { |
| this._handlers = {}; |
| return this; |
| } |
| |
| if(handler) { |
| if(_h[event]) { |
| var newList = []; |
| for (var i = 0, l = _h[event].length; i < l; i++) { |
| if (_h[event][i]['h'] != handler) { |
| newList.push(_h[event][i]); |
| } |
| } |
| _h[event] = newList; |
| } |
| |
| if(_h[event] && _h[event].length === 0) { |
| delete _h[event]; |
| } |
| } |
| else { |
| delete _h[event]; |
| } |
| |
| return this; |
| }; |
| |
| /** |
| * 事件分发 |
| * |
| * @param {string} type : 事件类型 |
| */ |
| Dispatcher.prototype.dispatch = function(type) { |
| var args = arguments; |
| var argLen = args.length; |
| |
| if (argLen > 3) { |
| args = Array.prototype.slice.call(args, 1); |
| } |
| |
| if(this._handlers[type]) { |
| var _h = this._handlers[type]; |
| var len = _h.length; |
| for (var i = 0; i < len;) { |
| // Optimize advise from backbone |
| switch (argLen) { |
| case 1: |
| _h[i]['h'].call(_h[i]['ctx']); |
| break; |
| case 2: |
| _h[i]['h'].call(_h[i]['ctx'], args[1]); |
| break; |
| case 3: |
| _h[i]['h'].call(_h[i]['ctx'], args[1], args[2]); |
| break; |
| default: |
| // have more than 2 given arguments |
| _h[i]['h'].apply(_h[i]['ctx'], args); |
| break; |
| } |
| |
| if (_h[i]['one']) { |
| _h.splice(i, 1); |
| len--; |
| } else { |
| i++; |
| } |
| } |
| } |
| |
| return this; |
| }; |
| |
| /** |
| * 带有context的事件分发, 最后一个参数是事件回调的context |
| * |
| * @param {string} type : 事件类型 |
| */ |
| Dispatcher.prototype.dispatchWithContext = function(type) { |
| var args = arguments; |
| var argLen = args.length; |
| |
| if (argLen > 4) { |
| args = Array.prototype.slice.call(args, 1, args.length - 1); |
| } |
| var ctx = args[args.length - 1]; |
| |
| if(this._handlers[type]) { |
| var _h = this._handlers[type]; |
| var len = _h.length; |
| for (var i = 0; i < len;) { |
| // Optimize advise from backbone |
| switch (argLen) { |
| case 1: |
| _h[i]['h'].call(ctx); |
| break; |
| case 2: |
| _h[i]['h'].call(ctx, args[1]); |
| break; |
| case 3: |
| _h[i]['h'].call(ctx, args[1], args[2]); |
| break; |
| default: |
| // have more than 2 given arguments |
| _h[i]['h'].apply(ctx, args); |
| break; |
| } |
| |
| if (_h[i]['one']) { |
| _h.splice(i, 1); |
| len--; |
| } else { |
| i++; |
| } |
| } |
| } |
| |
| return this; |
| }; |
| |
| return { |
| getX : getX, |
| getY : getY, |
| getDelta : getDelta, |
| stop : stop, |
| Dispatcher : Dispatcher |
| }; |
| } |
| ); |
| /** |
| * echarts设备环境识别 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author firede[firede@firede.us] |
| * @desc thanks zepto. |
| */ |
| define('zrender/tool/env',[],function() { |
| // Zepto.js |
| // (c) 2010-2013 Thomas Fuchs |
| // Zepto.js may be freely distributed under the MIT license. |
| |
| function detect( ua ) { |
| var os = this.os = {}; |
| var browser = this.browser = {}; |
| var webkit = ua.match(/Web[kK]it[\/]{0,1}([\d.]+)/); |
| var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/); |
| var ipad = ua.match(/(iPad).*OS\s([\d_]+)/); |
| var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/); |
| var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/); |
| var webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/); |
| var touchpad = webos && ua.match(/TouchPad/); |
| var kindle = ua.match(/Kindle\/([\d.]+)/); |
| var silk = ua.match(/Silk\/([\d._]+)/); |
| var blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/); |
| var bb10 = ua.match(/(BB10).*Version\/([\d.]+)/); |
| var rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/); |
| var playbook = ua.match(/PlayBook/); |
| var chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/); |
| var firefox = ua.match(/Firefox\/([\d.]+)/); |
| var ie = ua.match(/MSIE ([\d.]+)/); |
| var safari = webkit && ua.match(/Mobile\//) && !chrome; |
| var webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome; |
| var ie = ua.match(/MSIE\s([\d.]+)/); |
| |
| // Todo: clean this up with a better OS/browser seperation: |
| // - discern (more) between multiple browsers on android |
| // - decide if kindle fire in silk mode is android or not |
| // - Firefox on Android doesn't specify the Android version |
| // - possibly devide in os, device and browser hashes |
| |
| if (browser.webkit = !!webkit) browser.version = webkit[1]; |
| |
| if (android) os.android = true, os.version = android[2]; |
| if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.'); |
| if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.'); |
| if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null; |
| if (webos) os.webos = true, os.version = webos[2]; |
| if (touchpad) os.touchpad = true; |
| if (blackberry) os.blackberry = true, os.version = blackberry[2]; |
| if (bb10) os.bb10 = true, os.version = bb10[2]; |
| if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]; |
| if (playbook) browser.playbook = true; |
| if (kindle) os.kindle = true, os.version = kindle[1]; |
| if (silk) browser.silk = true, browser.version = silk[1]; |
| if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true; |
| if (chrome) browser.chrome = true, browser.version = chrome[1]; |
| if (firefox) browser.firefox = true, browser.version = firefox[1]; |
| if (ie) browser.ie = true, browser.version = ie[1]; |
| if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true; |
| if (webview) browser.webview = true; |
| if (ie) browser.ie = true, browser.version = ie[1]; |
| |
| os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || |
| (firefox && ua.match(/Tablet/)) || (ie && !ua.match(/Phone/) && ua.match(/Touch/))); |
| os.phone = !!(!os.tablet && !os.ipod && (android || iphone || webos || blackberry || bb10 || |
| (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || |
| (firefox && ua.match(/Mobile/)) || (ie && ua.match(/Touch/)))); |
| |
| return { |
| browser: browser, |
| os: os, |
| // 原生canvas支持 |
| canvasSupported : document.createElement('canvas').getContext |
| ? true : false |
| } |
| } |
| |
| return detect( navigator.userAgent ); |
| }); |
| /** |
| * zrender: config默认配置项 |
| * |
| * @desc zrender是一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define( |
| 'zrender/config',{ |
| 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, |
| |
| /** |
| * debug日志选项:catchBrushException为true下有效 |
| * 0 : 不生成debug数据,发布用 |
| * 1 : 异常抛出,调试用 |
| * 2 : 控制台输出,调试用 |
| */ |
| debugMode: 0 |
| } |
| ); |
| /** |
| * zrender: 日志记录 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| */ |
| |
| define( |
| 'zrender/tool/log',['require','../config'],function (require) { |
| var config = require('../config'); |
| |
| return function() { |
| if (config.debugMode === 0) { |
| return; |
| } |
| else if (config.debugMode == 1) { |
| for (var k in arguments) { |
| throw new Error(arguments[k]); |
| } |
| } |
| else if (config.debugMode > 1) { |
| for (var k in arguments) { |
| console.log(arguments[k]); |
| } |
| } |
| }; |
| |
| /* for debug |
| return function(mes) { |
| document.getElementById('wrong-message').innerHTML = |
| mes + ' ' + (new Date() - 0) |
| + '<br/>' |
| + document.getElementById('wrong-message').innerHTML; |
| }; |
| */ |
| } |
| ); |
| |
| /** |
| * zrender: 生成唯一id |
| * |
| * @author errorrik (errorrik@gmail.com) |
| */ |
| |
| define( |
| 'zrender/tool/guid',[],function() { |
| var idStart = 0x0907; |
| |
| return function () { |
| return 'zrender__' + (idStart++); |
| }; |
| } |
| ); |
| |
| /** |
| * Handler控制模块 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| define( |
| 'zrender/Handler',['require','./config','./tool/env','./tool/event','./tool/util'],function (require) { |
| |
| |
| |
| var config = require('./config'); |
| var env = require('./tool/env'); |
| var eventTool = require('./tool/event'); |
| var util = require('./tool/util'); |
| var EVENT = config.EVENT; |
| |
| var domHandlerNames = [ |
| 'resize', 'click', |
| 'mousewheel', 'mousemove', 'mouseout', 'mouseup', 'mousedown', |
| 'touchstart', 'touchend', 'touchmove' |
| ]; |
| |
| 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); |
| }, |
| |
| /** |
| * 点击响应函数 |
| * |
| * @param {event} event dom事件对象 |
| */ |
| click: function (event) { |
| event = this._zrenderEventFixed(event); |
| |
| //分发config.EVENT.CLICK事件 |
| var _lastHover = this._lastHover; |
| if (( _lastHover && _lastHover.clickable ) |
| || !_lastHover |
| ) { |
| this._dispatchAgency(_lastHover, EVENT.CLICK, event); |
| } |
| |
| this._mousemoveHandler(event); |
| }, |
| |
| /** |
| * 鼠标滚轮响应函数 |
| * |
| * @param {event} event dom事件对象 |
| */ |
| mousewheel: function (event) { |
| event = this._zrenderEventFixed(event); |
| |
| //分发config.EVENT.MOUSEWHEEL事件 |
| this._dispatchAgency(this._lastHover, EVENT.MOUSEWHEEL, event); |
| this._mousemoveHandler(event); |
| }, |
| |
| /** |
| * 鼠标(手指)移动响应函数 |
| * |
| * @param {event} event dom事件对象 |
| */ |
| mousemove: function (event) { |
| if (this.painter.isLoading()) { |
| return; |
| } |
| |
| event = this._zrenderEventFixed(event); |
| this._lastX = this._mouseX; |
| this._lastY = this._mouseY; |
| this._mouseX = eventTool.getX(event); |
| this._mouseY = eventTool.getY(event); |
| |
| // 可能出现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'}); |
| |
| // 找到的在迭代函数里做了处理,没找到得在迭代完后处理 |
| if (!this._hasfound) { |
| // 过滤首次拖拽产生的mouseout和dragLeave |
| if (!this._draggingTarget |
| || (this._lastHover && this._lastHover != this._draggingTarget) |
| ) { |
| // 可能出现config.EVENT.MOUSEOUT事件 |
| this._processOutShape(event); |
| |
| // 可能出现config.EVENT.DRAGLEAVE事件 |
| this._processDragLeave(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); |
| } |
| |
| // 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; |
| |
| // 分发config.EVENT.MOUSEMOVE事件 |
| this._dispatchAgency(this._lastHover, EVENT.MOUSEMOVE, 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; |
| } |
| } |
| |
| 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(); |
| } |
| |
| 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._lastMouseDownMoment = new Date(); |
| event = this._zrenderEventFixed(event); |
| this._isMouseDown = 1; |
| |
| //分发config.EVENT.MOUSEDOWN事件 |
| this._mouseDownTarget = this._lastHover; |
| this._dispatchAgency(this._lastHover, EVENT.MOUSEDOWN, event); |
| this._lastDownButton = event.button; |
| }, |
| |
| /** |
| * 鼠标(手指)抬起响应函数 |
| * |
| * @param {event} event dom事件对象 |
| */ |
| mouseup:function (event) { |
| event = this._zrenderEventFixed(event); |
| this.root.style.cursor = 'default'; |
| this._isMouseDown = 0; |
| this._mouseDownTarget = null; |
| |
| //分发config.EVENT.MOUSEUP事件 |
| this._dispatchAgency(this._lastHover, EVENT.MOUSEUP, event); |
| this._processDrop(event); |
| this._processDragEnd(event); |
| }, |
| |
| /** |
| * Touch开始响应函数 |
| * |
| * @param {event} event dom事件对象 |
| */ |
| touchstart: function (event) { |
| //eventTool.stop(event);// 阻止浏览器默认事件,重要 |
| event = this._zrenderEventFixed(event, true); |
| this._lastTouchMoment = new Date(); |
| |
| //平板补充一次findHover |
| this._mobildFindFixed(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结束响应函数 |
| * |
| * @param {event} event dom事件对象 |
| */ |
| touchend: function (event) { |
| //eventTool.stop(event);// 阻止浏览器默认事件,重要 |
| event = this._zrenderEventFixed(event, true); |
| this._mouseupHandler(event); |
| |
| if (new Date() - this._lastTouchMoment < EVENT.touchClickDelay) { |
| this._mobildFindFixed(event); |
| this._clickHandler(event); |
| } |
| this.painter.clearHover(); |
| } |
| }; |
| |
| /** |
| * bind一个参数的function |
| * |
| * @inner |
| * @param {Function} handler 要bind的function |
| * @param {Object} context 运行时this环境 |
| * @return {Function} |
| */ |
| function bind1Arg( handler, context ) { |
| return function ( e ) { |
| return handler.call( context, e ); |
| }; |
| } |
| |
| /** |
| * 为控制类实例初始化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 ); |
| } |
| } |
| |
| /** |
| * 控制类 (C) |
| * |
| * @param {HTMLElement} root 绘图区域 |
| * @param {storage} storage Storage实例 |
| * @param {painter} painter Painter实例 |
| * |
| * 分发事件支持详见config.EVENT |
| */ |
| function Handler(root, storage, painter) { |
| // 添加事件分发器特性 |
| eventTool.Dispatcher.call(this); |
| |
| this.root = root; |
| this.storage = storage; |
| this.painter = painter; |
| |
| // 各种事件标识的私有变量 |
| // 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; |
| |
| this._lastX = |
| this._lastY = |
| this._mouseX = |
| this._mouseY = 0; |
| |
| this._findHover = bind1Arg(findHover, this); |
| this._domHover = painter.getDomHover(); |
| initDomHandler(this); |
| |
| // 初始化,事件绑定,支持的所有事件都由如下原生事件计算得来 |
| 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); |
| |
| 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); |
| } |
| } |
| |
| /** |
| * 自定义事件绑定 |
| * @param {string} eventName 事件名称,resize,hover,drag,etc~ |
| * @param {Function} handler 响应函数 |
| */ |
| Handler.prototype.on = function (eventName, handler) { |
| this.bind(eventName, handler); |
| return this; |
| }; |
| |
| /** |
| * 自定义事件解绑 |
| * @param {string} eventName 事件名称,resize,hover,drag,etc~ |
| * @param {Function} handler 响应函数 |
| */ |
| Handler.prototype.un = function (eventName, handler) { |
| this.unbind(eventName, handler); |
| return this; |
| }; |
| |
| /** |
| * 事件触发 |
| * @param {string} eventName 事件名称,resize,hover,drag,etc~ |
| * @param {event=} eventArgs event dom事件对象 |
| */ |
| 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; |
| } |
| }; |
| |
| /** |
| * 释放 |
| */ |
| 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(); |
| }; |
| |
| /** |
| * 拖拽开始 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processDragStart = function (event) { |
| var _lastHover = this._lastHover; |
| |
| if (this._isMouseDown |
| && _lastHover |
| && _lastHover.draggable |
| && !this._draggingTarget |
| && this._mouseDownTarget == _lastHover |
| ) { |
| // 拖拽点击生效时长阀门,某些场景需要降低拖拽敏感度 |
| if (_lastHover.dragEnableTime && |
| new Date() - this._lastMouseDownMoment < _lastHover.dragEnableTime |
| ) { |
| return; |
| } |
| |
| var _draggingTarget = _lastHover; |
| this._draggingTarget = _draggingTarget; |
| this._isDragging = 1; |
| |
| _draggingTarget.invisible = true; |
| this.storage.mod(_draggingTarget.id); |
| |
| //分发config.EVENT.DRAGSTART事件 |
| this._dispatchAgency( |
| _draggingTarget, |
| EVENT.DRAGSTART, |
| event |
| ); |
| this.painter.refresh(); |
| } |
| }; |
| |
| /** |
| * 拖拽进入目标元素 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processDragEnter = function (event) { |
| if (this._draggingTarget) { |
| //分发config.EVENT.DRAGENTER事件 |
| this._dispatchAgency( |
| this._lastHover, |
| EVENT.DRAGENTER, |
| event, |
| this._draggingTarget |
| ); |
| } |
| }; |
| |
| /** |
| * 拖拽在目标元素上移动 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processDragOver = function (event) { |
| if (this._draggingTarget) { |
| //分发config.EVENT.DRAGOVER事件 |
| this._dispatchAgency( |
| this._lastHover, |
| EVENT.DRAGOVER, |
| event, |
| this._draggingTarget |
| ); |
| } |
| }; |
| |
| /** |
| * 拖拽离开目标元素 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processDragLeave = function (event) { |
| if (this._draggingTarget) { |
| //分发config.EVENT.DRAGLEAVE事件 |
| this._dispatchAgency( |
| this._lastHover, |
| EVENT.DRAGLEAVE, |
| event, |
| this._draggingTarget |
| ); |
| } |
| }; |
| |
| /** |
| * 拖拽在目标元素上完成 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processDrop = function (event) { |
| if (this._draggingTarget) { |
| this._draggingTarget.invisible = false; |
| this.storage.mod(this._draggingTarget.id); |
| this.painter.refresh(); |
| |
| //分发config.EVENT.DROP事件 |
| this._dispatchAgency( |
| this._lastHover, |
| EVENT.DROP, |
| event, |
| this._draggingTarget |
| ); |
| } |
| }; |
| |
| /** |
| * 拖拽结束 |
| * |
| * @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; |
| } |
| |
| this._isDragging = 0; |
| this._draggingTarget = null; |
| }; |
| |
| /** |
| * 鼠标在某个图形元素上移动 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processOverShape = function (event) { |
| //分发config.EVENT.MOUSEOVER事件 |
| this._dispatchAgency(this._lastHover, EVENT.MOUSEOVER, event); |
| }; |
| |
| /** |
| * 鼠标离开某个图形元素 |
| * |
| * @private |
| * @param {Object} event 事件对象 |
| */ |
| Handler.prototype._processOutShape = function (event) { |
| //分发config.EVENT.MOUSEOUT事件 |
| this._dispatchAgency(this._lastHover, EVENT.MOUSEOUT, event); |
| }; |
| |
| /** |
| * 事件分发代理 |
| * |
| * @private |
| * @param {Object} targetShape 目标图形元素 |
| * @param {string} eventName 事件名称 |
| * @param {Object} event 事件对象 |
| * @param {Object=} draggedShape 拖拽事件特有,当前被拖拽图形元素 |
| */ |
| Handler.prototype._dispatchAgency = function (targetShape, eventName, event, draggedShape) { |
| var eventHandler = 'on' + eventName; |
| var eventPacket = { |
| type : eventName, |
| event : event, |
| target : targetShape, |
| cancelBubble: false |
| }; |
| |
| var el = targetShape; |
| |
| if (draggedShape) { |
| eventPacket.dragged = draggedShape; |
| } |
| |
| while (el) { |
| el[eventHandler] && el[eventHandler](eventPacket); |
| el.dispatch(eventName, eventPacket); |
| |
| el = el.parent; |
| |
| if (eventPacket.cancelBubble) { |
| break; |
| } |
| } |
| |
| if (targetShape) { |
| // 冒泡到顶级 zrender 对象 |
| if (!eventPacket.cancelBubble) { |
| this.dispatch(eventName, eventPacket); |
| } |
| } |
| else if (!draggedShape) { |
| //无hover目标,无拖拽对象,原生事件分发 |
| this.dispatch(eventName, { |
| type: eventName, |
| event: event |
| }); |
| } |
| }; |
| |
| // 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 图形元素 |
| */ |
| 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); |
| } |
| // 查找是否在 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; |
| } |
| } |
| 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; |
| } |
| } |
| |
| event.zrenderFixed = 1; |
| return event; |
| }; |
| |
| util.merge(Handler.prototype, eventTool.Dispatcher.prototype, true); |
| |
| return Handler; |
| } |
| ); |
| |
| /** |
| * zrender: 3x2矩阵操作类 |
| * |
| * author: https://github.com/pissang |
| */ |
| |
| define( |
| 'zrender/tool/matrix',[],function() { |
| |
| var ArrayCtor = typeof Float32Array === 'undefined' |
| ? Array |
| : Float32Array; |
| |
| var matrix = { |
| create : function() { |
| var out = new ArrayCtor(6); |
| matrix.identity(out); |
| |
| return out; |
| }, |
| identity : function(out) { |
| out[0] = 1; |
| out[1] = 0; |
| out[2] = 0; |
| out[3] = 1; |
| out[4] = 0; |
| out[5] = 0; |
| }, |
| copy: function(out, m) { |
| out[0] = m[0]; |
| out[1] = m[1]; |
| out[2] = m[2]; |
| out[3] = m[3]; |
| out[4] = m[4]; |
| out[5] = m[5]; |
| }, |
| mul : function(out, m1, m2) { |
| out[0] = m1[0] * m2[0] + m1[2] * m2[1]; |
| out[1] = m1[1] * m2[0] + m1[3] * m2[1]; |
| out[2] = m1[0] * m2[2] + m1[2] * m2[3]; |
| out[3] = m1[1] * m2[2] + m1[3] * m2[3]; |
| out[4] = m1[0] * m2[4] + m1[2] * m2[5] + m1[4]; |
| out[5] = m1[1] * m2[4] + m1[3] * m2[5] + m1[5]; |
| return out; |
| }, |
| translate : function(out, a, v) { |
| out[0] = a[0]; |
| out[1] = a[1]; |
| out[2] = a[2]; |
| out[3] = a[3]; |
| out[4] = a[4] + v[0]; |
| out[5] = a[5] + v[1]; |
| return out; |
| }, |
| rotate : function(out, a, rad) { |
| var aa = a[0], ac = a[2], atx = a[4]; |
| var ab = a[1], ad = a[3], aty = a[5]; |
| var st = Math.sin(rad); |
| var ct = Math.cos(rad); |
| |
| out[0] = aa*ct + ab*st; |
| out[1] = -aa*st + ab*ct; |
| out[2] = ac*ct + ad*st; |
| out[3] = -ac*st + ct*ad; |
| out[4] = ct*atx + st*aty; |
| out[5] = ct*aty - st*atx; |
| return out; |
| }, |
| scale : function(out, a, v) { |
| var vx = v[0], vy = v[1]; |
| out[0] = a[0] * vx; |
| out[1] = a[1] * vy; |
| out[2] = a[2] * vx; |
| out[3] = a[3] * vy; |
| out[4] = a[4] * vx; |
| out[5] = a[5] * vy; |
| return out; |
| }, |
| /** |
| * 求逆矩阵 |
| */ |
| invert : function(out, a) { |
| |
| var aa = a[0], ac = a[2], atx = a[4]; |
| var ab = a[1], ad = a[3], aty = a[5]; |
| |
| var det = aa * ad - ab * ac; |
| if(!det){ |
| return null; |
| } |
| det = 1.0 / det; |
| |
| out[0] = ad * det; |
| out[1] = -ab * det; |
| out[2] = -ac * det; |
| out[3] = aa * det; |
| out[4] = (ac * aty - ad * atx) * det; |
| out[5] = (ab * atx - aa * aty) * det; |
| return out; |
| }, |
| |
| /** |
| * 矩阵左乘向量 |
| */ |
| mulVector : function(out, a, v) { |
| var aa = a[0], ac = a[2], atx = a[4]; |
| var ab = a[1], ad = a[3], aty = a[5]; |
| |
| out[0] = v[0] * aa + v[1] * ac + atx; |
| out[1] = v[0] * ab + v[1] * ad + aty; |
| |
| return out; |
| } |
| }; |
| |
| return matrix; |
| } |
| ); |
| define('zrender/shape/mixin/Transformable',['require','../../tool/matrix'],function(require) { |
| |
| var matrix = require('../../tool/matrix'); |
| var origin = [0, 0]; |
| |
| var Transformable = function() { |
| |
| if (!this.position) { |
| this.position = [0, 0]; |
| } |
| if (typeof(this.rotation) == 'undefined') { |
| this.rotation = [0, 0, 0]; |
| } |
| if (!this.scale) { |
| this.scale = [1, 1, 0, 0]; |
| } |
| |
| this.needLocalTransform = false; |
| this.needTransform = false; |
| }; |
| |
| Transformable.prototype = { |
| |
| constructor: Transformable, |
| |
| updateNeedTransform: function() { |
| this.needLocalTransform = Math.abs(this.rotation[0]) > 0.0001 |
| || Math.abs(this.position[0]) > 0.0001 |
| || Math.abs(this.position[1]) > 0.0001 |
| || Math.abs(this.scale[0] - 1) > 0.0001 |
| || Math.abs(this.scale[1] - 1) > 0.0001; |
| }, |
| |
| updateTransform: function() { |
| |
| this.updateNeedTransform(); |
| |
| if (this.parent) { |
| this.needTransform = this.needLocalTransform || this.parent.needTransform; |
| } else { |
| this.needTransform = this.needLocalTransform; |
| } |
| |
| if (!this.needTransform) { |
| return; |
| } |
| |
| var m = this.transform || matrix.create(); |
| matrix.identity(m); |
| |
| if (this.needLocalTransform) { |
| if (this.scale && (this.scale[0] !== 1 || this.scale[1] !== 1)) { |
| origin[0] = -this.scale[2] || 0; |
| origin[1] = -this.scale[3] || 0; |
| if (origin[0] || origin[1]) { |
| matrix.translate( |
| m, m, origin |
| ); |
| } |
| matrix.scale(m, m, this.scale); |
| if (origin[0] || origin[1]) { |
| origin[0] = -origin[0]; |
| origin[1] = -origin[1]; |
| matrix.translate( |
| m, m, origin |
| ); |
| } |
| } |
| |
| if (this.rotation) { |
| if (this.rotation instanceof Array) { |
| if (this.rotation[0] !== 0) { |
| origin[0] = -this.rotation[1] || 0; |
| origin[1] = -this.rotation[2] || 0; |
| if (origin[0] || origin[1]) { |
| matrix.translate( |
| m, m, origin |
| ); |
| } |
| matrix.rotate(m, m, this.rotation[0]); |
| if (origin[0] || origin[1]) { |
| origin[0] = -origin[0]; |
| origin[1] = -origin[1]; |
| matrix.translate( |
| m, m, origin |
| ); |
| } |
| } |
| } |
| else { |
| if (this.rotation !== 0) { |
| matrix.rotate(m, m, this.rotation); |
| } |
| } |
| } |
| |
| if (this.position && (this.position[0] !==0 || this.position[1] !== 0)) { |
| matrix.translate(m, m, this.position); |
| } |
| } |
| |
| // 保存这个变换矩阵 |
| this.transform = m; |
| |
| // 应用父节点变换 |
| if (this.parent && this.parent.needTransform) { |
| if (this.needLocalTransform) { |
| matrix.mul(this.transform, this.parent.transform, this.transform); |
| } else { |
| matrix.copy(this.transform, this.parent.transform); |
| } |
| } |
| }, |
| |
| setTransform: function(ctx) { |
| if (this.needTransform) { |
| var m = this.transform; |
| ctx.transform( |
| m[0], m[1], |
| m[2], m[3], |
| m[4], m[5] |
| ); |
| } |
| } |
| }; |
| |
| return Transformable; |
| }); |
| /** |
| * 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( 'zrender/tool/color',['require','../tool/util'],function(require) { |
| var util = require('../tool/util'); |
| |
| 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' |
| }; |
| |
| /** |
| * 自定义调色板 |
| */ |
| function customPalette(userPalete) { |
| palette = userPalete; |
| } |
| |
| /** |
| * 复位默认色板 |
| */ |
| function resetPalette() { |
| palette = _palette; |
| } |
| |
| /** |
| * 获取色板颜色 |
| * |
| * @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 customHighlight(userHighlightColor) { |
| highlightColor = userHighlightColor; |
| } |
| |
| /** |
| * 重置默认高亮颜色 |
| */ |
| function resetHighlight() { |
| _highlightColor = highlightColor; |
| } |
| |
| /** |
| * 获取默认高亮颜色 |
| */ |
| 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(); |
| } |
| 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]); |
| } |
| 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(); |
| } |
| 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]); |
| } |
| gradient.__nonRecursion = true; |
| return gradient; |
| } |
| |
| /** |
| * 获取两种颜色之间渐变颜色数组 |
| * @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; |
| } |
| |
| /** |
| * 获取指定级数的渐变颜色数组 |
| * @param {Array} colors 颜色组 |
| * @param {number=20} step 渐变级数 |
| * @return {Array} 颜色数组 |
| */ |
| function getGradientColors(colors, step) { |
| var ret = []; |
| var len = colors.length; |
| if (step === undefined) { |
| step = 20; |
| } |
| 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); |
| } |
| } |
| 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]; |
| } |
| |
| if (format.indexOf('a') > -1) { |
| if (data.length === 3) { |
| data.push(1); |
| } |
| data[3] = adjust(data[3], [0, 1]); |
| return format + '(' + data.slice(0, 4).join(',') + ')'; |
| } |
| |
| return format + '(' + data.slice(0, 3).join(',') + ')'; |
| } |
| } |
| |
| /** |
| * 返回颜色值数组 |
| * |
| * @param {color} color 颜色 |
| * @return {Array} 颜色值数组 |
| */ |
| function toArray(color) { |
| color = trim(color); |
| if (color.indexOf('rgba') < 0) { |
| color = toRGBA(color); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * 颜色格式转化 |
| * |
| * @param {Array} data 颜色值数组 |
| * @param {string} format 格式,默认rgb |
| * @return {string} 颜色 |
| */ |
| function convert(color, format) { |
| var data = getData(color); |
| var alpha = data[3]; |
| if(typeof alpha === 'undefined') { |
| alpha = 1; |
| } |
| |
| if (color.indexOf('hsb') > -1) { |
| data = _HSV_2_RGB(data); |
| } else if (color.indexOf('hsl') > -1) { |
| data = _HSL_2_RGB(data); |
| } |
| |
| 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); |
| } |
| |
| data[3] = alpha; |
| |
| return toColor(data, format); |
| } |
| |
| /** |
| * 转换为rgba格式的颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} rgba颜色,rgba(r,g,b,a) |
| */ |
| function toRGBA(color) { |
| return convert(color, 'rgba'); |
| } |
| |
| /** |
| * 转换为rgb数字格式的颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} rgb颜色,rgb(0,0,0)格式 |
| */ |
| function toRGB(color) { |
| return convert(color, 'rgb'); |
| } |
| |
| /** |
| * 转换为16进制颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} 16进制颜色,#rrggbb格式 |
| */ |
| function toHex(color) { |
| return convert(color, 'hex'); |
| } |
| |
| /** |
| * 转换为HSV颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} HSVA颜色,hsva(h,s,v,a) |
| */ |
| function toHSVA(color) { |
| return convert(color, 'hsva'); |
| } |
| |
| /** |
| * 转换为HSV颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} HSV颜色,hsv(h,s,v) |
| */ |
| function toHSV(color) { |
| return convert(color, 'hsv'); |
| } |
| |
| /** |
| * 转换为HSBA颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} HSBA颜色,hsba(h,s,b,a) |
| */ |
| function toHSBA(color) { |
| return convert(color, 'hsba'); |
| } |
| |
| /** |
| * 转换为HSB颜色 |
| * |
| * @param {string} color 颜色 |
| * @return {string} HSB颜色,hsb(h,s,b) |
| */ |
| function toHSB(color) { |
| return convert(color, 'hsb'); |
| } |
| |
| /** |
| * 转换为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; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 移除颜色中多余空格 |
| * |
| * @param {string} color 颜色 |
| * @return {string} 无空格颜色 |
| */ |
| function trim(color) { |
| return String(color).replace(/\s+/g, ''); |
| } |
| |
| /** |
| * 颜色规范化 |
| * |
| * @param {string} color 颜色 |
| * @return {string} 规范化后的颜色 |
| */ |
| function normalize(color) { |
| // 颜色名 |
| if (_nameColors[color]) { |
| color = _nameColors[color]; |
| } |
| // 去掉空格 |
| 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; |
| } |
| |
| /** |
| * 颜色加深或减淡,当level>0加深,当level<0减淡 |
| * |
| * @param {string} color 颜色 |
| * @param {number} level 升降程度,取值区间[-1,1] |
| * @return {string} 加深或减淡后颜色值 |
| */ |
| 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; |
| } |
| } |
| return 'rgb(' + data.join(',') + ')'; |
| } |
| |
| /** |
| * 颜色翻转,[255-r,255-g,255-b,1-a] |
| * |
| * @param {string} color 颜色 |
| * @return {string} 翻转颜色 |
| */ |
| function reverse(color) { |
| var data = getData(toRGBA(color)); |
| data = map(data, |
| function(c) { |
| return 255 - c; |
| }); |
| return toColor(data, 'rgb'); |
| } |
| |
| /** |
| * 简单两种颜色混合 |
| * |
| * @param {string} color1 第一种颜色 |
| * @param {string} color2 第二种颜色 |
| * @param {string} weight 混合权重[0-1] |
| * @return {string} 结果色,rgb(r,g,b)或rgba(r,g,b,a) |
| */ |
| function mix(color1, color2, weight) { |
| if(typeof weight === 'undefined') { |
| weight = 0.5; |
| } |
| 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'); |
| } |
| |
| /** |
| * 随机颜色 |
| * |
| * @return {string} 颜色值,#rrggbb格式 |
| */ |
| 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'); // 颜色格式错误 |
| } |
| 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]); |
| }); |
| |
| } |
| 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]); |
| } |
| ); |
| |
| if(typeof a !== 'undefined') { |
| data.push(adjust(parseFloat(a), [0, 1])); |
| } |
| } |
| 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; |
| } |
| |
| /** |
| * 设置颜色透明度 |
| * @param {string} color 颜色 |
| * @param {number} alpha 透明度,区间[0,1] |
| * @return {string} rgba颜色值 |
| */ |
| function alpha(color, a) { |
| if (a === null) { |
| a = 1; |
| } |
| var data = getData(toRGBA(color)); |
| data[3] = adjust(Number(a).toFixed(4), [0, 1]); |
| |
| return toColor(data, 'rgba'); |
| } |
| |
| // 数组映射 |
| 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; |
| } |
| |
| // 调整值区间 |
| function adjust(value, region) { |
| // < to <= & > to >= |
| // modify by linzhifeng 2014-05-25 because -0 == 0 |
| if (value <= region[0]) { |
| value = region[0]; |
| } |
| else if (value >= region[1]) { |
| value = region[1]; |
| } |
| return value; |
| } |
| |
| // 参见 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 ]; |
| } |
| |
| 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); |
| } |
| |
| var v1 = 2 * L - v2; |
| |
| 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 [ 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; |
| } |
| |
| 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; |
| } |
| } |
| 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; |
| } |
| } |
| |
| H = H * 360; |
| S = S * 100; |
| L = L * 100; |
| |
| return [ H, S, L ]; |
| } |
| |
| 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 |
| }; |
| }); |
| |
| |
| /** |
| * zrender : shape基类 |
| * |
| * desc: zrender是一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型。 |
| * author: Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| * |
| * 可配图形属性: |
| { |
| // 基础属性,详见各shape |
| shape : {string}, // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 变换 |
| position : {array}, // 默认为[0, 0], shape的坐标 |
| rotation : {number|array}, // 默认为[0, 0, 0],shape绕自身旋转的角度,不被translate 影响 |
| // 后两个值为旋转的origin |
| scale : {array}, // 默认为[1, 1, 0, 0], shape纵横缩放比例,不被translate影响 |
| // 后两个值为缩放的origin |
| |
| // 样式属性,详见各shape,默认状态样式属性 |
| style : {Object}, |
| |
| // 样式属性,详见各shape,高亮样式属性,当不存在highlightStyle时使用默认样式扩展显示 |
| highlightStyle : {Object}, |
| |
| // 交互属性,zrender支持,非图形类实现 |
| hoverable : {boolean}, // 默认为true,可悬浮响应,默认悬浮响应为高亮显示 |
| // 可在onbrush中捕获并阻塞高亮绘画 |
| clickable : {boolean}, // 默认为false,可点击响应,影响鼠标hover时图标是否为可点击样式 |
| // 为false则阻断点击事件抛出,为true可在onclick中捕获 |
| draggable : {boolean}, // 默认为false,可拖拽响应,默认拖拽响应改变图形位置, |
| // 可在ondrift中捕获并阻塞默认拖拽行为 |
| |
| // 事件属性 |
| onbrush : {Function}, // 默认为null,当前图形被刷画时回调,可用于实现自定义绘画 |
| // 回传参数为: |
| // @param {2D Context} context 当前canvas context |
| // @param {Object} shape 当前shape |
| // @param {boolean} isHighlight 是否高亮 |
| // @return {boolean} 回调返回true则不执行默认绘画 |
| ondrift : {Function}, // 默认为null,当前图形被拖拽改变位置时回调,可用于限制拖拽范围 |
| // 回传参数为: |
| // @param {Object} shape 当前shape |
| // @param {number} dx x方向变化 |
| // @param {number} dy y方向变化 |
| onclick : {Function}, // 默认为null,当前图形点击响应,回传参数为: |
| // @param {Object} eventPacket 对象内容如下: |
| // @param {string} eventPacket.type 事件类型,EVENT.CLICK |
| // @param {event} eventPacket.event 原始dom事件对象 |
| // @param {Object} eventPacket.target 当前图形shape对象 |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| onmousewheel : {Function}, // 默认为null,当前图形上鼠标滚轮触发,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.MOUSEWHEEL |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| onmousemove : {Function}, // 默认为null,当前图上形鼠标(或手指)移动触发,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.MOUSEMOVE |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| onmouseover : {Function}, // 默认为null,鼠标(或手指)移动到当前图形上触发,回传参数格式同onclick: |
| // 事件类型为confit.EVENT.MOUSEOVER |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| onmouseout : {Function}, // 默认为null,鼠标(或手指)从当前图形移开,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.MOUSEOUT |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| onmousedown : {Function}, // 默认为null,鼠标按钮(或手指)按下,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.MOUSEDOWN |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| onmouseup : {Function}, // 默认为null,鼠标按钮(或手指)松开,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.MOUSEUP |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| ondragstart : {Function}, // 默认为null,开始拖拽时触发,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.DRAGSTART |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| ondragend : {Function}, // 默认为null,拖拽完毕时触发,回传参数格式同onclick,其中: |
| // 事件类型为confit.EVENT.DRAGEND |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| ondragenter : {Function}, // 默认为null,拖拽图形元素进入目标图形元素时触发 |
| // 回传参数格式同onclick,其中: |
| // @param {string} eventPacket.type 事件类型,EVENT.DRAGENTER |
| // @param {Object} eventPacket.target 目标图形元素shape对象 |
| // @param {Object} eventPacket.dragged 拖拽图形元素shape对象 |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| ondragover : {Function}, // 默认为null,拖拽图形元素在目标图形元素上移动时触发, |
| // 回传参数格式同onclick,其中: |
| // @param {string} eventPacket.type 事件类型,EVENT.DRAGOVER |
| // @param {Object} eventPacket.target 目标图形元素shape对象 |
| // @param {Object} eventPacket.dragged 拖拽图形元素shape对象 |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| ondragleave : {Function}, // 默认为null,拖拽图形元素离开目标图形元素时触发, |
| // 回传参数格式同onclick,其中: |
| // @param {string} eventPacket.type 事件类型,EVENT.DRAGLEAVE |
| // @param {Object} eventPacket.target 目标图形元素shape对象 |
| // @param {Object} eventPacket.dragged 拖拽图形元素shape对象 |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| |
| ondrop : {Function}, // 默认为null,拖拽图形元素放在目标图形元素内时触发, |
| // 回传参数格式同onclick,其中: |
| // @param {string} eventPacket.type 事件类型,EVENT.DRAG |
| // @param {Object} eventPacket.target 目标图形元素shape对象 |
| // @param {Object} eventPacket.dragged 拖拽图形元素shape对象 |
| // @return {boolean} 回调返回true则阻止抛出全局事件 |
| } |
| */ |
| define( |
| 'zrender/shape/Base',['require','../tool/matrix','../tool/guid','../tool/util','./mixin/Transformable','../tool/event','../tool/area','../tool/area','../tool/color','../tool/area'],function(require) { |
| var matrix = require('../tool/matrix'); |
| var guid = require('../tool/guid'); |
| var util = require('../tool/util'); |
| |
| var Transformable = require('./mixin/Transformable'); |
| var Dispatcher = require('../tool/event').Dispatcher; |
| |
| function _fillText(ctx, text, x, y, textFont, textAlign, textBaseline) { |
| if (textFont) { |
| ctx.font = textFont; |
| } |
| ctx.textAlign = textAlign; |
| ctx.textBaseline = textBaseline; |
| var rect = _getTextRect( |
| text, x, y, textFont, textAlign, textBaseline |
| ); |
| |
| text = (text + '').split('\n'); |
| var lineHeight = require('../tool/area').getTextHeight('国', textFont); |
| |
| switch (textBaseline) { |
| case 'top': |
| y = rect.y; |
| break; |
| case 'bottom': |
| y = rect.y + lineHeight; |
| break; |
| default: |
| y = rect.y + lineHeight / 2; |
| } |
| |
| for (var i = 0, l = text.length; i < l; i++) { |
| ctx.fillText(text[i], x, y); |
| y += lineHeight; |
| } |
| } |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * |
| * @inner |
| * @param {Object} style |
| */ |
| function _getTextRect(text, x, y, textFont, textAlign, textBaseline) { |
| var area = require('../tool/area'); |
| var width = area.getTextWidth(text, textFont); |
| var lineHeight = area.getTextHeight('国', textFont); |
| |
| text = (text + '').split('\n'); |
| |
| switch (textAlign) { |
| case 'end': |
| case 'right': |
| x -= width; |
| break; |
| case 'center': |
| x -= (width / 2); |
| break; |
| } |
| |
| switch (textBaseline) { |
| case 'top': |
| break; |
| case 'bottom': |
| y -= lineHeight * text.length; |
| break; |
| default: |
| y -= lineHeight * text.length / 2; |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : width, |
| height : lineHeight * text.length |
| }; |
| } |
| |
| function Base( options ) { |
| |
| options = options || {}; |
| |
| this.id = options.id || guid(); |
| this.zlevel = 0; |
| this.draggable = false; |
| this.clickable = false; |
| this.hoverable = true; |
| |
| for ( var key in options ) { |
| this[ key ] = options[ key ]; |
| } |
| |
| this.style = this.style || {}; |
| |
| this.parent = null; |
| |
| this.__dirty = true; |
| |
| Transformable.call(this); |
| Dispatcher.call(this); |
| } |
| |
| /** |
| * 画刷 |
| * |
| * @param ctx 画布句柄 |
| * @param isHighlight 是否为高亮状态 |
| * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e) |
| * 让painter更新视图,base.brush没用,需要的话重载brush |
| */ |
| Base.prototype.brush = function (ctx, isHighlight) { |
| var style = this.style; |
| |
| if (this.brushTypeOnly) { |
| style.brushType = this.brushTypeOnly; |
| } |
| |
| if (isHighlight) { |
| // 根据style扩展默认高亮样式 |
| style = this.getHighlightStyle( |
| style, |
| this.highlightStyle || {}, |
| this.brushTypeOnly |
| ); |
| } |
| |
| if (this.brushTypeOnly == 'stroke') { |
| style.strokeColor = style.strokeColor || style.color; |
| } |
| |
| ctx.save(); |
| |
| this.setContext(ctx, style); |
| |
| // 设置transform |
| this.setTransform(ctx); |
| |
| ctx.beginPath(); |
| this.buildPath(ctx, style); |
| if (this.brushTypeOnly != 'stroke') { |
| ctx.closePath(); |
| } |
| |
| switch (style.brushType) { |
| case 'both': |
| ctx.fill(); |
| case 'stroke': |
| style.lineWidth > 0 && ctx.stroke(); |
| break; |
| default: |
| ctx.fill(); |
| } |
| |
| this.drawText(ctx, style, this.style); |
| |
| ctx.restore(); |
| }; |
| |
| var STYLE_CTX_MAP = [ |
| ['color', 'fillStyle'], |
| ['strokeColor', 'strokeStyle'], |
| ['opacity', 'globalAlpha'], |
| ['lineCap', 'lineCap'], |
| ['lineJoin', 'lineJoin'], |
| ['miterLimit', 'miterLimit'], |
| ['lineWidth', 'lineWidth'], |
| ['shadowBlur', 'shadowBlur'], |
| ['shadowColor', 'shadowColor'], |
| ['shadowOffsetX', 'shadowOffsetX'], |
| ['shadowOffsetY', 'shadowOffsetY'] |
| ]; |
| |
| /** |
| * 画布通用设置 |
| * |
| * TODO Performance |
| * |
| * @param ctx 画布句柄 |
| * @param style 通用样式 |
| */ |
| Base.prototype.setContext = function (ctx, style) { |
| for (var i = 0, len = STYLE_CTX_MAP.length; i < len; i++) { |
| var styleProp = STYLE_CTX_MAP[i][0]; |
| var styleValue = style[styleProp]; |
| var ctxProp = STYLE_CTX_MAP[i][1]; |
| |
| if (typeof styleValue != 'undefined') { |
| ctx[ctxProp] = styleValue; |
| } |
| } |
| }; |
| |
| /** |
| * 根据默认样式扩展高亮样式 |
| * |
| * @param ctx Canvas 2D上下文 |
| * @param {Object} style 默认样式 |
| * @param {Object} highlightStyle 高亮样式 |
| */ |
| Base.prototype.getHighlightStyle = function (style, highlightStyle, brushTypeOnly) { |
| var newStyle = {}; |
| for (var k in style) { |
| newStyle[k] = style[k]; |
| } |
| |
| var color = require('../tool/color'); |
| var highlightColor = color.getHighlightColor(); |
| // 根据highlightStyle扩展 |
| if (style.brushType != 'stroke') { |
| // 带填充则用高亮色加粗边线 |
| newStyle.strokeColor = highlightColor; |
| newStyle.lineWidth = (style.lineWidth || 1) |
| + this.getHighlightZoom(); |
| newStyle.brushType = 'both'; |
| } |
| else { |
| if (brushTypeOnly != 'stroke') { |
| // 描边型的则用原色加工高亮 |
| newStyle.strokeColor = highlightColor; |
| newStyle.lineWidth = (style.lineWidth || 1) |
| + this.getHighlightZoom(); |
| } |
| else { |
| // 线型的则用原色加工高亮 |
| newStyle.strokeColor = highlightStyle.strokeColor |
| || color.mix( |
| style.strokeColor, |
| color.toRGB(highlightColor) |
| ); |
| } |
| } |
| |
| // 可自定义覆盖默认值 |
| for (var k in highlightStyle) { |
| if (typeof highlightStyle[k] != 'undefined') { |
| newStyle[k] = highlightStyle[k]; |
| } |
| } |
| |
| return newStyle; |
| }; |
| |
| /** |
| * 高亮放大效果参数 |
| * 当前统一设置为6,如有需要差异设置,通过this.type判断实例类型 |
| */ |
| Base.prototype.getHighlightZoom = function () { |
| return this.type != 'text' ? 6 : 2; |
| }; |
| |
| /** |
| * 默认漂移 |
| * |
| * @param dx 横坐标变化 |
| * @param dy 纵坐标变化 |
| */ |
| Base.prototype.drift = function (dx, dy) { |
| this.position[0] += dx; |
| this.position[1] += dy; |
| }; |
| |
| /** |
| * 获取鼠标坐标变换 |
| * TODO Performance |
| */ |
| Base.prototype.getTansform = (function() { |
| |
| var invTransform = []; |
| |
| return function (x, y) { |
| var originPos = [x, y]; |
| // 对鼠标的坐标也做相同的变换 |
| if (this.needTransform && this.transform) { |
| matrix.invert(invTransform, this.transform); |
| |
| matrix.mulVector(originPos, invTransform, [x, y, 1]); |
| |
| if (x == originPos[0] && y == originPos[1]) { |
| // 避免外部修改导致的needTransform不准确 |
| this.updateNeedTransform(); |
| } |
| } |
| return originPos; |
| }; |
| })(); |
| |
| /** |
| * 默认区域包含判断 |
| * |
| * @param x 横坐标 |
| * @param y 纵坐标 |
| */ |
| Base.prototype.isCover = function (x, y) { |
| var originPos = this.getTansform(x, y); |
| x = originPos[0]; |
| y = originPos[1]; |
| |
| // 快速预判并保留判断矩形 |
| var rect = this.style.__rect; |
| if (!rect) { |
| rect = this.style.__rect = this.getRect(this.style); |
| } |
| |
| if (x >= rect.x |
| && x <= (rect.x + rect.width) |
| && y >= rect.y |
| && y <= (rect.y + rect.height) |
| ) { |
| // 矩形内 |
| return require('../tool/area').isInside(this, this.style, x, y); |
| } |
| |
| return false; |
| }; |
| |
| /** |
| * 附加文本 |
| * |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| * @param {Object} normalStyle 默认样式,用于定位文字显示 |
| */ |
| Base.prototype.drawText = function (ctx, style, normalStyle) { |
| if (typeof(style.text) == 'undefined' || style.text === false ) { |
| return; |
| } |
| // 字体颜色策略 |
| var textColor = style.textColor || style.color || style.strokeColor; |
| ctx.fillStyle = textColor; |
| |
| /* |
| if (style.textPosition == 'inside') { |
| ctx.shadowColor = 'rgba(0,0,0,0)'; // 内部文字不带shadowColor |
| } |
| */ |
| |
| // 文本与图形间空白间隙 |
| var dd = 10; |
| var al; // 文本水平对齐 |
| var bl; // 文本垂直对齐 |
| var tx; // 文本横坐标 |
| var ty; // 文本纵坐标 |
| |
| var textPosition = style.textPosition // 用户定义 |
| || this.textPosition // shape默认 |
| || 'top'; // 全局默认 |
| |
| switch (textPosition) { |
| case 'inside': |
| case 'top': |
| case 'bottom': |
| case 'left': |
| case 'right': |
| if (this.getRect) { |
| var rect = (normalStyle || style).__rect |
| || this.getRect(normalStyle || style); |
| |
| switch (textPosition) { |
| case 'inside': |
| tx = rect.x + rect.width / 2; |
| ty = rect.y + rect.height / 2; |
| al = 'center'; |
| bl = 'middle'; |
| if (style.brushType != 'stroke' |
| && textColor == style.color |
| ) { |
| ctx.fillStyle = '#fff'; |
| } |
| break; |
| case 'left': |
| tx = rect.x - dd; |
| ty = rect.y + rect.height / 2; |
| al = 'end'; |
| bl = 'middle'; |
| break; |
| case 'right': |
| tx = rect.x + rect.width + dd; |
| ty = rect.y + rect.height / 2; |
| al = 'start'; |
| bl = 'middle'; |
| break; |
| case 'top': |
| tx = rect.x + rect.width / 2; |
| ty = rect.y - dd; |
| al = 'center'; |
| bl = 'bottom'; |
| break; |
| case 'bottom': |
| tx = rect.x + rect.width / 2; |
| ty = rect.y + rect.height + dd; |
| al = 'center'; |
| bl = 'top'; |
| break; |
| } |
| } |
| break; |
| case 'start': |
| case 'end': |
| var xStart; |
| var xEnd; |
| var yStart; |
| var yEnd; |
| if (typeof style.pointList != 'undefined') { |
| var pointList = style.pointList; |
| if (pointList.length < 2) { |
| // 少于2个点就不画了~ |
| return; |
| } |
| var length = pointList.length; |
| switch (textPosition) { |
| case 'start': |
| xStart = pointList[0][0]; |
| xEnd = pointList[1][0]; |
| yStart = pointList[0][1]; |
| yEnd = pointList[1][1]; |
| break; |
| case 'end': |
| xStart = pointList[length - 2][0]; |
| xEnd = pointList[length - 1][0]; |
| yStart = pointList[length - 2][1]; |
| yEnd = pointList[length - 1][1]; |
| break; |
| } |
| } |
| else { |
| xStart = style.xStart || 0; |
| xEnd = style.xEnd || 0; |
| yStart = style.yStart || 0; |
| yEnd = style.yEnd || 0; |
| } |
| |
| switch (textPosition) { |
| case 'start': |
| al = xStart < xEnd ? 'end' : 'start'; |
| bl = yStart < yEnd ? 'bottom' : 'top'; |
| tx = xStart; |
| ty = yStart; |
| break; |
| case 'end': |
| al = xStart < xEnd ? 'start' : 'end'; |
| bl = yStart < yEnd ? 'top' : 'bottom'; |
| tx = xEnd; |
| ty = yEnd; |
| break; |
| } |
| dd -= 4; |
| if (xStart != xEnd) { |
| tx -= (al == 'end' ? dd : -dd); |
| } |
| else { |
| al = 'center'; |
| } |
| |
| if (yStart != yEnd) { |
| ty -= (bl == 'bottom' ? dd : -dd); |
| } |
| else { |
| bl = 'middle'; |
| } |
| break; |
| case 'specific': |
| tx = style.textX || 0; |
| ty = style.textY || 0; |
| al = 'start'; |
| bl = 'middle'; |
| break; |
| } |
| |
| if (tx != null && ty != null) { |
| _fillText( |
| ctx, |
| style.text, |
| tx, ty, |
| style.textFont, |
| style.textAlign || al, |
| style.textBaseline || bl |
| ); |
| } |
| }; |
| // TODO |
| Base.prototype.isSilent = function () { |
| return !( |
| this.hoverable || this.draggable |
| || this.onmousemove || this.onmouseover || this.onmouseout |
| || this.onmousedown || this.onmouseup || this.onclick |
| || this.ondragenter || this.ondragover || this.ondragleave |
| || this.ondrop |
| ); |
| }; |
| |
| util.merge(Base.prototype, Transformable.prototype, true); |
| util.merge(Base.prototype, Dispatcher.prototype, true); |
| |
| return Base; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * author: CrossDo (chenhuaimu@baidu.com) |
| * |
| * shape类:路径 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'path', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| path : {string},// 必须,路径。例如:M 0 0 L 0 10 L 10 10 Z (一个三角形) |
| //M = moveto |
| //L = lineto |
| //H = horizontal lineto |
| //V = vertical lineto |
| //C = curveto |
| //S = smooth curveto |
| //Q = quadratic Belzier curve |
| //T = smooth quadratic Belzier curveto |
| //Z = closepath |
| |
| |
| x : {number}, // 必须,x轴坐标 |
| y : {number}, // 必须,y轴坐标 |
| |
| |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为top,附加文本位置。 |
| // inside | left | right | top | bottom |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| |
| **/ |
| |
| define('zrender/shape/Path',['require','./Base','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| |
| function Path(options) { |
| Base.call(this, options); |
| } |
| |
| Path.prototype = { |
| type: 'path', |
| |
| _parsePathData : function(data) { |
| if (!data) { |
| return []; |
| } |
| |
| // command string |
| var cs = data; |
| |
| // command chars |
| var cc = [ |
| 'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', |
| 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A' |
| ]; |
| |
| cs = cs.replace(/-/g, ' -'); |
| cs = cs.replace(/ /g, ' '); |
| cs = cs.replace(/ /g, ','); |
| cs = cs.replace(/,,/g, ','); |
| |
| |
| var n; |
| // create pipes so that we can split the data |
| for (n = 0; n < cc.length; n++) { |
| cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); |
| } |
| |
| // create array |
| var arr = cs.split('|'); |
| var ca = []; |
| // init context point |
| var cpx = 0; |
| var cpy = 0; |
| for (n = 1; n < arr.length; n++) { |
| var str = arr[n]; |
| var c = str.charAt(0); |
| str = str.slice(1); |
| str = str.replace(new RegExp('e,-', 'g'), 'e-'); |
| |
| var p = str.split(','); |
| if (p.length > 0 && p[0] === '') { |
| p.shift(); |
| } |
| |
| for (var i = 0; i < p.length; i++) { |
| p[i] = parseFloat(p[i]); |
| } |
| while (p.length > 0) { |
| if (isNaN(p[0])) { |
| break; |
| } |
| var cmd = null; |
| var points = []; |
| |
| var ctlPtx; |
| var ctlPty; |
| var prevCmd; |
| |
| var rx; |
| var ry; |
| var psi; |
| var fa; |
| var fs; |
| |
| var x1 = cpx; |
| var y1 = cpy; |
| |
| // convert l, H, h, V, and v to L |
| switch (c) { |
| case 'l': |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'L'; |
| points.push(cpx, cpy); |
| break; |
| case 'L': |
| cpx = p.shift(); |
| cpy = p.shift(); |
| points.push(cpx, cpy); |
| break; |
| case 'm': |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'M'; |
| points.push(cpx, cpy); |
| c = 'l'; |
| break; |
| case 'M': |
| cpx = p.shift(); |
| cpy = p.shift(); |
| cmd = 'M'; |
| points.push(cpx, cpy); |
| c = 'L'; |
| break; |
| |
| case 'h': |
| cpx += p.shift(); |
| cmd = 'L'; |
| points.push(cpx, cpy); |
| break; |
| case 'H': |
| cpx = p.shift(); |
| cmd = 'L'; |
| points.push(cpx, cpy); |
| break; |
| case 'v': |
| cpy += p.shift(); |
| cmd = 'L'; |
| points.push(cpx, cpy); |
| break; |
| case 'V': |
| cpy = p.shift(); |
| cmd = 'L'; |
| points.push(cpx, cpy); |
| break; |
| case 'C': |
| points.push(p.shift(), p.shift(), p.shift(), p.shift()); |
| cpx = p.shift(); |
| cpy = p.shift(); |
| points.push(cpx, cpy); |
| break; |
| case 'c': |
| points.push( |
| cpx + p.shift(), cpy + p.shift(), |
| cpx + p.shift(), cpy + p.shift() |
| ); |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'C'; |
| points.push(cpx, cpy); |
| break; |
| case 'S': |
| ctlPtx = cpx; |
| ctlPty = cpy; |
| prevCmd = ca[ca.length - 1]; |
| if (prevCmd.command === 'C') { |
| ctlPtx = cpx + (cpx - prevCmd.points[2]); |
| ctlPty = cpy + (cpy - prevCmd.points[3]); |
| } |
| points.push(ctlPtx, ctlPty, p.shift(), p.shift()); |
| cpx = p.shift(); |
| cpy = p.shift(); |
| cmd = 'C'; |
| points.push(cpx, cpy); |
| break; |
| case 's': |
| ctlPtx = cpx, ctlPty = cpy; |
| prevCmd = ca[ca.length - 1]; |
| if (prevCmd.command === 'C') { |
| ctlPtx = cpx + (cpx - prevCmd.points[2]); |
| ctlPty = cpy + (cpy - prevCmd.points[3]); |
| } |
| points.push( |
| ctlPtx, ctlPty, |
| cpx + p.shift(), cpy + p.shift() |
| ); |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'C'; |
| points.push(cpx, cpy); |
| break; |
| case 'Q': |
| points.push(p.shift(), p.shift()); |
| cpx = p.shift(); |
| cpy = p.shift(); |
| points.push(cpx, cpy); |
| break; |
| case 'q': |
| points.push(cpx + p.shift(), cpy + p.shift()); |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'Q'; |
| points.push(cpx, cpy); |
| break; |
| case 'T': |
| ctlPtx = cpx, ctlPty = cpy; |
| prevCmd = ca[ca.length - 1]; |
| if (prevCmd.command === 'Q') { |
| ctlPtx = cpx + (cpx - prevCmd.points[0]); |
| ctlPty = cpy + (cpy - prevCmd.points[1]); |
| } |
| cpx = p.shift(); |
| cpy = p.shift(); |
| cmd = 'Q'; |
| points.push(ctlPtx, ctlPty, cpx, cpy); |
| break; |
| case 't': |
| ctlPtx = cpx, ctlPty = cpy; |
| prevCmd = ca[ca.length - 1]; |
| if (prevCmd.command === 'Q') { |
| ctlPtx = cpx + (cpx - prevCmd.points[0]); |
| ctlPty = cpy + (cpy - prevCmd.points[1]); |
| } |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'Q'; |
| points.push(ctlPtx, ctlPty, cpx, cpy); |
| break; |
| case 'A': |
| rx = p.shift(); |
| ry = p.shift(); |
| psi = p.shift(); |
| fa = p.shift(); |
| fs = p.shift(); |
| |
| x1 = cpx, y1 = cpy; |
| cpx = p.shift(), cpy = p.shift(); |
| cmd = 'A'; |
| points = this._convertPoint( |
| x1, y1, cpx, cpy, fa, fs, rx, ry, psi |
| ); |
| break; |
| case 'a': |
| rx = p.shift(); |
| ry = p.shift(); |
| psi = p.shift(); |
| fa = p.shift(); |
| fs = p.shift(); |
| |
| x1 = cpx, y1 = cpy; |
| cpx += p.shift(); |
| cpy += p.shift(); |
| cmd = 'A'; |
| points = this._convertPoint( |
| x1, y1, cpx, cpy, fa, fs, rx, ry, psi |
| ); |
| break; |
| |
| } |
| |
| ca.push({ |
| command : cmd || c, |
| points : points |
| }); |
| } |
| |
| if (c === 'z' || c === 'Z') { |
| ca.push({ |
| command : 'z', |
| points : [] |
| }); |
| } |
| } |
| |
| return ca; |
| |
| }, |
| |
| _convertPoint : function(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) { |
| var psi = psiDeg * (Math.PI / 180.0); |
| var xp = Math.cos(psi) * (x1 - x2) / 2.0 |
| + Math.sin(psi) * (y1 - y2) / 2.0; |
| var yp = -1 * Math.sin(psi) * (x1 - x2) / 2.0 |
| + Math.cos(psi) * (y1 - y2) / 2.0; |
| |
| var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); |
| |
| if (lambda > 1) { |
| rx *= Math.sqrt(lambda); |
| ry *= Math.sqrt(lambda); |
| } |
| |
| var f = Math.sqrt((((rx * rx) * (ry * ry)) |
| - ((rx * rx) * (yp * yp)) |
| - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp) |
| + (ry * ry) * (xp * xp)) |
| ); |
| |
| if (fa === fs) { |
| f *= -1; |
| } |
| if (isNaN(f)) { |
| f = 0; |
| } |
| |
| var cxp = f * rx * yp / ry; |
| var cyp = f * -ry * xp / rx; |
| |
| var cx = (x1 + x2) / 2.0 |
| + Math.cos(psi) * cxp |
| - Math.sin(psi) * cyp; |
| var cy = (y1 + y2) / 2.0 |
| + Math.sin(psi) * cxp |
| + Math.cos(psi) * cyp; |
| |
| var vMag = function(v) { |
| return Math.sqrt(v[0] * v[0] + v[1] * v[1]); |
| }; |
| var vRatio = function(u, v) { |
| return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); |
| }; |
| var vAngle = function(u, v) { |
| return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) |
| * Math.acos(vRatio(u, v)); |
| }; |
| var theta = vAngle([ 1, 0 ], [ (xp - cxp) / rx, (yp - cyp) / ry ]); |
| var u = [ (xp - cxp) / rx, (yp - cyp) / ry ]; |
| var v = [ (-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry ]; |
| var dTheta = vAngle(u, v); |
| |
| if (vRatio(u, v) <= -1) { |
| dTheta = Math.PI; |
| } |
| if (vRatio(u, v) >= 1) { |
| dTheta = 0; |
| } |
| if (fs === 0 && dTheta > 0) { |
| dTheta = dTheta - 2 * Math.PI; |
| } |
| if (fs === 1 && dTheta < 0) { |
| dTheta = dTheta + 2 * Math.PI; |
| } |
| return [ cx, cy, rx, ry, theta, dTheta, psi, fs ]; |
| }, |
| |
| /** |
| * 创建路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| var path = style.path; |
| |
| var pathArray = this.pathArray || this._parsePathData(path); |
| |
| // 平移坐标 |
| var x = style.x || 0; |
| var y = style.y || 0; |
| |
| var p; |
| // 记录边界点,用于判断inside |
| var pointList = style.pointList = []; |
| var singlePointList = []; |
| for (var i = 0, l = pathArray.length; i < l; i++) { |
| if (pathArray[i].command.toUpperCase() == 'M') { |
| singlePointList.length > 0 |
| && pointList.push(singlePointList); |
| singlePointList = []; |
| } |
| p = pathArray[i].points; |
| for (var j = 0, k = p.length; j < k; j += 2) { |
| singlePointList.push([p[j] + x, p[j+1] + y]); |
| } |
| } |
| singlePointList.length > 0 && pointList.push(singlePointList); |
| |
| var c; |
| for (var i = 0, l = pathArray.length; i < l; i++) { |
| c = pathArray[i].command; |
| p = pathArray[i].points; |
| // 平移变换 |
| for (var j = 0, k = p.length; j < k; j++) { |
| if (j % 2 === 0) { |
| p[j] += x; |
| } else { |
| p[j] += y; |
| } |
| } |
| switch (c) { |
| case 'L': |
| ctx.lineTo(p[0], p[1]); |
| break; |
| case 'M': |
| ctx.moveTo(p[0], p[1]); |
| break; |
| case 'C': |
| ctx.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); |
| break; |
| case 'Q': |
| ctx.quadraticCurveTo(p[0], p[1], p[2], p[3]); |
| break; |
| case 'A': |
| var cx = p[0]; |
| var cy = p[1]; |
| var rx = p[2]; |
| var ry = p[3]; |
| var theta = p[4]; |
| var dTheta = p[5]; |
| var psi = p[6]; |
| var fs = p[7]; |
| var r = (rx > ry) ? rx : ry; |
| var scaleX = (rx > ry) ? 1 : rx / ry; |
| var scaleY = (rx > ry) ? ry / rx : 1; |
| |
| ctx.translate(cx, cy); |
| ctx.rotate(psi); |
| ctx.scale(scaleX, scaleY); |
| ctx.arc(0, 0, r, theta, theta + dTheta, 1 - fs); |
| ctx.scale(1 / scaleX, 1 / scaleY); |
| ctx.rotate(-psi); |
| ctx.translate(-cx, -cy); |
| break; |
| case 'z': |
| ctx.closePath(); |
| break; |
| } |
| } |
| |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style 样式 |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| |
| var minX = Number.MAX_VALUE; |
| var maxX = Number.MIN_VALUE; |
| |
| var minY = Number.MAX_VALUE; |
| var maxY = Number.MIN_VALUE; |
| |
| // 平移坐标 |
| var x = style.x || 0; |
| var y = style.y || 0; |
| |
| var pathArray = this.pathArray || this._parsePathData(style.path); |
| for (var i = 0; i < pathArray.length; i++) { |
| var p = pathArray[i].points; |
| |
| for (var j = 0; j < p.length; j++) { |
| if (j % 2 === 0) { |
| if (p[j] + x < minX) { |
| minX = p[j] + x; |
| } |
| if (p[j] + x > maxX) { |
| maxX = p[j] + x; |
| } |
| } |
| else { |
| if (p[j] + y < minY) { |
| minY = p[j] + y; |
| } |
| if (p[j] + y > maxY) { |
| maxY = p[j] + y; |
| } |
| } |
| } |
| } |
| |
| var rect; |
| if (minX === Number.MAX_VALUE |
| || maxX === Number.MIN_VALUE |
| || minY === Number.MAX_VALUE |
| || maxY === Number.MIN_VALUE |
| ) { |
| rect = { |
| x : 0, |
| y : 0, |
| width : 0, |
| height : 0 |
| }; |
| } |
| else { |
| rect = { |
| x : Math.round(minX - lineWidth / 2), |
| y : Math.round(minY - lineWidth / 2), |
| width : maxX - minX + lineWidth, |
| height : maxY - minY + lineWidth |
| }; |
| } |
| style.__rect = rect; |
| return rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Path, Base); |
| return Path; |
| }); |
| /** |
| * zrender: 图形空间辅助类 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * isInside:是否在区域内部 |
| * isOutside:是否在区域外部 |
| * getTextWidth:测算单行文本宽度 |
| */ |
| define( |
| 'zrender/tool/area',['require','../tool/util','../shape/Path'],function(require) { |
| var util = require('../tool/util'); |
| |
| var _ctx; |
| |
| var _textWidthCache = {}; |
| var _textHeightCache = {}; |
| var _textWidthCacheCounter = 0; |
| var _textHeightCacheCounter = 0; |
| var TEXT_CACHE_MAX = 20000; |
| |
| /** |
| * 包含判断 |
| * |
| * @param {Object} shape : 图形 |
| * @param {Object} area : 目标区域 |
| * @param {number} x : 横坐标 |
| * @param {number} y : 纵坐标 |
| */ |
| function isInside(shape, area, x, y) { |
| if (!area || !shape) { |
| // 无参数或不支持类型 |
| return false; |
| } |
| var zoneType = shape.type; |
| |
| _ctx = _ctx || util.getContext(); |
| |
| if (!_isInsideRectangle(area.__rect || shape.getRect(area), x, y)) { |
| // 不在矩形区域内直接返回false |
| return false; |
| } |
| |
| // 未实现或不可用时(excanvas不支持)则数学运算,主要是line,brokenLine,ring |
| var _mathReturn = _mathMethod(zoneType, area, x, y); |
| if (typeof _mathReturn != 'undefined') { |
| return _mathReturn; |
| } |
| |
| if (zoneType != 'bezier-curve' |
| && shape.buildPath |
| && _ctx.isPointInPath |
| ) { |
| return _buildPathMethod(shape, _ctx, area, x, y); |
| } |
| else if (_ctx.getImageData) { |
| return _pixelMethod(shape, area, x, y); |
| } |
| |
| // 上面的方法都行不通时 |
| switch (zoneType) { |
| case 'heart': //心形---------10 // Todo,不精确 |
| case 'droplet':// 水滴----------11 // Todo,不精确 |
| case 'ellipse': // Todo,不精确 |
| return true; |
| // 旋轮曲线 不准确 |
| case 'trochoid': |
| var _r = area.location == 'out' |
| ? area.r1 + area.r2 + area.d |
| : area.r1 - area.r2 + area.d; |
| return _isInsideCircle(area, x, y, _r); |
| // 玫瑰线 不准确 |
| case 'rose' : |
| return _isInsideCircle(area, x, y, area.maxr); |
| //路径,椭圆,曲线等-----------------13 |
| default: |
| return false; // Todo,暂不支持 |
| } |
| } |
| |
| /** |
| * 用数学方法判断,三个方法中最快,但是支持的shape少 |
| * |
| * @param {string} zoneType : 图形类型 |
| * @param {Object} area :目标区域 |
| * @param {number} x : 横坐标 |
| * @param {number} y : 纵坐标 |
| * @return {boolean=} true表示坐标处在图形中 |
| */ |
| function _mathMethod(zoneType, area, x, y) { |
| // 在矩形内则部分图形需要进一步判断 |
| switch (zoneType) { |
| //线-----------------------1 |
| case 'line': |
| return _isInsideLine(area, x, y); |
| //折线----------------------2 |
| case 'broken-line': |
| return _isInsideBrokenLine(area, x, y); |
| //文本----------------------3 |
| case 'text': |
| return true; |
| //圆环----------------------4 |
| case 'ring': |
| return _isInsideRing(area, x, y); |
| //矩形----------------------5 |
| case 'rectangle': |
| return true; |
| //圆形----------------------6 |
| case 'circle': |
| return _isInsideCircle(area, x, y, area.r); |
| //扇形----------------------7 |
| case 'sector': |
| return _isInsideSector(area, x, y); |
| //多边形---------------------8 |
| case 'path': |
| return _isInsidePath(area, x, y); |
| case 'polygon': |
| case 'star': |
| case 'isogon': |
| return _isInsidePolygon(area, x, y); |
| //图片----------------------9 |
| case 'image': |
| return true; |
| } |
| } |
| |
| /** |
| * 通过buildPath方法来判断,三个方法中较快,但是不支持线条类型的shape, |
| * 而且excanvas不支持isPointInPath方法 |
| * |
| * @param {Object} shape : shape |
| * @param {Object} context : 上下文 |
| * @param {Object} area :目标区域 |
| * @param {number} x : 横坐标 |
| * @param {number} y : 纵坐标 |
| * @return {boolean} true表示坐标处在图形中 |
| */ |
| function _buildPathMethod(shape, context, area, x, y) { |
| // 图形类实现路径创建了则用类的path |
| context.beginPath(); |
| shape.buildPath(context, area); |
| context.closePath(); |
| return context.isPointInPath(x, y); |
| } |
| |
| /** |
| * 通过像素值来判断,三个方法中最慢,但是支持广,不足之处是excanvas不支持像素处理 |
| * |
| * @param {Object} shape shape类 |
| * @param {Object} area 目标区域 |
| * @param {number} x 横坐标 |
| * @param {number} y 纵坐标 |
| * @return {boolean} true表示坐标处在图形中 |
| */ |
| function _pixelMethod(shape, area, x, y) { |
| var _rect = area.__rect || shape.getRect(area); |
| var _context = util.getPixelContext(); |
| var _offset = util.getPixelOffset(); |
| |
| util.adjustCanvasSize(x, y); |
| _context.clearRect(_rect.x, _rect.y, _rect.width, _rect.height); |
| _context.beginPath(); |
| shape.brush(_context, {style : area}); |
| _context.closePath(); |
| |
| return _isPainted(_context, x + _offset.x, y + _offset.y); |
| } |
| |
| /** |
| * 坐标像素值,判断坐标是否被作色 |
| * |
| * @param {Object} context : 上下文 |
| * @param {number} x : 横坐标 |
| * @param {number} y : 纵坐标 |
| * @param {number=} unit : 触发的精度,越大越容易触发,可选,缺省是为1 |
| * @return {boolean} 已经被画过返回true |
| */ |
| function _isPainted(context, x, y, unit) { |
| var pixelsData; |
| |
| if (typeof unit != 'undefined') { |
| unit = (unit || 1 ) >> 1; |
| pixelsData = context.getImageData( |
| x - unit, |
| y - unit, |
| unit + unit, |
| unit + unit |
| ).data; |
| } |
| else { |
| pixelsData = context.getImageData(x, y, 1, 1).data; |
| } |
| |
| var len = pixelsData.length; |
| while (len--) { |
| if (pixelsData[len] !== 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * !isInside |
| */ |
| function isOutside(shape, area, x, y) { |
| return !isInside(shape, area, x, y); |
| } |
| |
| /** |
| * 线段包含判断 |
| */ |
| function _isInsideLine(area, x, y) { |
| var _x1 = area.xStart; |
| var _y1 = area.yStart; |
| var _x2 = area.xEnd; |
| var _y2 = area.yEnd; |
| var _l = Math.max(area.lineWidth, 5); |
| var _a = 0; |
| var _b = _x1; |
| |
| var minX, maxX; |
| if (_x1 < _x2) { |
| minX = _x1 - _l; maxX = _x2 + _l; |
| } else { |
| minX = _x2 - _l; maxX = _x1 + _l; |
| } |
| |
| var minY, maxY; |
| if (_y1 < _y2) { |
| minY = _y1 - _l; maxY = _y2 + _l; |
| } else { |
| minY = _y2 - _l; maxY = _y1 + _l; |
| } |
| |
| if (x < minX || x > maxX || y < minY || y > maxY) { |
| return false; |
| } |
| |
| if (_x1 !== _x2) { |
| _a = (_y1 - _y2) / (_x1 - _x2); |
| _b = (_x1 * _y2 - _x2 * _y1) / (_x1 - _x2) ; |
| } |
| else { |
| return Math.abs(x - _x1) <= _l / 2; |
| } |
| |
| var _s = (_a * x - y + _b) * (_a * x - y + _b) / (_a * _a + 1); |
| return _s <= _l / 2 * _l / 2; |
| } |
| |
| function _isInsideBrokenLine(area, x, y) { |
| var pointList = area.pointList; |
| var lineArea = { |
| xStart : 0, |
| yStart : 0, |
| xEnd : 0, |
| yEnd : 0, |
| lineWidth : 0 |
| }; |
| for (var i = 0, l = pointList.length - 1; i < l; i++) { |
| lineArea.xStart = pointList[i][0]; |
| lineArea.yStart = pointList[i][1]; |
| lineArea.xEnd = pointList[i + 1][0]; |
| lineArea.yEnd = pointList[i + 1][1]; |
| lineArea.lineWidth = Math.max(area.lineWidth, 10); |
| |
| if (_isInsideLine(lineArea, x, y)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| function _isInsideRing(area, x, y) { |
| return _isInsideCircle(area, x, y, area.r) |
| && !_isInsideCircle({x: area.x, y: area.y}, x, y, area.r0 || 0); |
| } |
| |
| /** |
| * 矩形包含判断 |
| */ |
| function _isInsideRectangle(area, x, y) { |
| return x >= area.x |
| && x <= (area.x + area.width) |
| && y >= area.y |
| && y <= (area.y + area.height); |
| } |
| |
| /** |
| * 圆形包含判断 |
| */ |
| function _isInsideCircle(area, x, y, r) { |
| return (x - area.x) * (x - area.x) + (y - area.y) * (y - area.y) |
| < r * r; |
| } |
| |
| /** |
| * 扇形包含判断 |
| */ |
| function _isInsideSector(area, x, y) { |
| if (!_isInsideCircle(area, x, y, area.r) |
| || (area.r0 > 0 |
| && _isInsideCircle( |
| { |
| x : area.x, |
| y : area.y |
| }, |
| x, y, |
| area.r0 |
| ) |
| ) |
| ){ |
| // 大圆外或者小圆内直接false |
| return false; |
| } |
| |
| // 判断夹角 |
| if (Math.abs(area.endAngle - area.startAngle) >= 360) { |
| // 大于360度的扇形,在环内就为true |
| return true; |
| } |
| |
| var angle = (360 |
| - Math.atan2(y - area.y, x - area.x) / Math.PI |
| * 180) |
| % 360; |
| var endA = (360 + area.endAngle) % 360; |
| var startA = (360 + area.startAngle) % 360; |
| if (endA > startA) { |
| return (angle >= startA && angle <= endA); |
| } |
| |
| return !(angle >= endA && angle <= startA); |
| } |
| |
| /** |
| * 多边形包含判断 |
| * 警告:下面这段代码会很难看,建议跳过~ |
| */ |
| function _isInsidePolygon(area, x, y) { |
| /** |
| * 射线判别法 |
| * 如果一个点在多边形内部,任意角度做射线肯定会与多边形要么有一个交点,要么有与多边形边界线重叠 |
| * 如果一个点在多边形外部,任意角度做射线要么与多边形有一个交点, |
| * 要么有两个交点,要么没有交点,要么有与多边形边界线重叠。 |
| */ |
| var i; |
| var j; |
| var polygon = area.pointList; |
| var N = polygon.length; |
| var inside = false; |
| var redo = true; |
| var v; |
| |
| for (i = 0; i < N; ++i) { |
| // 是否在顶点上 |
| if (polygon[i][0] == x && polygon[i][1] == y ) { |
| redo = false; |
| inside = true; |
| break; |
| } |
| } |
| |
| if (redo) { |
| redo = false; |
| inside = false; |
| for (i = 0,j = N - 1; i < N; j = i++) { |
| if ((polygon[i][1] < y && y < polygon[j][1]) |
| || (polygon[j][1] < y && y < polygon[i][1]) |
| ) { |
| if (x <= polygon[i][0] || x <= polygon[j][0]) { |
| v = (y - polygon[i][1]) |
| * (polygon[j][0] - polygon[i][0]) |
| / (polygon[j][1] - polygon[i][1]) |
| + polygon[i][0]; |
| if (x < v) { // 在线的左侧 |
| inside = !inside; |
| } |
| else if (x == v) { // 在线上 |
| inside = true; |
| break; |
| } |
| } |
| } |
| else if (y == polygon[i][1]) { |
| if (x < polygon[i][0]) { // 交点在顶点上 |
| polygon[i][1] > polygon[j][1] ? --y : ++y; |
| //redo = true; |
| break; |
| } |
| } |
| else if (polygon[i][1] == polygon[j][1] // 在水平的边界线上 |
| && y == polygon[i][1] |
| && ((polygon[i][0] < x && x < polygon[j][0]) |
| || (polygon[j][0] < x && x < polygon[i][0])) |
| ) { |
| inside = true; |
| break; |
| } |
| } |
| } |
| return inside; |
| } |
| |
| /** |
| * 路径包含判断,依赖多边形判断 |
| */ |
| function _isInsidePath(area, x, y) { |
| if (!area.pointList) { |
| require('../shape/Path').prototype.buildPath(_ctx, area); |
| } |
| var pointList = area.pointList; |
| var insideCatch = false; |
| for (var i = 0, l = pointList.length; i < l; i++) { |
| insideCatch = _isInsidePolygon( |
| { pointList : pointList[i] }, x, y |
| ); |
| |
| if (insideCatch) { |
| break; |
| } |
| } |
| |
| return insideCatch; |
| } |
| |
| /** |
| * 测算多行文本宽度 |
| * @param {Object} text |
| * @param {Object} textFont |
| */ |
| function getTextWidth(text, textFont) { |
| var key = text+':'+textFont; |
| if (_textWidthCache[key]) { |
| return _textWidthCache[key]; |
| } |
| _ctx = _ctx || util.getContext(); |
| _ctx.save(); |
| |
| if (textFont) { |
| _ctx.font = textFont; |
| } |
| |
| text = (text + '').split('\n'); |
| var width = 0; |
| for (var i = 0, l = text.length; i < l; i++) { |
| width = Math.max( |
| _ctx.measureText(text[i]).width, |
| width |
| ); |
| } |
| _ctx.restore(); |
| |
| _textWidthCache[key] = width; |
| if (++_textWidthCacheCounter > TEXT_CACHE_MAX) { |
| // 内存释放 |
| _textWidthCacheCounter = 0; |
| _textWidthCache = {}; |
| } |
| |
| return width; |
| } |
| |
| /** |
| * 测算多行文本高度 |
| * @param {Object} text |
| * @param {Object} textFont |
| */ |
| function getTextHeight(text, textFont) { |
| var key = text+':'+textFont; |
| if (_textHeightCache[key]) { |
| return _textHeightCache[key]; |
| } |
| |
| _ctx = _ctx || util.getContext(); |
| |
| _ctx.save(); |
| if (textFont) { |
| _ctx.font = textFont; |
| } |
| |
| text = (text + '').split('\n'); |
| //比较粗暴 |
| var height = (_ctx.measureText('国').width + 2) * text.length; |
| |
| _ctx.restore(); |
| |
| _textHeightCache[key] = height; |
| if (++_textHeightCacheCounter > TEXT_CACHE_MAX) { |
| // 内存释放 |
| _textHeightCacheCounter = 0; |
| _textHeightCache = {}; |
| } |
| return height; |
| } |
| |
| return { |
| isInside : isInside, |
| isOutside : isOutside, |
| getTextWidth : getTextWidth, |
| getTextHeight : getTextHeight |
| }; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:文字 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'text', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,横坐标 |
| y : {number}, // 必须,纵坐标 |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',线条颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 必须,文本内容 |
| textFont : {string}, // 默认为null,文本文字样式,eg:'bold 18px verdana' |
| textAlign : {string}, // 默认为start,文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认为middle,文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| maxWidth : {number} // 默认为null,最大宽度 |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'text', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| color : 'red', |
| text : 'Baidu' |
| }, |
| myName : 'kener', //可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Text',['require','../tool/area','./Base','../tool/util'],function (require) { |
| var area = require('../tool/area'); |
| var Base = require('./Base'); |
| |
| function Text(options) { |
| Base.call(this, options); |
| } |
| |
| Text.prototype = { |
| type: 'text', |
| |
| /** |
| * 画刷,重载基类方法 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param isHighlight 是否为高亮状态 |
| */ |
| brush : function(ctx, isHighlight) { |
| var style = this.style; |
| if (isHighlight) { |
| // 根据style扩展默认高亮样式 |
| style = this.getHighlightStyle( |
| style, this.highlightStyle || {} |
| ); |
| } |
| |
| if (typeof(style.text) == 'undefined' || style.text === false) { |
| return; |
| } |
| |
| ctx.save(); |
| this.setContext(ctx, style); |
| |
| // 设置transform |
| this.setTransform(ctx); |
| |
| if (style.textFont) { |
| ctx.font = style.textFont; |
| } |
| ctx.textAlign = style.textAlign || 'start'; |
| ctx.textBaseline = style.textBaseline || 'middle'; |
| |
| var text = (style.text + '').split('\n'); |
| var lineHeight = area.getTextHeight('国', style.textFont); |
| var rect = this.getRect(style); |
| var x = style.x; |
| var y; |
| if (style.textBaseline == 'top') { |
| y = rect.y; |
| } |
| else if (style.textBaseline == 'bottom') { |
| y = rect.y + lineHeight; |
| } |
| else { |
| y = rect.y + lineHeight / 2; |
| } |
| |
| for (var i = 0, l = text.length; i < l; i++) { |
| if (style.maxWidth) { |
| switch (style.brushType) { |
| case 'fill': |
| ctx.fillText( |
| text[i], |
| x, y, style.maxWidth |
| ); |
| break; |
| case 'stroke': |
| ctx.strokeText( |
| text[i], |
| x, y, style.maxWidth |
| ); |
| break; |
| case 'both': |
| ctx.fillText( |
| text[i], |
| x, y, style.maxWidth |
| ); |
| ctx.strokeText( |
| text[i], |
| x, y, style.maxWidth |
| ); |
| break; |
| default: |
| ctx.fillText( |
| text[i], |
| x, y, style.maxWidth |
| ); |
| } |
| } |
| else{ |
| switch (style.brushType) { |
| case 'fill': |
| ctx.fillText(text[i], x, y); |
| break; |
| case 'stroke': |
| ctx.strokeText(text[i], x, y); |
| break; |
| case 'both': |
| ctx.fillText(text[i], x, y); |
| ctx.strokeText(text[i], x, y); |
| break; |
| default: |
| ctx.fillText(text[i], x, y); |
| } |
| } |
| y += lineHeight; |
| } |
| |
| ctx.restore(); |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var width = area.getTextWidth(style.text, style.textFont); |
| var height = area.getTextHeight(style.text, style.textFont); |
| |
| var textX = style.x; //默认start == left |
| if (style.textAlign == 'end' || style.textAlign == 'right') { |
| textX -= width; |
| } |
| else if (style.textAlign == 'center') { |
| textX -= (width / 2); |
| } |
| |
| var textY; |
| if (style.textBaseline == 'top') { |
| textY = style.y; |
| } |
| else if (style.textBaseline == 'bottom') { |
| textY = style.y - height; |
| } |
| else { |
| // middle |
| textY = style.y - height / 2; |
| } |
| |
| style.__rect = { |
| x : textX, |
| y : textY, |
| width : width, |
| height : height |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Text, Base); |
| return Text; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) , |
| * strwind (@劲风FEI, yaofeifei@baidu.com) |
| * |
| * shape类:矩形 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'rectangle', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,左上角横坐标 |
| y : {number}, // 必须,左上角纵坐标 |
| width : {number}, // 必须,宽度 |
| height : {number}, // 必须,高度 |
| radius : {array}, // 默认为[0],圆角 |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为top,附加文本位置。 |
| // inside | left | right | top | bottom |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'rectangle', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| width : 150, |
| height : 50, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Rectangle',['require','./Base','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| |
| function Rectangle(options) { |
| Base.call(this, options); |
| } |
| |
| Rectangle.prototype = { |
| type: 'rectangle', |
| |
| /** |
| * 绘制圆角矩形 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| _buildRadiusPath: function(ctx, style) { |
| //左上、右上、右下、左下角的半径依次为r1、r2、r3、r4 |
| //r缩写为1 相当于 [1, 1, 1, 1] |
| //r缩写为[1] 相当于 [1, 1, 1, 1] |
| //r缩写为[1, 2] 相当于 [1, 2, 1, 2] |
| //r缩写为[1, 2, 3] 相当于 [1, 2, 3, 2] |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| var r = style.radius; |
| var r1; |
| var r2; |
| var r3; |
| var r4; |
| |
| if(typeof r === 'number') { |
| r1 = r2 = r3 = r4 = r; |
| } |
| else if(r instanceof Array) { |
| if (r.length === 1) { |
| r1 = r2 = r3 = r4 = r[0]; |
| } |
| else if(r.length === 2) { |
| r1 = r3 = r[0]; |
| r2 = r4 = r[1]; |
| } |
| else if(r.length === 3) { |
| r1 = r[0]; |
| r2 = r4 = r[1]; |
| r3 = r[2]; |
| } else { |
| r1 = r[0]; |
| r2 = r[1]; |
| r3 = r[2]; |
| r4 = r[3]; |
| } |
| } else { |
| r1 = r2 = r3 = r4 = 0; |
| } |
| ctx.moveTo(x + r1, y); |
| ctx.lineTo(x + width - r2, y); |
| r2 !== 0 && ctx.quadraticCurveTo( |
| x + width, y, x + width, y + r2 |
| ); |
| ctx.lineTo(x + width, y + height - r3); |
| r3 !== 0 && ctx.quadraticCurveTo( |
| x + width, y + height, x + width - r3, y + height |
| ); |
| ctx.lineTo(x + r4, y + height); |
| r4 !== 0 && ctx.quadraticCurveTo( |
| x, y + height, x, y + height - r4 |
| ); |
| ctx.lineTo(x, y + r1); |
| r1 !== 0 && ctx.quadraticCurveTo(x, y, x + r1, y); |
| }, |
| |
| /** |
| * 创建矩形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| if(!style.radius) { |
| ctx.moveTo(style.x, style.y); |
| ctx.lineTo(style.x + style.width, style.y); |
| ctx.lineTo(style.x + style.width, style.y + style.height); |
| ctx.lineTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x, style.y); |
| //ctx.rect(style.x, style.y, style.width, style.height); |
| } else { |
| this._buildRadiusPath(ctx, style); |
| } |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| style.__rect = { |
| x : Math.round(style.x - lineWidth / 2), |
| y : Math.round(style.y - lineWidth / 2), |
| width : style.width + lineWidth, |
| height : style.height + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Rectangle, Base); |
| return Rectangle; |
| } |
| ); |
| /** |
| * zrender: loading特效类 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| define( |
| 'zrender/loadingEffect/Base',['require','../tool/util','../shape/Text','../shape/Rectangle'],function(require) { |
| var util = require('../tool/util'); |
| var TextShape = require('../shape/Text'); |
| var RectangleShape = require('../shape/Rectangle'); |
| |
| |
| var DEFAULT_TEXT = 'Loading...'; |
| var DEFAULT_TEXT_FONT = 'normal 16px Arial'; |
| |
| /** |
| * @constructor |
| * |
| * @param {Object} options 选项 |
| * @param {color} options.backgroundColor 背景颜色 |
| * @param {Object} options.textStyle 文字样式,同shape/text.style |
| * @param {number=} options.progress 进度参数,部分特效有用 |
| * @param {Object=} options.effect 特效参数,部分特效有用 |
| * |
| * { |
| * effect, |
| * //loading话术 |
| * text:'', |
| * // 水平安放位置,默认为 'center',可指定x坐标 |
| * x:'center' || 'left' || 'right' || {number}, |
| * // 垂直安放位置,默认为'top',可指定y坐标 |
| * y:'top' || 'bottom' || {number}, |
| * |
| * textStyle:{ |
| * textFont: 'normal 20px Arial' || {textFont}, //文本字体 |
| * color: {color} |
| * } |
| * } |
| */ |
| function Base(options) { |
| this.setOptions(options); |
| } |
| |
| /** |
| * 创建loading文字图形 |
| * |
| * @param {Object} textStyle 文字style,同shape/text.style |
| */ |
| Base.prototype.createTextShape = function (textStyle) { |
| return new TextShape({ |
| highlightStyle : util.merge( |
| { |
| x : this.canvasWidth / 2, |
| y : this.canvasHeight / 2, |
| text : DEFAULT_TEXT, |
| textAlign : 'center', |
| textBaseline : 'middle', |
| textFont : DEFAULT_TEXT_FONT, |
| color: '#333', |
| brushType : 'fill' |
| }, |
| textStyle, |
| true |
| ) |
| }); |
| }; |
| |
| /** |
| * 获取loading背景图形 |
| * |
| * @param {color} color 背景颜色 |
| */ |
| Base.prototype.createBackgroundShape = function (color) { |
| return new RectangleShape({ |
| highlightStyle : { |
| x : 0, |
| y : 0, |
| width : this.canvasWidth, |
| height : this.canvasHeight, |
| brushType : 'fill', |
| color : color |
| } |
| }); |
| }; |
| |
| Base.prototype.start = function (painter) { |
| this.canvasWidth = painter._width; |
| this.canvasHeight = painter._height; |
| |
| function addShapeHandle(param) { |
| painter.storage.addHover(param); |
| } |
| function refreshHandle() { |
| painter.refreshHover(); |
| } |
| this.loadingTimer = this._start(addShapeHandle, refreshHandle); |
| }; |
| |
| Base.prototype._start = function (/*addShapeHandle, refreshHandle*/) { |
| return setInterval(function(){}, 10000); |
| }; |
| |
| Base.prototype.stop = function () { |
| clearInterval(this.loadingTimer); |
| }; |
| |
| Base.prototype.setOptions = function (options) { |
| this.options = options || {}; |
| }; |
| |
| Base.prototype.adjust = function (value, region) { |
| if (value <= region[0]) { |
| value = region[0]; |
| } |
| else if (value >= region[1]) { |
| value = region[1]; |
| } |
| return value; |
| }; |
| |
| return Base; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * @author lang( shenyi01@baidu.com ) |
| * |
| * shape类:图片 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'image', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,左上角横坐标 |
| y : {number}, // 必须,左上角纵坐标 |
| width : {number}, // 可选,宽度 |
| height : {number}, // 可选,高度 |
| sx : {number}, // 可选, 从图片中裁剪的x |
| sy : {number}, // 可选, 从图片中裁剪的y |
| sWidth : {number}, // 可选, 从图片中裁剪的宽度 |
| sHeight : {number}, // 可选, 从图片中裁剪的高度 |
| image : {string|Image} // 必须,图片url或者图片对象 |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为top,附加文本位置。 |
| // inside | left | right | top | bottom |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'image', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| width : 150, |
| height : 50, |
| image : 'tests.jpg', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Image',['require','./Base','../tool/util'],function (require) { |
| var _cache = {}; |
| var _needsRefresh = []; |
| var _refreshTimeout; |
| |
| var Base = require('./Base'); |
| |
| function ZImage(options) { |
| Base.call(this, options); |
| } |
| |
| ZImage.prototype = { |
| type: 'image', |
| brush : function(ctx, isHighlight, refresh) { |
| var style = this.style || {}; |
| |
| if (isHighlight) { |
| // 根据style扩展默认高亮样式 |
| style = this.getHighlightStyle( |
| style, this.highlightStyle || {} |
| ); |
| } |
| |
| var image = style.image; |
| var me = this; |
| |
| if (typeof(image) === 'string') { |
| var src = image; |
| if (_cache[src]) { |
| image = _cache[src]; |
| } |
| else { |
| image = new Image();//document.createElement('image'); |
| image.onload = function(){ |
| image.onload = null; |
| clearTimeout( _refreshTimeout ); |
| _needsRefresh.push( me ); |
| // 防止因为缓存短时间内触发多次onload事件 |
| _refreshTimeout = setTimeout(function(){ |
| refresh && refresh( _needsRefresh ); |
| // 清空needsRefresh |
| _needsRefresh = []; |
| }, 10); |
| }; |
| _cache[ src ] = image; |
| |
| image.src = src; |
| } |
| } |
| if (image) { |
| //图片已经加载完成 |
| if (image.nodeName.toUpperCase() == 'IMG') { |
| if (window.ActiveXObject) { |
| if (image.readyState != 'complete') { |
| return; |
| } |
| } |
| else { |
| if (!image.complete) { |
| return; |
| } |
| } |
| } |
| // Else is canvas |
| |
| ctx.save(); |
| this.setContext(ctx, style); |
| |
| // 设置transform |
| this.setTransform(ctx); |
| |
| var width = style.width || image.width; |
| var height = style.height || image.height; |
| var x = style.x; |
| var y = style.y; |
| if (style.sWidth && style.sHeight) { |
| var sx = style.sx || 0; |
| var sy = style.sy || 0; |
| ctx.drawImage( |
| image, |
| sx, sy, style.sWidth, style.sHeight, |
| x, y, width, height |
| ); |
| } |
| else if (style.sx && style.sy) { |
| var sx = style.sx; |
| var sy = style.sy; |
| var sWidth = width - sx; |
| var sHeight = height - sy; |
| ctx.drawImage( |
| image, |
| sx, sy, sWidth, sHeight, |
| x, y, width, height |
| ); |
| } |
| else { |
| ctx.drawImage(image, x, y, width, height); |
| } |
| // 如果没设置宽和高的话自动根据图片宽高设置 |
| style.width = width; |
| style.height = height; |
| this.style.width = width; |
| this.style.height = height; |
| |
| |
| this.drawText(ctx, style, this.style); |
| |
| ctx.restore(); |
| } |
| }, |
| |
| /** |
| * 创建路径,用于判断hover时调用isPointInPath~ |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| ctx.rect(style.x, style.y, style.width, style.height); |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| return { |
| x : style.x, |
| y : style.y, |
| width : style.width, |
| height : style.height |
| }; |
| } |
| }; |
| |
| require('../tool/util').inherits(ZImage, Base); |
| return ZImage; |
| } |
| ); |
| /** |
| * Painter绘图模块 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| |
| |
| define( |
| 'zrender/Painter',['require','./config','./tool/util','./tool/log','./tool/matrix','./loadingEffect/Base','./shape/Image'],function (require) { |
| |
| |
| |
| var config = require('./config'); |
| var util = require('./tool/util'); |
| var log = require('./tool/log'); |
| var matrix = require('./tool/matrix'); |
| var BaseLoadingEffect = require('./loadingEffect/Base'); |
| |
| // retina 屏幕优化 |
| var devicePixelRatio = window.devicePixelRatio || 1; |
| var vmlCanvasManager = window.G_vmlCanvasManager; |
| |
| /** |
| * 返回false的方法,用于避免页面被选中 |
| * |
| * @inner |
| */ |
| function returnFalse() { |
| return false; |
| } |
| |
| /** |
| * 什么都不干的空方法 |
| * |
| * @inner |
| */ |
| function doNothing() {} |
| |
| /** |
| * 绘图类 (V) |
| * |
| * @param {HTMLElement} root 绘图区域 |
| * @param {storage} storage Storage实例 |
| */ |
| function Painter(root, storage) { |
| this.root = root; |
| this.storage = storage; |
| |
| root.innerHTML = ''; |
| this._width = this._getWidth(); // 宽,缓存记录 |
| this._height = this._getHeight(); // 高,缓存记录 |
| |
| var domRoot = document.createElement('div'); |
| this._domRoot = domRoot; |
| |
| //domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬 |
| domRoot.style.position = 'relative'; |
| domRoot.style.overflow = 'hidden'; |
| domRoot.style.width = this._width + 'px'; |
| domRoot.style.height = this._height + 'px'; |
| root.appendChild(domRoot); |
| |
| this._layers = {}; |
| |
| this._layerConfig = {}; |
| |
| this._loadingEffect = new BaseLoadingEffect({}); |
| this.shapeToImage = this._createShapeToImageProcessor(); |
| |
| // 创建各层canvas |
| // 背景 |
| this._bgDom = createDom('bg', 'div', this); |
| domRoot.appendChild(this._bgDom); |
| |
| // 高亮 |
| var hoverLayer = new Layer('_zrender_hover_', this); |
| this._layers['hover'] = hoverLayer; |
| domRoot.appendChild(hoverLayer.dom); |
| |
| hoverLayer.onselectstart = returnFalse; |
| |
| var me = this; |
| this.updatePainter = function(shapeList, callback) { |
| me.update(shapeList, callback); |
| }; |
| } |
| |
| /** |
| * 首次绘图,创建各种dom和context |
| * |
| * @param {Function=} callback 绘画结束后的回调函数 |
| */ |
| Painter.prototype.render = function (callback) { |
| if (this.isLoading()) { |
| this.hideLoading(); |
| } |
| |
| // TODO |
| this.refresh(callback); |
| |
| return this; |
| }; |
| |
| /** |
| * 刷新 |
| * |
| * @param {Function=} callback 刷新结束后的回调函数 |
| */ |
| Painter.prototype.refresh = function (callback) { |
| |
| var list = this.storage.getShapeList(true); |
| |
| this._paintList(list); |
| |
| if (typeof callback == 'function') { |
| callback(); |
| } |
| |
| return this; |
| }; |
| |
| Painter.prototype._paintList = function(list) { |
| |
| var layerStatus = this._getLayerStatus(list); |
| |
| var currentLayer; |
| var currentZLevel; |
| var currentLayerDirty = true; |
| var ctx; |
| |
| for (var id in this._layers) { |
| if (id !== 'hover') { |
| this._layers[id].unusedCount++; |
| } |
| } |
| |
| var invTransform = []; |
| |
| for (var i = 0, l = list.length; i < l; i++) { |
| var shape = list[i]; |
| |
| if (currentZLevel !== shape.zlevel) { |
| currentLayer = this._getLayer(shape.zlevel, currentLayer); |
| ctx = currentLayer.ctx; |
| currentZLevel = shape.zlevel; |
| currentLayerDirty = layerStatus[currentZLevel]; |
| |
| // Reset the count |
| currentLayer.unusedCount = 0; |
| |
| if (currentLayerDirty) { |
| currentLayer.clear(); |
| } |
| } |
| |
| // 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] |
| ); |
| } |
| |
| 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 (currentLayerDirty && !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); |
| } |
| } |
| } |
| |
| // Stop group clipping |
| if (shape.__stopClip && !vmlCanvasManager) { |
| ctx.restore(); |
| } |
| |
| shape.__dirty = false; |
| } |
| |
| for (var id in this._layers) { |
| if (id !== 'hover') { |
| var layer = this._layers[id]; |
| if (layer.unusedCount >= 2) { |
| delete this._layers[id]; |
| layer.dom.parentNode.removeChild(layer.dom); |
| } |
| else if (layer.unusedCount == 1) { |
| layer.clear(); |
| } |
| } |
| } |
| }; |
| |
| 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 (prevDom.nextSibling) { |
| prevDom.parentNode.insertBefore( |
| currentLayer.dom, |
| prevDom.nextSibling |
| ); |
| } else { |
| prevDom.parentNode.appendChild( |
| currentLayer.dom |
| ); |
| } |
| |
| this._layers[zlevel] = currentLayer; |
| |
| currentLayer.config = this._layerConfig[zlevel]; |
| } |
| |
| return currentLayer; |
| }; |
| |
| Painter.prototype._getLayerStatus = function(list) { |
| |
| var obj = {}; |
| |
| for (var i = 0, l = list.length; i < l; i++) { |
| var shape = list[i]; |
| var zlevel = shape.zlevel; |
| // Already mark as dirty |
| if (obj[zlevel]) { |
| continue; |
| } |
| obj[zlevel] = shape.__dirty; |
| } |
| |
| return obj; |
| }; |
| |
| /** |
| * 视图更新 |
| * |
| * @param {Array} shapeList 需要更新的图形元素列表 |
| * @param {Function} callback 视图更新后回调函数 |
| */ |
| 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); |
| return this; |
| }; |
| |
| /** |
| * 设置loading特效 |
| * |
| * @param {Object} loadingEffect loading特效 |
| * @return {Painter} |
| */ |
| Painter.prototype.setLoadingEffect = function (loadingEffect) { |
| this._loadingEffect = loadingEffect; |
| return this; |
| }; |
| |
| /** |
| * 清除hover层外所有内容 |
| */ |
| Painter.prototype.clear = function () { |
| for (var k in this._layers) { |
| if (k == 'hover') { |
| continue; |
| } |
| this._layers[k].clear(); |
| } |
| |
| return this; |
| }; |
| |
| /** |
| * 修改指定zlevel的绘制参数 |
| */ |
| Painter.prototype.modLayer = function (zlevel, config) { |
| if (config) { |
| if (!this._layerConfig[zlevel]) { |
| this._layerConfig[zlevel] = config; |
| } else { |
| util.merge(this._layerConfig[zlevel], config, true); |
| } |
| |
| var layer = this._layers[zlevel]; |
| |
| if (layer) { |
| layer.config = this._layerConfig[zlevel]; |
| } |
| } |
| }; |
| |
| /** |
| * 刷新hover层 |
| */ |
| 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]); |
| } |
| this.storage.delHover(); |
| |
| return this; |
| }; |
| |
| /** |
| * 清除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 () { |
| var domRoot = this._domRoot; |
| domRoot.style.display = 'none'; |
| |
| var width = this._getWidth(); |
| var height = this._getHeight(); |
| |
| domRoot.style.display = ''; |
| |
| // 优化没有实际改变的resize |
| if (this._width != width || height != this._height){ |
| this._width = width; |
| this._height = height; |
| |
| domRoot.style.width = width + 'px'; |
| domRoot.style.height = height + 'px'; |
| |
| for (var id in this._layers) { |
| |
| this._layers[id].resize(width, height); |
| } |
| |
| this.refresh(); |
| } |
| |
| return this; |
| }; |
| |
| /** |
| * 清除单独的一个层 |
| */ |
| Painter.prototype.clearLayer = function (k) { |
| var layer = this._layers[k]; |
| if (layer) { |
| layer.clear(); |
| } |
| }; |
| |
| /** |
| * 释放 |
| */ |
| Painter.prototype.dispose = function () { |
| if (this.isLoading()) { |
| this.hideLoading(); |
| } |
| |
| this.root.innerHTML = ''; |
| |
| this.root = |
| this.storage = |
| |
| this._domRoot = |
| this._layers = null; |
| }; |
| |
| Painter.prototype.getDomHover = function () { |
| return this._layers.hover.dom; |
| }; |
| |
| Painter.prototype.toDataURL = function (type, backgroundColor, args) { |
| if (vmlCanvasManager) { |
| return null; |
| } |
| |
| 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; |
| }; |
| |
| /** |
| * 获取绘图区域宽度 |
| */ |
| Painter.prototype.getWidth = function () { |
| return this._width; |
| }; |
| |
| /** |
| * 获取绘图区域高度 |
| */ |
| Painter.prototype.getHeight = function () { |
| return this._height; |
| }; |
| |
| Painter.prototype._getWidth = function() { |
| var root = this.root; |
| var stl = root.currentStyle |
| || document.defaultView.getComputedStyle(root); |
| |
| return ((root.clientWidth || parseInt(stl.width, 10)) |
| - parseInt(stl.paddingLeft, 10) // 请原谅我这比较粗暴 |
| - parseInt(stl.paddingRight, 10)).toFixed(0) - 0; |
| }; |
| |
| Painter.prototype._getHeight = function () { |
| var root = this.root; |
| var stl = root.currentStyle |
| || document.defaultView.getComputedStyle(root); |
| |
| return ((root.clientHeight || parseInt(stl.height, 10)) |
| - parseInt(stl.paddingTop, 10) // 请原谅我这比较粗暴 |
| - parseInt(stl.paddingBottom, 10)).toFixed(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 |
| ) { |
| 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); |
| |
| var shapeTransform = { |
| position : shape.position, |
| rotation : shape.rotation, |
| scale : shape.scale |
| }; |
| shape.position = [0, 0, 0]; |
| shape.rotation = 0; |
| shape.scale = [1, 1]; |
| if (shape) { |
| shape.brush(ctx, false); |
| } |
| |
| var ImageShape = require( './shape/Image' ); |
| var imgShape = new ImageShape({ |
| id : id, |
| style : { |
| x : 0, |
| y : 0, |
| image : canvas |
| } |
| }); |
| |
| if (shapeTransform.position != null) { |
| imgShape.position = shape.position = shapeTransform.position; |
| } |
| |
| if (shapeTransform.rotation != null) { |
| imgShape.rotation = shape.rotation = shapeTransform.rotation; |
| } |
| |
| if (shapeTransform.scale != null) { |
| imgShape.scale = shape.scale = shapeTransform.scale; |
| } |
| |
| return imgShape; |
| }; |
| |
| Painter.prototype._createShapeToImageProcessor = function () { |
| if (vmlCanvasManager) { |
| return doNothing; |
| } |
| |
| var painter = this; |
| |
| return function (id, e, width, height) { |
| return painter._shapeToImage( |
| id, e, width, height, devicePixelRatio |
| ); |
| }; |
| }; |
| |
| /** |
| * 创建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.ctx = this.dom.getContext('2d'); |
| |
| if (devicePixelRatio != 1) { |
| this.ctx.scale(devicePixelRatio, devicePixelRatio); |
| } |
| |
| this.domBack = null; |
| this.ctxBack = null; |
| |
| this.painter = painter; |
| |
| this.unusedCount = 0; |
| |
| this.config = null; |
| } |
| |
| 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.setAttribute('width', width); |
| this.dom.setAttribute('height', 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', width * 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, |
| height |
| ); |
| } |
| }; |
| |
| return Painter; |
| } |
| ); |
| |
| define('zrender/shape/Group',['require','../tool/guid','../tool/util','../tool/event','./mixin/Transformable'],function(require) { |
| |
| var guid = require('../tool/guid'); |
| var util = require('../tool/util'); |
| |
| var Dispatcher = require('../tool/event').Dispatcher; |
| var Transformable = require('./mixin/Transformable'); |
| |
| /** |
| * @constructor zrender.shape.Group |
| */ |
| function Group(options) { |
| |
| options = options || {}; |
| |
| this.id = options.id || guid(); |
| |
| for (var key in options) { |
| this[key] = options[key]; |
| } |
| |
| this.type = 'group'; |
| |
| this.clipShape = null; |
| |
| this._children = []; |
| |
| this._storage = null; |
| |
| this.__dirty = true; |
| |
| // Mixin |
| Transformable.call(this); |
| Dispatcher.call(this); |
| } |
| |
| Group.prototype.children = function() { |
| return this._children.slice(); |
| }; |
| |
| Group.prototype.childAt = function(idx) { |
| return this._children[idx]; |
| }; |
| |
| Group.prototype.addChild = function(child) { |
| if (child == this) { |
| return; |
| } |
| |
| if (child.parent == this) { |
| return; |
| } |
| if (child.parent) { |
| child.parent.removeChild(child); |
| } |
| |
| this._children.push(child); |
| child.parent = this; |
| |
| if (this._storage && this._storage !== child._storage) { |
| |
| this._storage.addToMap(child); |
| |
| if (child instanceof Group) { |
| child.addChildrenToStorage(this._storage); |
| } |
| } |
| }; |
| |
| Group.prototype.removeChild = function(child) { |
| var idx = util.indexOf(this._children, child); |
| |
| this._children.splice(idx, 1); |
| child.parent = null; |
| |
| if (child._storage) { |
| |
| this._storage.delFromMap(child.id); |
| |
| if (child instanceof Group) { |
| child.delChildrenFromStorage(child._storage); |
| } |
| } |
| }; |
| |
| Group.prototype.each = function(cb, context) { |
| var haveContext = !!context; |
| for (var i = 0; i < this._children.length; i++) { |
| var child = this._children[i]; |
| if (haveContext) { |
| cb.call(context, child); |
| } else { |
| cb(child); |
| } |
| } |
| }; |
| |
| Group.prototype.iterate = function(cb, context) { |
| var haveContext = !!context; |
| |
| for (var i = 0; i < this._children.length; i++) { |
| var child = this._children[i]; |
| if (haveContext) { |
| cb.call(context, child); |
| } else { |
| cb(child); |
| } |
| |
| if (child.type === 'group') { |
| child.iterate(cb, context); |
| } |
| } |
| }; |
| |
| Group.prototype.addChildrenToStorage = function(storage) { |
| for (var i = 0; i < this._children.length; i++) { |
| var child = this._children[i]; |
| storage.addToMap(child); |
| if (child.type === 'group') { |
| child.addChildrenToStorage(storage); |
| } |
| } |
| }; |
| |
| Group.prototype.delChildrenFromStorage = function(storage) { |
| for (var i = 0; i < this._children.length; i++) { |
| var child = this._children[i]; |
| storage.delFromMap(child); |
| if (child.type === 'group') { |
| child.delChildrenFromStorage(storage); |
| } |
| } |
| }; |
| |
| util.merge(Group.prototype, Transformable.prototype, true); |
| util.merge(Group.prototype, Dispatcher.prototype, true); |
| |
| return Group; |
| }); |
| /** |
| * Storage内容仓库模块 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| |
| define( |
| 'zrender/Storage',['require','./tool/util','./shape/Group'],function (require) { |
| |
| |
| |
| 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) { |
| return a.__renderidx - b.__renderidx; |
| } |
| return a.zlevel - b.zlevel; |
| } |
| /** |
| * 内容仓库 (M) |
| * |
| */ |
| function Storage() { |
| // 所有常规形状,id索引的map |
| this._elements = {}; |
| |
| // 高亮层形状,不稳定,动态增删,数组位置也是z轴方向,靠前显示在下方 |
| this._hoverElements = []; |
| |
| this._roots = []; |
| |
| this._shapeList = []; |
| |
| this._shapeListOffset = 0; |
| } |
| |
| /** |
| * 遍历迭代器 |
| * |
| * @param {Function} fun 迭代回调函数,return true终止迭代 |
| * @param {Object=} option 迭代参数,缺省为仅降序遍历常规形状 |
| * hover : true 是否迭代高亮层数据 |
| * normal : 'down' | 'up' 是否迭代常规数据,迭代时是否指定及z轴顺序 |
| * update : false 是否更新shapeList |
| */ |
| Storage.prototype.iterShape = function (fun, option) { |
| if (!option) { |
| option = defaultIterateOption; |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| if (option.update) { |
| this.updateShapeList(); |
| } |
| |
| //遍历: '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; |
| } |
| |
| return this; |
| }; |
| |
| Storage.prototype.getHoverShapes = function(update) { |
| if (update) { |
| for (var i = 0, l = this._hoverElements.length; i < l; i++) { |
| this._hoverElements[i].updateTransform(); |
| } |
| } |
| return this._hoverElements; |
| }; |
| |
| Storage.prototype.getShapeList = function(update) { |
| if (update) { |
| this.updateShapeList(); |
| } |
| return this._shapeList; |
| }; |
| |
| |
| 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; |
| |
| for (var i = 0, len = this._shapeList.length; i < len; i++) { |
| this._shapeList[i].__renderidx = i; |
| } |
| |
| this._shapeList.sort(shapeCompareFunc); |
| }; |
| |
| Storage.prototype._updateAndAddShape = function(el) { |
| |
| el.updateTransform(); |
| |
| if (el.type == 'group') { |
| |
| if (el.clipShape) { |
| // clipShape 的变换是基于 group 的变换 |
| el.clipShape.parent = el; |
| el.clipShape.updateTransform(); |
| |
| var startClipShape = el._children[0]; |
| if (startClipShape) { |
| startClipShape.__startClip = el.clipShape; |
| } |
| } |
| |
| for (var i = 0; i < el._children.length; i++) { |
| var child = el._children[i]; |
| |
| // Force to mark as dirty if group is dirty |
| child.__dirty = el.__dirty || el.__dirty; |
| |
| this._updateAndAddShape(child); |
| } |
| |
| if (el.clipShape) { |
| var stopClipShape = this._shapeList[this._shapeListOffset - 1]; |
| if (stopClipShape) { |
| stopClipShape.__stopClip = true; |
| } |
| } |
| } else { |
| this._shapeList[this._shapeListOffset++] = el; |
| } |
| }; |
| |
| /** |
| * 修改 |
| * |
| * @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); |
| } |
| } |
| } |
| |
| return this; |
| }; |
| |
| /** |
| * 常规形状位置漂移,形状自身定义漂移函数 |
| * |
| * @param {string} idx 形状唯一标识 |
| */ |
| 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); |
| } |
| } |
| |
| 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) { |
| el.addChildrenToStorage(this); |
| } |
| |
| this.addToMap(el); |
| this._roots.push(el); |
| }; |
| |
| Storage.prototype.delRoot = function (elId) { |
| if (typeof(elId) == 'undefined') { |
| // 不指定elId清空 |
| for (var i = 0; i < this._roots.length; i++) { |
| var root = this._roots[i]; |
| if (root instanceof Group) { |
| root.delChildrenFromStorage(this); |
| } |
| } |
| |
| this._elements = {}; |
| this._hoverElements = []; |
| this._roots = []; |
| |
| return; |
| } |
| |
| if (elId instanceof Array) { |
| for (var i = 0, l = elId.length; i < l; i++) { |
| this.delRoot(elId[i]); |
| } |
| return; |
| } |
| |
| var el; |
| if (typeof(elId) == 'string') { |
| el = this._elements[elId]; |
| } else { |
| el = elId; |
| } |
| |
| var idx = util.indexOf(this._roots, el); |
| if (idx >= 0) { |
| this.delFromMap(el.id); |
| this._roots.splice(idx, 1); |
| if (el instanceof Group) { |
| el.delChildrenFromStorage(this); |
| } |
| } |
| }; |
| |
| /** |
| * 添加 |
| * |
| * @param {Shape|Group} el 参数 |
| */ |
| Storage.prototype.addToMap = function (el) { |
| if (el instanceof Group) { |
| el._storage = this; |
| } else { |
| el.style.__rect = null; |
| } |
| |
| this._elements[el.id] = el; |
| |
| return this; |
| }; |
| |
| /** |
| * 根据指定的elId获取相应的shape属性 |
| * |
| * @param {string=} idx 唯一标识 |
| */ |
| Storage.prototype.get = function (elId) { |
| return this._elements[elId]; |
| }; |
| |
| /** |
| * 删除,elId不指定则全清空 |
| * |
| * @param {string} idx 唯一标识 |
| */ |
| Storage.prototype.delFromMap = function (elId) { |
| var el = this._elements[elId]; |
| if (el) { |
| delete this._elements[elId]; |
| |
| if (el instanceof Group) { |
| el._storage = null; |
| } |
| } |
| |
| return this; |
| }; |
| |
| |
| /** |
| * 释放 |
| */ |
| Storage.prototype.dispose = function () { |
| this._elements = |
| this._renderList = |
| this._roots = |
| this._hoverElements = null; |
| }; |
| |
| return Storage; |
| } |
| ); |
| |
| /** |
| * 缓动代码来自 https://github.com/sole/tween.js/blob/master/src/Tween.js |
| * author: lang(shenyi01@baidu.com) |
| */ |
| define( |
| 'zrender/animation/easing',[],function() { |
| var Easing = { |
| // 线性 |
| 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); |
| }, |
| |
| // 三次方的缓动(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^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^5) |
| QuinticIn: function(k) { |
| return k * k * k * k * k; |
| }, |
| |
| 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)) |
| 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) |
| 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)) |
| 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); |
| }, |
| |
| // 创建类似于弹簧在停止前来回振荡的动画 |
| 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; |
| |
| }, |
| |
| // 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动 |
| 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; |
| } |
| }; |
| |
| return Easing; |
| } |
| ); |
| |
| |
| /** |
| * 动画主控制器 |
| * @config target 动画对象,可以是数组,如果是数组的话会批量分发onframe等事件 |
| * @config life(1000) 动画时长 |
| * @config delay(0) 动画延迟时间 |
| * @config loop(true) |
| * @config gap(0) 循环的间隔时间 |
| * @config onframe |
| * @config easing(optional) |
| * @config ondestroy(optional) |
| * @config onrestart(optional) |
| */ |
| define( |
| 'zrender/animation/Clip',['require','./easing'],function(require) { |
| |
| var Easing = require('./easing'); |
| |
| function Clip(options) { |
| |
| this._targetPool = options.target || {}; |
| if (!(this._targetPool instanceof Array)) { |
| this._targetPool = [this._targetPool]; |
| } |
| |
| //生命周期 |
| this._life = options.life || 1000; |
| //延时 |
| this._delay = options.delay || 0; |
| //开始时间 |
| this._startTime = new Date().getTime() + this._delay;//单位毫秒 |
| |
| //结束时间 |
| this._endTime = this._startTime + this._life * 1000; |
| |
| //是否循环 |
| this.loop = typeof options.loop == 'undefined' |
| ? false : options.loop; |
| |
| this.gap = options.gap || 0; |
| |
| this.easing = options.easing || 'Linear'; |
| |
| this.onframe = options.onframe; |
| this.ondestroy = options.ondestroy; |
| this.onrestart = options.onrestart; |
| } |
| |
| Clip.prototype = { |
| step : function (time) { |
| var percent = (time - this._startTime) / this._life; |
| |
| //还没开始 |
| if (percent < 0) { |
| return; |
| } |
| |
| percent = Math.min(percent, 1); |
| |
| var easingFunc = typeof this.easing == 'string' |
| ? Easing[this.easing] |
| : this.easing; |
| var schedule = typeof easingFunc === 'function' |
| ? easingFunc(percent) |
| : percent; |
| |
| this.fire('frame', schedule); |
| |
| // 结束 |
| if (percent == 1) { |
| if (this.loop) { |
| this.restart(); |
| // 重新开始周期 |
| // 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件 |
| return 'restart'; |
| |
| } |
| |
| // 动画完成将这个控制器标识为待删除 |
| // 在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; |
| }, |
| 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; |
| } |
| ); |
| |
| /** |
| * 动画主类, 调度和管理所有动画控制器 |
| * |
| * @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( |
| 'zrender/animation/Animation',['require','./Clip','../tool/color','../tool/util','../tool/event'],function(require) { |
| |
| |
| |
| var Clip = require('./Clip'); |
| var color = require('../tool/color'); |
| var util = require('../tool/util'); |
| var Dispatcher = require('../tool/event').Dispatcher; |
| |
| var requestAnimationFrame = window.requestAnimationFrame |
| || window.msRequestAnimationFrame |
| || window.mozRequestAnimationFrame |
| || window.webkitRequestAnimationFrame |
| || function(func){setTimeout(func, 16);}; |
| |
| var arraySlice = Array.prototype.slice; |
| |
| function Animation(options) { |
| |
| options = options || {}; |
| |
| this.stage = options.stage || {}; |
| |
| this.onframe = options.onframe || function() {}; |
| |
| // private properties |
| this._clips = []; |
| |
| this._running = false; |
| |
| this._time = 0; |
| |
| 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() { |
| |
| var time = new Date().getTime(); |
| var delta = time - this._time; |
| var clips = this._clips; |
| var len = clips.length; |
| |
| 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(); |
| } |
| |
| // Remove the finished clip |
| for (var i = 0; i < len;) { |
| if (clips[i]._needsRemove) { |
| clips[i] = clips[len-1]; |
| clips.pop(); |
| len--; |
| } else { |
| i++; |
| } |
| } |
| |
| len = deferredEvents.length; |
| for (var i = 0; i < len; i++) { |
| deferredClips[i].fire(deferredEvents[i]); |
| } |
| |
| 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); |
| } |
| } |
| |
| 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 |
| ); |
| } |
| } 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 |
| ); |
| } |
| } |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| function rgba2String(rgba) { |
| rgba[0] = Math.floor(rgba[0]); |
| rgba[1] = Math.floor(rgba[1]); |
| rgba[2] = Math.floor(rgba[2]); |
| |
| return 'rgba(' + rgba.join(',') + ')'; |
| } |
| |
| function Deferred(target, loop, getter, setter) { |
| this._tracks = {}; |
| this._target = target; |
| |
| this._loop = loop || false; |
| |
| this._getter = getter || _defaultGetter; |
| this._setter = setter || _defaultSetter; |
| |
| this._clipCount = 0; |
| |
| this._delay = 0; |
| |
| this._doneList = []; |
| |
| this._onframeList = []; |
| |
| this._clipList = []; |
| } |
| |
| 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) { |
| |
| 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; |
| } |
| ); |
| |
| /*! |
| * ZRender, a lightweight canvas library with a MVC architecture, data-driven |
| * and provides an event model like DOM. |
| * |
| * Copyright (c) 2013, Baidu Inc. |
| * All rights reserved. |
| * |
| * LICENSE |
| * https://github.com/ecomfe/zrender/blob/master/LICENSE.txt |
| */ |
| |
| /** |
| * zrender: core核心类 |
| * |
| * @desc zrender是一个轻量级的Canvas类库,MVC封装,数据驱动,提供类Dom事件模型。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define( |
| 'zrender/zrender',['require','./lib/excanvas','./tool/util','./tool/log','./tool/guid','./Handler','./Painter','./Storage','./animation/Animation','./tool/env'],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 util = require('./tool/util'); |
| var log = require('./tool/log'); |
| var guid = require('./tool/guid'); |
| |
| var Handler = require('./Handler'); |
| var Painter = require('./Painter'); |
| var Storage = require('./Storage'); |
| var Animation = require('./animation/Animation'); |
| |
| var _instances = {}; //ZRender实例map索引 |
| |
| var zrender = {}; |
| zrender.version = '2.0.1'; |
| |
| /** |
| * 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; |
| }; |
| |
| /** |
| * zrender实例销毁,记在_instances里的索引也会删除了 |
| * 管生就得管死,可以通过zrender.dispose(zi)销毁指定ZRender实例 |
| * 当然也可以直接zi.dispose()自己销毁 |
| * |
| * @param {ZRender=} zi ZRender对象,不传则销毁全部 |
| */ |
| zrender.dispose = function (zi) { |
| if (zi) { |
| zi.dispose(); |
| } |
| else { |
| for (var key in _instances) { |
| _instances[key].dispose(); |
| } |
| _instances = {}; |
| } |
| |
| return zrender; |
| }; |
| |
| /** |
| * 获取zrender实例 |
| * |
| * @param {string} id 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; |
| }; |
| |
| 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); |
| } |
| |
| if (animatingShapes.length || zrInstance._needsRefreshNextFrame) { |
| zrInstance.refresh(); |
| zrInstance._needsRefreshNextFrame = false; |
| } |
| }; |
| } |
| |
| /** |
| * ZRender接口类,对外可用的所有接口都在这里!! |
| * storage(M)、painter(V)、handler(C)为内部私有类,外部接口不可见 |
| * 非get接口统一返回支持链式调用~ |
| * |
| * @param {string} id 唯一标识 |
| * @param {HTMLElement} dom dom对象,不帮你做document.getElementById |
| * |
| * @return {ZRender} ZRender实例 |
| */ |
| function ZRender(id, dom) { |
| this.id = id; |
| this.env = require('./tool/env'); |
| |
| this.storage = new Storage(); |
| this.painter = new Painter(dom, this.storage); |
| this.handler = new Handler(dom, this.storage, this.painter); |
| |
| // 动画控制 |
| this.animatingShapes = []; |
| this.animation = new Animation({ |
| stage : { |
| update : getFrameCallback(this) |
| } |
| }); |
| this.animation.start(); |
| |
| this._needsRefreshNextFrame = false; |
| } |
| |
| /** |
| * 获取实例唯一标识 |
| */ |
| ZRender.prototype.getId = function () { |
| return this.id; |
| }; |
| |
| /** |
| * 添加图形形状到根节点 |
| * |
| * @param {zrender.shape.Base} shape 形状对象,可用属性全集,详见各shape |
| */ |
| ZRender.prototype.addShape = function (shape) { |
| this.storage.addRoot(shape); |
| return this; |
| }; |
| |
| /** |
| * 添加组到根节点 |
| * |
| * @param {zrender.shape.Group} group |
| */ |
| ZRender.prototype.addGroup = function(group) { |
| this.storage.addRoot(group); |
| return this; |
| }; |
| |
| /** |
| * 从根节点删除图形形状 |
| * |
| * @param {string} shapeId 形状对象唯一标识 |
| */ |
| ZRender.prototype.delShape = function (shapeId) { |
| this.storage.delRoot(shapeId); |
| return this; |
| }; |
| |
| /** |
| * 从根节点删除组 |
| * |
| * @param {string} groupId |
| */ |
| ZRender.prototype.delGroup = function (groupId) { |
| this.storage.delRoot(groupId); |
| return this; |
| }; |
| |
| /** |
| * 修改图形形状 |
| * |
| * @param {string} shapeId 形状对象唯一标识 |
| * @param {Object} shape 形状对象 |
| */ |
| ZRender.prototype.modShape = function (shapeId, shape) { |
| this.storage.mod(shapeId, shape); |
| return this; |
| }; |
| |
| /** |
| * 修改组 |
| * |
| * @param {string} shapeId |
| * @param {Object} group |
| */ |
| ZRender.prototype.modGroup = function (groupId, group) { |
| this.storage.mod(groupId, group); |
| return this; |
| }; |
| |
| /** |
| * 修改指定zlevel的绘制配置项,例如clearColor |
| * |
| * @param {string} zLevel |
| * @param {Object} config 配置对象, 目前支持clearColor |
| */ |
| ZRender.prototype.modLayer = function (zLevel, config) { |
| this.painter.modLayer(zLevel, config); |
| return this; |
| }; |
| |
| /** |
| * 添加额外高亮层显示,仅提供添加方法,每次刷新后高亮层图形均被清空 |
| * |
| * @param {Object} shape 形状对象 |
| */ |
| ZRender.prototype.addHoverShape = function (shape) { |
| this.storage.addHover(shape); |
| return this; |
| }; |
| |
| /** |
| * 渲染 |
| * |
| * @param {Function} callback 渲染结束后回调函数 |
| * todo:增加缓动函数 |
| */ |
| ZRender.prototype.render = function (callback) { |
| this.painter.render(callback); |
| return this; |
| }; |
| |
| /** |
| * 视图更新 |
| * |
| * @param {Function} callback 视图更新后回调函数 |
| */ |
| ZRender.prototype.refresh = function (callback) { |
| this.painter.refresh(callback); |
| return this; |
| }; |
| |
| // TODO |
| // 好像会有奇怪的问题 |
| ZRender.prototype.refreshNextFrame = function() { |
| this._needsRefreshNextFrame = true; |
| return this; |
| }; |
| |
| /** |
| * 高亮层更新 |
| * |
| * @param {Function} callback 视图更新后回调函数 |
| */ |
| ZRender.prototype.refreshHover = function (callback) { |
| this.painter.refreshHover(callback); |
| return this; |
| }; |
| |
| /** |
| * 视图更新 |
| * |
| * @param {Array} shapeList 需要更新的图形元素列表 |
| * @param {Function} callback 视图更新后回调函数 |
| */ |
| ZRender.prototype.update = function (shapeList, callback) { |
| this.painter.update(shapeList, callback); |
| return this; |
| }; |
| |
| ZRender.prototype.resize = function() { |
| this.painter.resize(); |
| return this; |
| }; |
| |
| /** |
| * 动画 |
| * |
| * @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 () { |
| 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() { |
| return this.painter.getWidth(); |
| }; |
| |
| /** |
| * 获取视图高度 |
| */ |
| ZRender.prototype.getHeight = function() { |
| return this.painter.getHeight(); |
| }; |
| |
| /** |
| * 图像导出 |
| */ |
| ZRender.prototype.toDataURL = function(type, backgroundColor, args) { |
| return this.painter.toDataURL(type, backgroundColor, args); |
| }; |
| |
| /** |
| * 将常规shape转成image shape |
| */ |
| ZRender.prototype.shapeToImage = function(e, width, height) { |
| var id = guid(); |
| return this.painter.shapeToImage(id, e, width, height); |
| }; |
| |
| /** |
| * 事件绑定 |
| * |
| * @param {string} eventName 事件名称 |
| * @param {Function} eventHandler 响应函数 |
| */ |
| ZRender.prototype.on = function(eventName, eventHandler) { |
| this.handler.on(eventName, eventHandler); |
| return this; |
| }; |
| |
| /** |
| * 事件解绑定,参数为空则解绑所有自定义事件 |
| * |
| * @param {string} eventName 事件名称 |
| * @param {Function} eventHandler 响应函数 |
| */ |
| ZRender.prototype.un = function(eventName, eventHandler) { |
| this.handler.un(eventName, eventHandler); |
| return this; |
| }; |
| |
| /** |
| * 事件触发 |
| * |
| * @param {string} event 事件名称,resize,hover,drag,etc~ |
| * @param {event=} event event dom事件对象 |
| */ |
| ZRender.prototype.trigger = function (eventName, event) { |
| this.handler.trigger(eventName, event); |
| return this; |
| }; |
| |
| |
| /** |
| * 清除当前ZRender下所有类图的数据和显示,clear后MVC和已绑定事件均还存在在,ZRender可用 |
| */ |
| ZRender.prototype.clear = function () { |
| this.storage.delRoot(); |
| this.painter.clear(); |
| return this; |
| }; |
| |
| /** |
| * 释放当前ZR实例(删除包括dom,数据、显示和事件绑定),dispose后ZR不可用 |
| */ |
| ZRender.prototype.dispose = function () { |
| this.animation.stop(); |
| |
| this.clear(); |
| this.storage.dispose(); |
| this.painter.dispose(); |
| this.handler.dispose(); |
| |
| this.animation = |
| this.animatingShapes = |
| this.storage = |
| this.painter = |
| this.handler = null; |
| |
| //释放后告诉全局删除对自己的索引,没想到啥好方法 |
| zrender.delInstance(this.id); |
| }; |
| |
| return zrender; |
| } |
| ); |
| |
| define('zrender', ['zrender/zrender'], function (main) { return main; }); |
| |
| /** |
| * echarts层级查找方法 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/util/ecQuery',['zrender/tool/util'],function() { |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 获取嵌套选项的基础方法 |
| * 返回optionTarget中位于optionLocation上的值,如果没有定义,则返回undefined |
| */ |
| function query(optionTarget, optionLocation) { |
| if (typeof optionTarget == 'undefined') { |
| return; |
| } |
| |
| if (!optionLocation) { |
| return optionTarget; |
| } |
| |
| optionLocation = optionLocation.split('.'); |
| var length = optionLocation.length; |
| var curIdx = 0; |
| while (curIdx < length) { |
| optionTarget = optionTarget[optionLocation[curIdx]]; |
| if (typeof optionTarget == 'undefined') { |
| return; |
| } |
| curIdx++; |
| } |
| |
| return optionTarget; |
| } |
| |
| /** |
| * 获取多级控制嵌套属性的基础方法 |
| * 返回ctrList中优先级最高(最靠前)的非undefined属性,ctrList中均无定义则返回undefined |
| */ |
| function deepQuery(ctrList, optionLocation) { |
| var finalOption; |
| for (var i = 0, l = ctrList.length; i < l; i++) { |
| finalOption = query(ctrList[i], optionLocation); |
| if (typeof finalOption != 'undefined') { |
| return finalOption; |
| } |
| } |
| } |
| |
| /** |
| * 获取多级控制嵌套属性的基础方法 |
| * 根据ctrList中优先级合并产出目标属性 |
| */ |
| function deepMerge(ctrList, optionLocation) { |
| var finalOption; |
| var len = ctrList.length; |
| while (len--) { |
| var tempOption = query(ctrList[len], optionLocation); |
| if (typeof tempOption != 'undefined') { |
| if (typeof finalOption == 'undefined') { |
| finalOption = zrUtil.clone(tempOption); |
| } |
| else { |
| zrUtil.merge( |
| finalOption, tempOption, true |
| ); |
| } |
| } |
| } |
| |
| return finalOption; |
| } |
| |
| return { |
| query : query, |
| deepQuery : deepQuery, |
| deepMerge : deepMerge |
| }; |
| }); |
| /** |
| * echarts数字运算相关 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/util/number',[],function() { |
| function _trim(str) { |
| return str.replace(/^\s+/, '').replace(/\s+$/, ''); |
| } |
| |
| /** |
| * 百分比计算 |
| */ |
| function parsePercent(value, maxValue) { |
| if (typeof value === 'string') { |
| if (_trim(value).match(/%$/)) { |
| return parseFloat(value) / 100 * maxValue; |
| } |
| |
| return parseFloat(value); |
| } |
| |
| return value; |
| } |
| |
| /** |
| * 获取中心坐标 |
| */ |
| function parseCenter(zr, center) { |
| return [ |
| parsePercent(center[0], zr.getWidth()), |
| parsePercent(center[1], zr.getHeight()) |
| ]; |
| } |
| |
| /** |
| * 获取自适应半径 |
| */ |
| function parseRadius(zr, radius) { |
| // 传数组实现环形图,[内半径,外半径],传单个则默认为外半径为 |
| if (!(radius instanceof Array)) { |
| radius = [0, radius]; |
| } |
| var zrSize = Math.min(zr.getWidth(), zr.getHeight()) / 2; |
| return [ |
| parsePercent(radius[0], zrSize), |
| parsePercent(radius[1], zrSize) |
| ]; |
| } |
| |
| /** |
| * 每三位默认加,格式化 |
| */ |
| function addCommas(x) { |
| if (isNaN(x)) { |
| return '-'; |
| } |
| x = (x + '').split('.'); |
| return x[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,'$1,') |
| + (x.length > 1 ? ('.' + x[1]) : ''); |
| } |
| |
| return { |
| parsePercent : parsePercent, |
| parseCenter : parseCenter, |
| parseRadius : parseRadius, |
| addCommas : addCommas |
| }; |
| }); |
| /** |
| * echarts组件基类 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/base',['require','../config','../util/ecQuery','../util/number','zrender/tool/util','zrender/tool/env'],function (require) { |
| var ecConfig = require('../config'); |
| var ecQuery = require('../util/ecQuery'); |
| var number = require('../util/number'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function Base(ecTheme, messageCenter, zr, option, myChart){ |
| this.ecTheme = ecTheme; |
| this.messageCenter = messageCenter; |
| this.zr =zr; |
| this.option = option; |
| this.series = option.series; |
| this.myChart = myChart; |
| this.component = myChart.component; |
| |
| this._zlevelBase = this.getZlevelBase(); |
| this.shapeList = []; |
| this.effectList = []; |
| |
| var self = this; |
| self.hoverConnect = function (param) { |
| var target = (param.target || {}).hoverConnect; |
| if (target) { |
| var zlevel = 10; |
| var shape; |
| if (!(target instanceof Array)) { |
| shape = self.getShapeById(target); |
| if (shape) { |
| self.zr.addHoverShape(shape); |
| zlevel = Math.min(zlevel, shape.zlevel); |
| } |
| } |
| else { |
| for (var i = 0, l = target.length; i < l; i++) { |
| shape = self.getShapeById(target[i]); |
| self.zr.addHoverShape(shape); |
| zlevel = Math.min(zlevel, shape.zlevel); |
| } |
| } |
| if (zlevel < param.target.zlevel) { |
| self.zr.addHoverShape(param.target); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * 基类方法 |
| */ |
| Base.prototype = { |
| canvasSupported : require('zrender/tool/env').canvasSupported, |
| /** |
| * 获取zlevel基数配置 |
| * @param {Object} contentType |
| */ |
| getZlevelBase : function (contentType) { |
| contentType = contentType || this.type + ''; |
| |
| switch (contentType) { |
| case ecConfig.COMPONENT_TYPE_GRID : |
| case ecConfig.COMPONENT_TYPE_AXIS_CATEGORY : |
| case ecConfig.COMPONENT_TYPE_AXIS_VALUE : |
| case ecConfig.COMPONENT_TYPE_POLAR : |
| return 0; |
| |
| case ecConfig.CHART_TYPE_LINE : |
| case ecConfig.CHART_TYPE_BAR : |
| case ecConfig.CHART_TYPE_SCATTER : |
| case ecConfig.CHART_TYPE_PIE : |
| case ecConfig.CHART_TYPE_RADAR : |
| case ecConfig.CHART_TYPE_MAP : |
| case ecConfig.CHART_TYPE_K : |
| case ecConfig.CHART_TYPE_CHORD: |
| case ecConfig.CHART_TYPE_GUAGE: |
| case ecConfig.CHART_TYPE_FUNNEL: |
| return 2; |
| |
| case ecConfig.COMPONENT_TYPE_LEGEND : |
| case ecConfig.COMPONENT_TYPE_DATARANGE: |
| case ecConfig.COMPONENT_TYPE_DATAZOOM : |
| case ecConfig.COMPONENT_TYPE_TIMELINE : |
| return 4; |
| |
| case ecConfig.CHART_TYPE_ISLAND : |
| return 5; |
| |
| case ecConfig.COMPONENT_TYPE_TOOLBOX : |
| case ecConfig.COMPONENT_TYPE_TITLE : |
| return 6; |
| |
| // ecConfig.EFFECT_ZLEVEL = 7; |
| |
| case ecConfig.COMPONENT_TYPE_TOOLTIP : |
| return 8; |
| |
| default : |
| return 0; |
| } |
| }, |
| |
| /** |
| * 参数修正&默认值赋值 |
| * @param {Object} opt 参数 |
| * |
| * @return {Object} 修正后的参数 |
| */ |
| reformOption : function (opt) { |
| return zrUtil.merge( |
| opt || {}, |
| zrUtil.clone(this.ecTheme[this.type] || {}) |
| ); |
| }, |
| |
| /** |
| * css类属性数组补全,如padding,margin等~ |
| */ |
| reformCssArray : function (p) { |
| if (p instanceof Array) { |
| switch (p.length + '') { |
| case '4': |
| return p; |
| case '3': |
| return [p[0], p[1], p[2], p[1]]; |
| case '2': |
| return [p[0], p[1], p[0], p[1]]; |
| case '1': |
| return [p[0], p[0], p[0], p[0]]; |
| case '0': |
| return [0, 0, 0, 0]; |
| } |
| } |
| else { |
| return [p, p, p, p]; |
| } |
| }, |
| |
| getShapeById : function(id) { |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| if (this.shapeList[i].id == id) { |
| return this.shapeList[i]; |
| } |
| } |
| return null; |
| }, |
| |
| /** |
| * 获取自定义和默认配置合并后的字体设置 |
| */ |
| getFont : function (textStyle) { |
| var finalTextStyle = zrUtil.merge( |
| zrUtil.clone(textStyle) || {}, |
| this.ecTheme.textStyle |
| ); |
| return finalTextStyle.fontStyle + ' ' |
| + finalTextStyle.fontWeight + ' ' |
| + finalTextStyle.fontSize + 'px ' |
| + finalTextStyle.fontFamily; |
| }, |
| |
| getItemStyleColor : function (itemColor, seriesIndex, dataIndex, data) { |
| return typeof itemColor == 'function' |
| ? itemColor(seriesIndex, dataIndex, data) : itemColor; |
| |
| }, |
| |
| // 亚像素优化 |
| subPixelOptimize : function (position, lineWidth) { |
| if (lineWidth % 2 == 1) { |
| //position += position == Math.ceil(position) ? 0.5 : 0; |
| position = Math.floor(position) + 0.5; |
| } |
| else { |
| position = Math.round(position); |
| } |
| return position; |
| }, |
| |
| |
| resize : function () { |
| this.refresh && this.refresh(); |
| this.clearEffectShape && this.clearEffectShape(true); |
| var self = this; |
| setTimeout(function(){ |
| self.animationEffect && self.animationEffect(); |
| },200); |
| }, |
| |
| /** |
| * 清除图形数据,实例仍可用 |
| */ |
| clear :function () { |
| this.clearEffectShape && this.clearEffectShape(); |
| this.zr && this.zr.delShape(this.shapeList); |
| this.shapeList = []; |
| }, |
| |
| /** |
| * 释放后实例不可用 |
| */ |
| dispose : function () { |
| this.clear(); |
| this.shapeList = null; |
| this.effectList = null; |
| }, |
| |
| query : ecQuery.query, |
| deepQuery : ecQuery.deepQuery, |
| deepMerge : ecQuery.deepMerge, |
| |
| parsePercent : number.parsePercent, |
| parseCenter : number.parseCenter, |
| parseRadius : number.parseRadius, |
| numAddCommas : number.addCommas |
| }; |
| |
| return Base; |
| }); |
| |
| /** |
| * zrender: 数学辅助类 |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * sin:正弦函数,自动缓存 |
| * cos:余弦函数,自动缓存 |
| * degreeToRadian:角度转弧度 |
| * radianToDegree:弧度转角度 |
| */ |
| define( |
| 'zrender/tool/math',[],function() { |
| /* |
| var _cache = { |
| sin : {}, //sin缓存 |
| cos : {} //cos缓存 |
| }; |
| */ |
| var _radians = Math.PI / 180; |
| |
| /** |
| * @param angle 弧度(角度)参数 |
| * @param isDegrees angle参数是否为角度计算,默认为false,angle为以弧度计量的角度 |
| */ |
| function sin(angle, isDegrees) { |
| return Math.sin(isDegrees ? angle * _radians : angle); |
| /* |
| angle = (isDegrees ? angle * _radians : angle).toFixed(4); |
| if(typeof _cache.sin[angle] == 'undefined') { |
| _cache.sin[angle] = Math.sin(angle); |
| } |
| return _cache.sin[angle]; |
| */ |
| } |
| |
| /** |
| * @param radians 弧度参数 |
| */ |
| function cos(angle, isDegrees) { |
| return Math.cos(isDegrees ? angle * _radians : angle); |
| /* |
| angle = (isDegrees ? angle * _radians : angle).toFixed(4); |
| if(typeof _cache.cos[angle] == 'undefined') { |
| _cache.cos[angle] = Math.cos(angle); |
| } |
| return _cache.cos[angle]; |
| */ |
| } |
| |
| /** |
| * 角度转弧度 |
| * @param {Object} angle |
| */ |
| function degreeToRadian(angle) { |
| return angle * _radians; |
| } |
| |
| /** |
| * 弧度转角度 |
| * @param {Object} angle |
| */ |
| function radianToDegree(angle) { |
| return angle / _radians; |
| } |
| |
| return { |
| sin : sin, |
| cos : cos, |
| degreeToRadian : degreeToRadian, |
| radianToDegree : radianToDegree |
| }; |
| } |
| ); |
| /** |
| * zrender |
| * |
| * @author sushuang (宿爽, sushuang@baidu.com) |
| * |
| * shape类:n角星(n>3) |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'star', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,n角星外接圆心横坐标 |
| y : {number}, // 必须,n角星外接圆心纵坐标 |
| r : {number}, // 必须,n角星外接圆半径 |
| r0 : {number}, // n角星内部顶点(凹点)的外接圆半径, |
| // 如果不指定此参数,则自动计算:取相隔外部顶点连线的交点作内部顶点 |
| n : {number}, // 必须,指明几角星 |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| lineJoin : {string}, // 默认为miter,线段连接样式。miter | round | bevel |
| |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为outside,附加文本位置。 |
| // outside | inside |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'star', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| r : 150, |
| n : 5, |
| color : '#eee' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Star',['require','../tool/math','./Base','../tool/util'],function (require) { |
| |
| var math = require('../tool/math'); |
| var sin = math.sin; |
| var cos = math.cos; |
| var PI = Math.PI; |
| |
| var Base = require('./Base'); |
| |
| function Star(options) { |
| Base.call(this, options); |
| } |
| |
| Star.prototype = { |
| type: 'star', |
| |
| /** |
| * 创建n角星(n>3)路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| var n = style.n; |
| if (!n || n < 2) { return; } |
| |
| var x = style.x; |
| var y = style.y; |
| var r = style.r; |
| var r0 = style.r0; |
| |
| // 如果未指定内部顶点外接圆半径,则自动计算 |
| if (r0 == null) { |
| r0 = n > 4 |
| // 相隔的外部顶点的连线的交点, |
| // 被取为内部交点,以此计算r0 |
| ? r * cos(2 * PI / n) / cos(PI / n) |
| // 二三四角星的特殊处理 |
| : r / 3; |
| } |
| |
| var dStep = PI / n; |
| var deg = -PI / 2; |
| var xStart = x + r * cos(deg); |
| var yStart = y + r * sin(deg); |
| deg += dStep; |
| |
| // 记录边界点,用于判断inside |
| var pointList = style.pointList = []; |
| pointList.push([xStart, yStart]); |
| for (var i = 0, end = n * 2 - 1, ri; i < end; i ++) { |
| ri = i % 2 === 0 ? r0 : r; |
| pointList.push([x + ri * cos(deg), y + ri * sin(deg)]); |
| deg += dStep; |
| } |
| pointList.push([xStart, yStart]); |
| |
| // 绘制 |
| ctx.moveTo(pointList[0][0], pointList[0][1]); |
| for (var i = 0; i < pointList.length; i ++) { |
| ctx.lineTo(pointList[i][0], pointList[i][1]); |
| } |
| |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| style.__rect = { |
| x : Math.round(style.x - style.r - lineWidth / 2), |
| y : Math.round(style.y - style.r - lineWidth / 2), |
| width : style.r * 2 + lineWidth, |
| height : style.r * 2 + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Star, Base); |
| return Star; |
| } |
| ); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:心形 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'heart', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,心形内部尖端横坐标 |
| y : {number}, // 必须,心形内部尖端纵坐标 |
| a : {number}, // 必须,心形横宽(中轴线到水平边缘最宽处距离) |
| b : {number}, // 必须,心形纵高(内尖到外尖距离) |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为outside,附加文本位置。 |
| // outside | inside |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'heart', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| a : 50, |
| b : 80, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Heart',['require','./Base','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| |
| function Heart(options) { |
| Base.call(this, options); |
| } |
| |
| Heart.prototype = { |
| type: 'heart', |
| |
| /** |
| * 创建扇形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| ctx.moveTo(style.x, style.y); |
| ctx.bezierCurveTo( |
| style.x + style.a / 2, |
| style.y - style.b * 2 / 3, |
| style.x + style.a * 2, |
| style.y + style.b / 3, |
| style.x, |
| style.y + style.b |
| ); |
| ctx.bezierCurveTo( |
| style.x - style.a * 2, |
| style.y + style.b / 3, |
| style.x - style.a / 2, |
| style.y - style.b * 2 / 3, |
| style.x, |
| style.y |
| ); |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| style.__rect = { |
| x : Math.round(style.x - style.a - lineWidth / 2), |
| y : Math.round(style.y - style.b / 4 - lineWidth / 2), |
| width : style.a * 2 + lineWidth, |
| height : style.b * 5 / 4 + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Heart, Base); |
| return Heart; |
| } |
| ); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:水滴 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'heart', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,水滴中心横坐标 |
| y : {number}, // 必须,水滴中心纵坐标 |
| a : {number}, // 必须,水滴横宽(中心到水平边缘最宽处距离) |
| b : {number}, // 必须,水滴纵高(中心到尖端距离) |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为outside,附加文本位置。 |
| // outside | inside |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'droplet', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| a : 50, |
| b : 80, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Droplet',['require','./Base','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| |
| function Droplet(options) { |
| Base.call(this, options); |
| } |
| |
| Droplet.prototype = { |
| type: 'droplet', |
| |
| /** |
| * 创建扇形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| ctx.moveTo(style.x, style.y + style.a); |
| ctx.bezierCurveTo( |
| style.x + style.a, |
| style.y + style.a, |
| style.x + style.a * 3 / 2, |
| style.y - style.a / 3, |
| style.x, |
| style.y - style.b |
| ); |
| ctx.bezierCurveTo( |
| style.x - style.a * 3 / 2, |
| style.y - style.a / 3, |
| style.x - style.a, |
| style.y + style.a, |
| style.x, |
| style.y + style.a |
| ); |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| style.__rect = { |
| x : Math.round(style.x - style.a - lineWidth / 2), |
| y : Math.round(style.y - style.b - lineWidth / 2), |
| width : style.a * 2 + lineWidth, |
| height : style.a + style.b + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Droplet, Base); |
| return Droplet; |
| } |
| ); |
| /** |
| * echarts扩展zrender shape |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:icon |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'icon', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,左上角横坐标 |
| y : {number}, // 必须,左上角纵坐标 |
| width : {number}, // 必须,宽度 |
| height : {number}, // 必须,高度 |
| iconType : {string}, // 必须,icon类型 |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| */ |
| define('echarts/util/shape/Icon',['require','zrender/tool/util','zrender/shape/Star','zrender/shape/Heart','zrender/shape/Droplet','zrender/shape/Image','zrender/shape/Base'],function (require) { |
| var zrUtil = require('zrender/tool/util'); |
| |
| function _iconMark(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| ctx.moveTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x + 5 * dx, style.y + 14 * dy); |
| ctx.lineTo(style.x + style.width, style.y + 3 * dy); |
| ctx.lineTo(style.x + 13 * dx, style.y); |
| ctx.lineTo(style.x + 2 * dx, style.y + 11 * dy); |
| ctx.lineTo(style.x, style.y + style.height); |
| |
| ctx.moveTo(style.x + 6 * dx, style.y + 10 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 2 * dy); |
| |
| ctx.moveTo(style.x + 10 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + style.width, style.y + 13 * dy); |
| |
| ctx.moveTo(style.x + 13 * dx, style.y + 10 * dy); |
| ctx.lineTo(style.x + 13 * dx, style.y + style.height); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconMarkUndo(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| ctx.moveTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x + 5 * dx, style.y + 14 * dy); |
| ctx.lineTo(style.x + style.width, style.y + 3 * dy); |
| ctx.lineTo(style.x + 13 * dx, style.y); |
| ctx.lineTo(style.x + 2 * dx, style.y + 11 * dy); |
| ctx.lineTo(style.x, style.y + style.height); |
| |
| ctx.moveTo(style.x + 6 * dx, style.y + 10 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 2 * dy); |
| |
| ctx.moveTo(style.x + 10 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + style.width, style.y + 13 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconMarkClear(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| |
| ctx.moveTo(style.x + 4 * dx, style.y + 15 * dy); |
| ctx.lineTo(style.x + 9 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 8 * dy); |
| ctx.lineTo(style.x + 11 * dx, style.y + 5 * dy); |
| ctx.lineTo(style.x + 6 * dx, style.y + 10 * dy); |
| ctx.lineTo(style.x + 4 * dx, style.y + 15 * dy); |
| |
| ctx.moveTo(style.x + 5 * dx, style.y); |
| ctx.lineTo(style.x + 11 * dx, style.y); |
| ctx.moveTo(style.x + 5 * dx, style.y + dy); |
| ctx.lineTo(style.x + 11 * dx, style.y + dy); |
| ctx.moveTo(style.x, style.y + 2 * dy); |
| ctx.lineTo(style.x + style.width, style.y + 2 * dy); |
| |
| ctx.moveTo(style.x, style.y + 5 * dy); |
| ctx.lineTo(style.x + 3 * dx, style.y + style.height); |
| ctx.lineTo(style.x + 13 * dx, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y + 5 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconDataZoom(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| |
| ctx.moveTo(style.x, style.y + 3 * dy); |
| ctx.lineTo(style.x + 6 * dx, style.y + 3 * dy); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y); |
| ctx.lineTo(style.x + 3 * dx, style.y + 6 * dy); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y + 8 * dy); |
| ctx.lineTo(style.x + 3 * dx, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y + 3 * dy); |
| ctx.lineTo(style.x + 8 * dx, style.y + 3 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconDataZoomReset(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| |
| ctx.moveTo(style.x + 6 * dx, style.y); |
| ctx.lineTo(style.x + 2 * dx, style.y + 3 * dy); |
| ctx.lineTo(style.x + 6 * dx, style.y + 6 * dy); |
| |
| ctx.moveTo(style.x + 2 * dx, style.y + 3 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 3 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 11 * dy); |
| |
| ctx.moveTo(style.x + 2 * dx, style.y + 5 * dy); |
| ctx.lineTo(style.x + 2 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 13 * dy); |
| |
| ctx.moveTo(style.x + 10 * dx, style.y + 10 * dy); |
| ctx.lineTo(style.x + 14 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + 10 * dx, style.y + style.height); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconRestore(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| var r = style.width / 2; |
| |
| ctx.lineWidth = 1.5; |
| |
| ctx.arc(style.x + r, style.y + r, r - dx, 0, Math.PI * 2 / 3); |
| ctx.moveTo(style.x + 3 * dx, style.y + style.height); |
| ctx.lineTo(style.x + 0 * dx, style.y + 12 * dy); |
| ctx.lineTo(style.x + 5 * dx, style.y + 11 * dy); |
| |
| ctx.moveTo(style.x, style.y + 8 * dy); |
| ctx.arc(style.x + r, style.y + r, r - dx, Math.PI, Math.PI * 5 / 3); |
| ctx.moveTo(style.x + 13 * dx, style.y); |
| ctx.lineTo(style.x + style.width, style.y + 4 * dy); |
| ctx.lineTo(style.x + 11 * dx, style.y + 5 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconLineChart(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| |
| ctx.moveTo(style.x, style.y); |
| ctx.lineTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y + style.height); |
| |
| ctx.moveTo(style.x + 2 * dx, style.y + 14 * dy); |
| ctx.lineTo(style.x + 7 * dx, style.y + 6 * dy); |
| ctx.lineTo(style.x + 11 * dx, style.y + 11 * dy); |
| ctx.lineTo(style.x + 15 * dx, style.y + 2 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconBarChart(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| |
| ctx.moveTo(style.x, style.y); |
| ctx.lineTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y + style.height); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y + 14 * dy); |
| ctx.lineTo(style.x + 3 * dx, style.y + 6 * dy); |
| ctx.lineTo(style.x + 4 * dx, style.y + 6 * dy); |
| ctx.lineTo(style.x + 4 * dx, style.y + 14 * dy); |
| ctx.moveTo(style.x + 7 * dx, style.y + 14 * dy); |
| ctx.lineTo(style.x + 7 * dx, style.y + 2 * dy); |
| ctx.lineTo(style.x + 8 * dx, style.y + 2 * dy); |
| ctx.lineTo(style.x + 8 * dx, style.y + 14 * dy); |
| ctx.moveTo(style.x + 11 * dx, style.y + 14 * dy); |
| ctx.lineTo(style.x + 11 * dx, style.y + 9 * dy); |
| ctx.lineTo(style.x + 12 * dx, style.y + 9 * dy); |
| ctx.lineTo(style.x + 12 * dx, style.y + 14 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconStackChart(ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| var dy = Math.round(height / 3); |
| var len = 3; |
| while (len--) { |
| ctx.rect(x, y + dy * len + 2, width, 2); |
| } |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconTiledChart(ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| var dx = Math.round(width / 3); |
| var len = 3; |
| while (len--) { |
| ctx.rect(x + dx * len, y, 2, height); |
| } |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconDataView(ctx, style) { |
| var dx = style.width / 16; |
| |
| ctx.moveTo(style.x + dx, style.y); |
| ctx.lineTo(style.x + dx, style.y + style.height); |
| ctx.lineTo(style.x + 15 * dx, style.y + style.height); |
| ctx.lineTo(style.x + 15 * dx, style.y); |
| ctx.lineTo(style.x + dx, style.y); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y + 3 * dx); |
| ctx.lineTo(style.x + 13 * dx, style.y + 3 * dx); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y + 6 * dx); |
| ctx.lineTo(style.x + 13 * dx, style.y + 6 * dx); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y + 9 * dx); |
| ctx.lineTo(style.x + 13 * dx, style.y + 9 * dx); |
| |
| ctx.moveTo(style.x + 3 * dx, style.y + 12 * dx); |
| ctx.lineTo(style.x + 9 * dx, style.y + 12 * dx); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconSave(ctx, style) { |
| var dx = style.width / 16; |
| var dy = style.height / 16; |
| |
| ctx.moveTo(style.x, style.y); |
| ctx.lineTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y + style.height); |
| ctx.lineTo(style.x + style.width, style.y); |
| ctx.lineTo(style.x, style.y); |
| |
| ctx.moveTo(style.x + 4 * dx, style.y); |
| ctx.lineTo(style.x + 4 * dx, style.y + 8 * dy); |
| ctx.lineTo(style.x + 12 * dx, style.y + 8 * dy); |
| ctx.lineTo(style.x + 12 * dx, style.y); |
| |
| ctx.moveTo(style.x + 6 * dx, style.y + 11 * dy); |
| ctx.lineTo(style.x + 6 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + 10 * dx, style.y + 13 * dy); |
| ctx.lineTo(style.x + 10 * dx, style.y + 11 * dy); |
| ctx.lineTo(style.x + 6 * dx, style.y + 11 * dy); |
| |
| // 避免自动闭合路径 |
| ctx.moveTo(style.x, style.y); |
| } |
| |
| function _iconCross(ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| ctx.moveTo(x, y + height / 2); |
| ctx.lineTo(x + width, y + height / 2); |
| |
| ctx.moveTo(x + width / 2, y); |
| ctx.lineTo(x + width / 2, y + height); |
| } |
| |
| function _iconCircle(ctx, style) { |
| var width = style.width / 2; |
| var height = style.height / 2; |
| var r = Math.min(width, height); |
| ctx.moveTo( |
| style.x + width + r, |
| style.y + height |
| ); |
| ctx.arc( |
| style.x + width, |
| style.y + height, |
| r, |
| 0, |
| Math.PI * 2 |
| ); |
| } |
| |
| function _iconRectangle(ctx, style) { |
| ctx.rect(style.x, style.y, style.width, style.height); |
| } |
| |
| function _iconTriangle(ctx, style) { |
| var width = style.width / 2; |
| var height = style.height / 2; |
| var x = style.x + width; |
| var y = style.y + height; |
| var symbolSize = Math.min(width, height); |
| ctx.moveTo(x, y - symbolSize); |
| ctx.lineTo(x + symbolSize, y + symbolSize); |
| ctx.lineTo(x - symbolSize, y + symbolSize); |
| ctx.lineTo(x, y - symbolSize); |
| } |
| |
| function _iconDiamond(ctx, style) { |
| var width = style.width / 2; |
| var height = style.height / 2; |
| var x = style.x + width; |
| var y = style.y + height; |
| var symbolSize = Math.min(width, height); |
| ctx.moveTo(x, y - symbolSize); |
| ctx.lineTo(x + symbolSize, y); |
| ctx.lineTo(x, y + symbolSize); |
| ctx.lineTo(x - symbolSize, y); |
| ctx.lineTo(x, y - symbolSize); |
| } |
| |
| function _iconArrow(ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var dx = style.width / 16; |
| ctx.moveTo(x + 8 * dx, y); |
| ctx.lineTo(x + dx, y + style.height); |
| ctx.lineTo(x + 8 * dx, y + style.height / 4 * 3); |
| ctx.lineTo(x + 15 * dx, y + style.height); |
| ctx.lineTo(x + 8 * dx, y); |
| } |
| |
| function _iconStar(ctx, style) { |
| var StarShape = require('zrender/shape/Star'); |
| var width = style.width / 2; |
| var height = style.height / 2; |
| StarShape.prototype.buildPath(ctx, { |
| x : style.x + width, |
| y : style.y + height, |
| r : Math.min(width, height), |
| n : style.n || 5 |
| }); |
| } |
| |
| function _iconHeart(ctx, style) { |
| var HeartShape = require('zrender/shape/Heart'); |
| HeartShape.prototype.buildPath(ctx, { |
| x : style.x + style.width / 2, |
| y : style.y + style.height * 0.2, |
| a : style.width / 2, |
| b : style.height * 0.8 |
| }); |
| } |
| |
| function _iconDroplet(ctx, style) { |
| var DropletShape = require('zrender/shape/Droplet'); |
| DropletShape.prototype.buildPath(ctx, { |
| x : style.x + style.width * 0.5, |
| y : style.y + style.height * 0.5, |
| a : style.width * 0.5, |
| b : style.height * 0.8 |
| }); |
| } |
| |
| function _iconPin(ctx, style) { |
| var x = style.x; |
| var y = style.y - style.height / 2 * 1.5; |
| var width = style.width / 2; |
| var height = style.height / 2; |
| var r = Math.min(width, height); |
| ctx.arc( |
| x + width, |
| y + height, |
| r, |
| Math.PI / 5 * 4, |
| Math.PI / 5 |
| ); |
| ctx.lineTo(x + width, y + height + r * 1.5); |
| } |
| |
| function _iconImage(ctx, style) { |
| setTimeout(function (){ |
| var ImageShape = require('zrender/shape/Image'); |
| var itemShape = new ImageShape({ |
| style : style |
| }); |
| itemShape.brush(ctx); |
| },100); |
| } |
| |
| var Base = require('zrender/shape/Base'); |
| |
| function Icon(options) { |
| Base.call(this, options); |
| } |
| |
| Icon.prototype = { |
| type : 'icon', |
| iconLibrary : { |
| mark : _iconMark, |
| markUndo : _iconMarkUndo, |
| markClear : _iconMarkClear, |
| dataZoom : _iconDataZoom, |
| dataZoomReset : _iconDataZoomReset, |
| restore : _iconRestore, |
| lineChart : _iconLineChart, |
| barChart : _iconBarChart, |
| stackChart : _iconStackChart, |
| tiledChart : _iconTiledChart, |
| dataView : _iconDataView, |
| saveAsImage : _iconSave, |
| |
| cross : _iconCross, |
| circle : _iconCircle, |
| rectangle : _iconRectangle, |
| triangle : _iconTriangle, |
| diamond : _iconDiamond, |
| arrow : _iconArrow, |
| star : _iconStar, |
| heart : _iconHeart, |
| droplet : _iconDroplet, |
| pin : _iconPin, |
| image : _iconImage |
| }, |
| /** |
| * 创建矩形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| if (this.iconLibrary[style.iconType]) { |
| this.iconLibrary[style.iconType](ctx, style); |
| } |
| else { |
| ctx.moveTo(style.x, style.y); |
| ctx.lineTo(style.x + style.width, style.y); |
| ctx.lineTo(style.x + style.width, style.y + style.height); |
| ctx.lineTo(style.x, style.y + style.height); |
| ctx.lineTo(style.x, style.y); |
| } |
| |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function (style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| // pin比较特殊,让尖端在目标x,y上 |
| style.__rect = { |
| x : Math.round(style.x), |
| y : Math.round(style.y - (style.iconType == 'pin' |
| ? (style.height / 2 * 1.5) : 0) |
| ), |
| width : style.width, |
| height : style.height |
| }; |
| |
| return style.__rect; |
| }, |
| |
| isCover : function (x, y) { |
| var originPos = this.getTansform(x, y); |
| x = originPos[0]; |
| y = originPos[1]; |
| |
| // 快速预判并保留判断矩形 |
| var rect = this.style.__rect; |
| if (!rect) { |
| rect = this.style.__rect = this.getRect(this.style); |
| } |
| // 提高交互体验,太小的图形包围盒四向扩大4px |
| var delta = (rect.height < 8 || rect.width < 8 ) ? 4 : 0; |
| if (x >= rect.x - delta |
| && x <= (rect.x + rect.width + delta) |
| && y >= rect.y - delta |
| && y <= (rect.y + rect.height + delta) |
| ) { |
| // 矩形内 |
| return true; |
| } |
| else { |
| return false; |
| } |
| } |
| }; |
| |
| zrUtil.inherits(Icon, Base); |
| |
| return Icon; |
| }); |
| /** |
| * 虚线lineTo |
| * |
| * author: Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| define( |
| 'zrender/shape/util/dashedLineTo',[],function (/* require */) { |
| |
| var dashPattern = [5, 5]; |
| /** |
| * 虚线lineTo |
| */ |
| return function (ctx, x1, y1, x2, y2, dashLength) { |
| // http://msdn.microsoft.com/en-us/library/ie/dn265063(v=vs.85).aspx |
| if (ctx.setLineDash) { |
| dashPattern[0] = dashPattern[1] = dashLength; |
| ctx.setLineDash(dashPattern); |
| ctx.moveTo(x1, y1); |
| ctx.lineTo(x2, y2); |
| return; |
| } |
| |
| dashLength = typeof dashLength != 'number' |
| ? 5 |
| : dashLength; |
| |
| var dx = x2 - x1; |
| var dy = y2 - y1; |
| var numDashes = Math.floor( |
| Math.sqrt(dx * dx + dy * dy) / dashLength |
| ); |
| dx = dx / numDashes; |
| dy = dy / numDashes; |
| var flag = true; |
| for (var i = 0; i < numDashes; ++i) { |
| if (flag) { |
| ctx.moveTo(x1, y1); |
| } else { |
| ctx.lineTo(x1, y1); |
| } |
| flag = !flag; |
| x1 += dx; |
| y1 += dy; |
| } |
| ctx.lineTo(x2, y2); |
| }; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:直线 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'line', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| xStart : {number}, // 必须,起点横坐标 |
| yStart : {number}, // 必须,起点纵坐标 |
| xEnd : {number}, // 必须,终点横坐标 |
| yEnd : {number}, // 必须,终点纵坐标 |
| strokeColor : {color}, // 默认为'#000',线条颜色(轮廓),支持rgba |
| lineType : {string}, // 默认为solid,线条类型,solid | dashed | dotted |
| lineWidth : {number}, // 默认为1,线条宽度 |
| lineCap : {string}, // 默认为butt,线帽样式。butt | round | square |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为end,附加文本位置。 |
| // inside | start | end |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#000' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'line', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| xStart : 100, |
| yStart : 100, |
| xEnd : 200, |
| yEnd : 200, |
| strokeColor : '#eee', |
| lineWidth : 20, |
| text : 'Baidu' |
| }, |
| myName : 'kener', //可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Line',['require','./Base','./util/dashedLineTo','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| var dashedLineTo = require('./util/dashedLineTo'); |
| |
| function Line(options) { |
| this.brushTypeOnly = 'stroke'; //线条只能描边,填充后果自负 |
| this.textPosition = 'end'; |
| Base.call(this, options); |
| } |
| |
| Line.prototype = { |
| type: 'line', |
| |
| /** |
| * 创建线条路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| if (!style.lineType || style.lineType == 'solid') { |
| //默认为实线 |
| ctx.moveTo(style.xStart, style.yStart); |
| ctx.lineTo(style.xEnd, style.yEnd); |
| } |
| else if (style.lineType == 'dashed' |
| || style.lineType == 'dotted' |
| ) { |
| var dashLength = (style.lineWidth || 1) |
| * (style.lineType == 'dashed' ? 5 : 1); |
| dashedLineTo( |
| ctx, |
| style.xStart, style.yStart, |
| style.xEnd, style.yEnd, |
| dashLength |
| ); |
| } |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth = style.lineWidth || 1; |
| style.__rect = { |
| x : Math.min(style.xStart, style.xEnd) - lineWidth, |
| y : Math.min(style.yStart, style.yEnd) - lineWidth, |
| width : Math.abs(style.xStart - style.xEnd) |
| + lineWidth, |
| height : Math.abs(style.yStart - style.yEnd) |
| + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Line, Base); |
| return Line; |
| } |
| ); |
| /** |
| * zrender: 向量操作类 |
| * |
| * author : https://github.com/pissang |
| */ |
| define( |
| 'zrender/tool/vector',[],function() { |
| var ArrayCtor = typeof Float32Array === 'undefined' |
| ? Array |
| : Float32Array; |
| |
| var vector = { |
| create : function(x, y) { |
| var out = new ArrayCtor(2); |
| out[0] = x || 0; |
| out[1] = y || 0; |
| return out; |
| }, |
| copy : function(out, v) { |
| out[0] = v[0]; |
| out[1] = v[1]; |
| }, |
| set : function(out, a, b) { |
| out[0] = a; |
| out[1] = b; |
| }, |
| add : function(out, v1, v2) { |
| out[0] = v1[0] + v2[0]; |
| out[1] = v1[1] + v2[1]; |
| return out; |
| }, |
| scaleAndAdd : function(out, v1, v2, a) { |
| out[0] = v1[0] + v2[0] * a; |
| out[1] = v1[1] + v2[1] * a; |
| return out; |
| }, |
| sub : function(out, v1, v2) { |
| out[0] = v1[0] - v2[0]; |
| out[1] = v1[1] - v2[1]; |
| return out; |
| }, |
| length : function(v) { |
| return Math.sqrt(this.lengthSquare(v)); |
| }, |
| lengthSquare : function(v) { |
| return v[0] * v[0] + v[1] * v[1]; |
| }, |
| mul : function(out, v1, v2) { |
| out[0] = v1[0] * v2[0]; |
| out[1] = v1[1] * v2[1]; |
| return out; |
| }, |
| dot : function(v1, v2) { |
| return v1[0] * v2[0] + v1[1] * v2[1]; |
| }, |
| scale : function(out, v, s) { |
| out[0] = v[0] * s; |
| out[1] = v[1] * s; |
| return out; |
| }, |
| normalize : function(out, v) { |
| var d = vector.length(v); |
| if(d === 0){ |
| out[0] = 0; |
| out[1] = 0; |
| }else{ |
| out[0] = v[0]/d; |
| out[1] = v[1]/d; |
| } |
| return out; |
| }, |
| distance : function(v1, v2) { |
| return Math.sqrt( |
| (v1[0] - v2[0]) * (v1[0] - v2[0]) + |
| (v1[1] - v2[1]) * (v1[1] - v2[1]) |
| ); |
| }, |
| negate : function(out, v) { |
| out[0] = -v[0]; |
| out[1] = -v[1]; |
| }, |
| middle : function(out, v1, v2) { |
| out[0] = (v1[0] + v2[0])/2; |
| out[1] = (v1[1] + v2[1])/2; |
| return out; |
| }, |
| applyTransform: function(out, v, m) { |
| var x = v[0]; |
| var y = v[1]; |
| out[0] = m[0] * x + m[2] * y + m[4]; |
| out[1] = m[1] * x + m[3] * y + m[5]; |
| return out; |
| } |
| }; |
| |
| vector.len = vector.length; |
| vector.dist = vector.distance; |
| |
| return vector; |
| } |
| ); |
| /** |
| * 多线段平滑曲线 Catmull-Rom spline |
| * |
| * author: Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| |
| define( |
| 'zrender/shape/util/smoothSpline',['require','../../tool/vector'],function ( require ) { |
| var vector = require('../../tool/vector'); |
| |
| /** |
| * @inner |
| */ |
| function interpolate(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; |
| } |
| |
| /** |
| * 多线段平滑曲线 Catmull-Rom spline |
| */ |
| return function (points, isLoop) { |
| var len = points.length; |
| var ret = []; |
| |
| var distance = 0; |
| for (var i = 1; i < len; i++) { |
| distance += vector.distance(points[i-1], points[i]); |
| } |
| |
| var segs = distance / 5; |
| segs = segs < len ? len : segs; |
| for (var i = 0; i < segs; i++) { |
| var pos = i / (segs-1) * (isLoop ? len : len - 1); |
| var idx = Math.floor(pos); |
| |
| var w = pos - idx; |
| |
| var p0; |
| var p1 = points[idx % len]; |
| var p2; |
| var p3; |
| if (!isLoop) { |
| p0 = points[idx === 0 ? idx : idx - 1]; |
| p2 = points[idx > len - 2 ? len - 1 : idx + 1]; |
| p3 = points[idx > len - 3 ? len - 1 : idx + 2]; |
| } else { |
| p0 = points[(idx -1 + len) % len]; |
| p2 = points[(idx + 1) % len]; |
| p3 = points[(idx + 2) % len]; |
| } |
| |
| var w2 = w * w; |
| var w3 = w * w2; |
| |
| ret.push([ |
| interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3), |
| interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3) |
| ]); |
| } |
| return ret; |
| }; |
| } |
| ); |
| |
| /** |
| * 贝塞尔平滑曲线 |
| * |
| * author: Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * errorrik (errorrik@gmail.com) |
| */ |
| |
| define( |
| 'zrender/shape/util/smoothBezier',['require','../../tool/vector'],function ( require ) { |
| var vector = require('../../tool/vector'); |
| |
| /** |
| * 贝塞尔平滑曲线 |
| */ |
| return function (points, smooth, isLoop) { |
| var cps = []; |
| |
| var v = []; |
| var v1 = []; |
| var v2 = []; |
| var prevPoint; |
| var nextPoint; |
| |
| for (var i = 0, len = points.length; i < len; i++) { |
| var point = points[i]; |
| var prevPoint; |
| var nextPoint; |
| |
| if (isLoop) { |
| prevPoint = points[i ? i - 1 : len - 1]; |
| nextPoint = points[(i + 1) % len]; |
| } |
| else { |
| if (i === 0 || i === len - 1) { |
| cps.push(points[i]); |
| continue; |
| } |
| else { |
| prevPoint = points[i - 1]; |
| nextPoint = points[i + 1]; |
| } |
| } |
| |
| vector.sub(v, nextPoint, prevPoint); |
| |
| //use degree to scale the handle length |
| vector.scale(v, v, smooth); |
| |
| var d0 = vector.distance(point, prevPoint); |
| var d1 = vector.distance(point, nextPoint); |
| var sum = d0 + d1; |
| d0 /= sum; |
| d1 /= sum; |
| |
| vector.scale(v1, v, -d0); |
| vector.scale(v2, v, d1); |
| |
| cps.push(vector.add([], point, v1)); |
| cps.push(vector.add([], point, v2)); |
| } |
| |
| if (isLoop) { |
| cps.push(cps.shift()); |
| } |
| |
| return cps; |
| }; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:多边形 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'polygon', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| pointList : {Array}, // 必须,多边形各个顶角坐标 |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为top,附加文本位置。 |
| // inside | left | right | top | bottom |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'polygon', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| pointList : [[10, 10], [300, 20], [298, 400], [50, 450]] |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Polygon',['require','./Base','./util/smoothSpline','./util/smoothBezier','./util/dashedLineTo','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| var smoothSpline = require('./util/smoothSpline'); |
| var smoothBezier = require('./util/smoothBezier'); |
| var dashedLineTo = require('./util/dashedLineTo'); |
| |
| |
| function Polygon(options) { |
| Base.call(this, options); |
| } |
| |
| Polygon.prototype = { |
| type: 'polygon', |
| |
| /** |
| * 画刷 |
| * @param ctx 画布句柄 |
| * @param isHighlight 是否为高亮状态 |
| * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e) |
| * 让painter更新视图,base.brush没用,需要的话重载brush |
| */ |
| brush : function (ctx, isHighlight) { |
| var style = this.style; |
| if (isHighlight) { |
| // 根据style扩展默认高亮样式 |
| style = this.getHighlightStyle( |
| style, |
| this.highlightStyle || {} |
| ); |
| } |
| |
| ctx.save(); |
| this.setContext(ctx, style); |
| |
| // 设置transform |
| this.setTransform(ctx); |
| |
| // 先fill再stroke |
| var hasPath = false; |
| if (style.brushType == 'fill' |
| || style.brushType == 'both' |
| || typeof style.brushType == 'undefined' // 默认为fill |
| ) { |
| ctx.beginPath(); |
| if (style.lineType == 'dashed' |
| || style.lineType == 'dotted' |
| ) { |
| // 特殊处理,虚线围不成path,实线再build一次 |
| this.buildPath( |
| ctx, |
| { |
| lineType: 'solid', |
| lineWidth: style.lineWidth, |
| pointList: style.pointList |
| } |
| ); |
| hasPath = false; // 这个path不能用 |
| } |
| else { |
| this.buildPath(ctx, style); |
| hasPath = true; // 这个path能用 |
| } |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| |
| if (style.lineWidth > 0 |
| && (style.brushType == 'stroke' || style.brushType == 'both') |
| ) { |
| if (!hasPath) { |
| ctx.beginPath(); |
| this.buildPath(ctx, style); |
| ctx.closePath(); |
| } |
| ctx.stroke(); |
| } |
| |
| this.drawText(ctx, style, this.style); |
| |
| ctx.restore(); |
| |
| return; |
| }, |
| |
| /** |
| * 创建多边形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| // 虽然能重用brokenLine,但底层图形基于性能考虑,重复代码减少调用吧 |
| var pointList = style.pointList; |
| // 开始点和结束点重复 |
| /* |
| var start = pointList[0]; |
| var end = pointList[pointList.length-1]; |
| |
| if (start && end) { |
| if (start[0] == end[0] && |
| start[1] == end[1]) { |
| // 移除最后一个点 |
| pointList.pop(); |
| } |
| } |
| */ |
| |
| if (pointList.length < 2) { |
| // 少于2个点就不画了~ |
| return; |
| } |
| |
| if (style.smooth && style.smooth !== 'spline') { |
| var controlPoints = smoothBezier( |
| pointList, style.smooth, true |
| ); |
| |
| ctx.moveTo(pointList[0][0], pointList[0][1]); |
| var cp1; |
| var cp2; |
| var p; |
| var len = pointList.length; |
| for (var i = 0; i < len; i++) { |
| cp1 = controlPoints[i * 2]; |
| cp2 = controlPoints[i * 2 + 1]; |
| p = pointList[(i + 1) % len]; |
| ctx.bezierCurveTo( |
| cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1] |
| ); |
| } |
| } |
| else { |
| if (style.smooth === 'spline') { |
| pointList = smoothSpline(pointList, true); |
| } |
| |
| if (!style.lineType || style.lineType == 'solid') { |
| //默认为实线 |
| ctx.moveTo(pointList[0][0],pointList[0][1]); |
| for (var i = 1, l = pointList.length; i < l; i++) { |
| ctx.lineTo(pointList[i][0],pointList[i][1]); |
| } |
| ctx.lineTo(pointList[0][0], pointList[0][1]); |
| } |
| else if (style.lineType == 'dashed' |
| || style.lineType == 'dotted' |
| ) { |
| var dashLength = |
| style._dashLength |
| || (style.lineWidth || 1) |
| * (style.lineType == 'dashed' ? 5 : 1); |
| style._dashLength = dashLength; |
| ctx.moveTo(pointList[0][0],pointList[0][1]); |
| for (var i = 1, l = pointList.length; i < l; i++) { |
| dashedLineTo( |
| ctx, |
| pointList[i - 1][0], pointList[i - 1][1], |
| pointList[i][0], pointList[i][1], |
| dashLength |
| ); |
| } |
| dashedLineTo( |
| ctx, |
| pointList[pointList.length - 1][0], |
| pointList[pointList.length - 1][1], |
| pointList[0][0], |
| pointList[0][1], |
| dashLength |
| ); |
| } |
| } |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var minX = Number.MAX_VALUE; |
| var maxX = Number.MIN_VALUE; |
| var minY = Number.MAX_VALUE; |
| var maxY = Number.MIN_VALUE; |
| |
| var pointList = style.pointList; |
| for(var i = 0, l = pointList.length; i < l; i++) { |
| if (pointList[i][0] < minX) { |
| minX = pointList[i][0]; |
| } |
| if (pointList[i][0] > maxX) { |
| maxX = pointList[i][0]; |
| } |
| if (pointList[i][1] < minY) { |
| minY = pointList[i][1]; |
| } |
| if (pointList[i][1] > maxY) { |
| maxY = pointList[i][1]; |
| } |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| |
| style.__rect = { |
| x : Math.round(minX - lineWidth / 2), |
| y : Math.round(minY - lineWidth / 2), |
| width : maxX - minX + lineWidth, |
| height : maxY - minY + lineWidth |
| }; |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Polygon, Base); |
| return Polygon; |
| } |
| ); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:折线 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'brokenLine', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| pointList : {Array}, // 必须,各个顶角坐标 |
| smooth : {Number}, // 默认为0 |
| strokeColor : {color}, // 默认为'#000',线条颜色(轮廓),支持rgba |
| lineType : {string}, // 默认为solid,线条类型,solid | dashed | dotted |
| lineWidth : {number}, // 默认为1,线条宽度 |
| lineCap : {string}, // 默认为butt,线帽样式。butt | round | square |
| lineJoin : {string}, // 默认为miter,线段连接样式。miter | round | bevel |
| miterLimit : {number}, // 默认为10,最大斜接长度,仅当lineJoin为miter时生效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为end,附加文本位置。 |
| // start | end |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#000' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'brokenLine', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| pointList : [[10, 10], [300, 20], [298, 400], [50, 450]], |
| strokeColor : '#eee', |
| lineWidth : 20, |
| text : 'Baidu' |
| }, |
| myName : 'kener', //可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/BrokenLine',['require','./Base','./util/smoothSpline','./util/smoothBezier','./util/dashedLineTo','./Polygon','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| var smoothSpline = require('./util/smoothSpline'); |
| var smoothBezier = require('./util/smoothBezier'); |
| var dashedLineTo = require('./util/dashedLineTo'); |
| |
| function BrokenLine( options ) { |
| this.brushTypeOnly = 'stroke'; //线条只能描边,填充后果自负 |
| this.textPosition = 'end'; |
| Base.call(this, options); |
| } |
| |
| BrokenLine.prototype = { |
| type: 'broken-line', |
| |
| /** |
| * 创建多边形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| var pointList = style.pointList; |
| if (pointList.length < 2) { |
| // 少于2个点就不画了~ |
| return; |
| } |
| |
| var len = Math.min( |
| style.pointList.length, |
| Math.round(style.pointListLength || style.pointList.length) |
| ); |
| |
| if (style.smooth && style.smooth !== 'spline') { |
| var controlPoints = smoothBezier( |
| pointList, style.smooth |
| ); |
| |
| ctx.moveTo(pointList[0][0], pointList[0][1]); |
| var cp1; |
| var cp2; |
| var p; |
| for (var i = 0; i < len - 1; i++) { |
| cp1 = controlPoints[i * 2]; |
| cp2 = controlPoints[i * 2 + 1]; |
| p = pointList[i + 1]; |
| ctx.bezierCurveTo( |
| cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1] |
| ); |
| } |
| } |
| else { |
| if (style.smooth === 'spline') { |
| pointList = smoothSpline(pointList); |
| len = pointList.length; |
| } |
| if (!style.lineType || style.lineType == 'solid') { |
| //默认为实线 |
| ctx.moveTo(pointList[0][0],pointList[0][1]); |
| for (var i = 1; i < len; i++) { |
| ctx.lineTo(pointList[i][0],pointList[i][1]); |
| } |
| } |
| else if (style.lineType == 'dashed' |
| || style.lineType == 'dotted' |
| ) { |
| var dashLength = (style.lineWidth || 1) |
| * (style.lineType == 'dashed' ? 5 : 1); |
| ctx.moveTo(pointList[0][0],pointList[0][1]); |
| for (var i = 1; i < len; i++) { |
| dashedLineTo( |
| ctx, |
| pointList[i - 1][0], pointList[i - 1][1], |
| pointList[i][0], pointList[i][1], |
| dashLength |
| ); |
| } |
| } |
| } |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| return require('./Polygon').prototype.getRect(style); |
| } |
| }; |
| |
| require('../tool/util').inherits(BrokenLine, Base); |
| return BrokenLine; |
| } |
| ); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:标线 |
| */ |
| define('echarts/util/shape/MarkLine',['require','zrender/shape/Base','./Icon','zrender/shape/Line','zrender/shape/BrokenLine','zrender/tool/matrix','zrender/tool/area','zrender/shape/util/dashedLineTo','zrender/shape/util/smoothSpline','zrender/tool/util'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var IconShape = require('./Icon'); |
| var LineShape = require('zrender/shape/Line'); |
| var lineInstance = new LineShape({}); |
| var BrokenLineShape = require('zrender/shape/BrokenLine'); |
| var brokenLineInstance = new BrokenLineShape({}); |
| |
| var matrix = require('zrender/tool/matrix'); |
| var area = require('zrender/tool/area'); |
| var dashedLineTo = require('zrender/shape/util/dashedLineTo'); |
| var smoothSpline = require('zrender/shape/util/smoothSpline'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| |
| function MarkLine(options) { |
| Base.call(this, options); |
| } |
| |
| MarkLine.prototype = { |
| type : 'mark-line', |
| /** |
| * 画刷 |
| * @param ctx 画布句柄 |
| * @param e 形状实体 |
| * @param isHighlight 是否为高亮状态 |
| * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e) |
| * 让painter更新视图,base.brush没用,需要的话重载brush |
| */ |
| brush : function (ctx, isHighlight) { |
| var style = this.style; |
| |
| if (isHighlight) { |
| // 根据style扩展默认高亮样式 |
| style = this.getHighlightStyle( |
| style, |
| this.highlightStyle || {} |
| ); |
| } |
| |
| ctx.save(); |
| this.setContext(ctx, style); |
| |
| // 设置transform |
| this.setTransform(ctx); |
| |
| ctx.beginPath(); |
| this.buildLinePath(ctx, style); |
| ctx.stroke(); |
| |
| this.brushSymbol(ctx, style, 0); |
| this.brushSymbol(ctx, style, 1); |
| |
| this.drawText(ctx, style, this.style); |
| |
| ctx.restore(); |
| }, |
| |
| /** |
| * 创建线条路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildLinePath : function (ctx, style) { |
| var pointList = style.pointList || this.getPointList(style); |
| style.pointList = pointList; |
| |
| var len = Math.min( |
| style.pointList.length, |
| Math.round(style.pointListLength || style.pointList.length) |
| ); |
| |
| if (!style.lineType || style.lineType == 'solid') { |
| //默认为实线 |
| ctx.moveTo(pointList[0][0],pointList[0][1]); |
| for (var i = 1; i < len; i++) { |
| ctx.lineTo(pointList[i][0],pointList[i][1]); |
| } |
| } |
| else if (style.lineType == 'dashed' |
| || style.lineType == 'dotted' |
| ) { |
| if (style.smooth !== 'spline') { |
| // 直线 |
| var dashLength = (style.lineWidth || 1) |
| * (style.lineType == 'dashed' ? 5 : 1); |
| ctx.moveTo(pointList[0][0],pointList[0][1]); |
| for (var i = 1; i < len; i++) { |
| dashedLineTo( |
| ctx, |
| pointList[i - 1][0], pointList[i - 1][1], |
| pointList[i][0], pointList[i][1], |
| dashLength |
| ); |
| } |
| } |
| else { |
| // 曲线 |
| for (var i = 1; i < len; i += 2) { |
| ctx.moveTo(pointList[i - 1][0],pointList[i - 1][1]); |
| ctx.lineTo(pointList[i][0],pointList[i][1]); |
| } |
| } |
| } |
| }, |
| |
| /** |
| * 标线始末标注 |
| */ |
| brushSymbol : function (ctx, style, idx) { |
| if (style.symbol[idx] == 'none') { |
| return; |
| } |
| ctx.save(); |
| ctx.beginPath(); |
| |
| ctx.lineWidth = style.symbolBorder; |
| ctx.strokeStyle = style.symbolBorderColor; |
| // symbol |
| style.iconType = style.symbol[idx].replace('empty', '') |
| .toLowerCase(); |
| if (style.symbol[idx].match('empty')) { |
| ctx.fillStyle = '#fff'; //'rgba(0, 0, 0, 0)'; |
| } |
| |
| // symbolRotate |
| var len = Math.min( |
| style.pointList.length, |
| Math.round(style.pointListLength || style.pointList.length) |
| ); |
| var x = idx === 0 ? style.pointList[0][0] : style.pointList[len - 1][0]; |
| var y = idx === 0 ? style.pointList[0][1] : style.pointList[len - 1][1]; |
| var rotate = typeof style.symbolRotate[idx] != 'undefined' |
| ? (style.symbolRotate[idx] - 0) : 0; |
| var transform; |
| if (rotate !== 0) { |
| transform = matrix.create(); |
| matrix.identity(transform); |
| if (x || y ) { |
| matrix.translate(transform, transform, [-x, -y]); |
| } |
| matrix.rotate( |
| transform, transform, |
| rotate * Math.PI / 180 |
| ); |
| if (x || y ) { |
| matrix.translate(transform, transform, [x, y]); |
| } |
| ctx.transform.apply(ctx, transform); |
| } |
| |
| if (style.iconType == 'arrow' && rotate === 0) { |
| // 箭头自动旋转,手动画 |
| this.buildArrawPath(ctx, style, idx); |
| } |
| else { |
| // symbolSize |
| var symbolSize = style.symbolSize[idx]; |
| style.x = x - symbolSize; |
| style.y = y - symbolSize, |
| style.width = symbolSize * 2; |
| style.height = symbolSize * 2; |
| IconShape.prototype.buildPath(ctx, style); |
| } |
| |
| ctx.closePath(); |
| ctx.fill(); |
| ctx.stroke(); |
| ctx.restore(); |
| }, |
| |
| buildArrawPath : function (ctx, style, idx) { |
| var len = Math.min( |
| style.pointList.length, |
| Math.round(style.pointListLength || style.pointList.length) |
| ); |
| var symbolSize = style.symbolSize[idx] * 2; |
| var xStart = style.pointList[0][0]; |
| var xEnd = style.pointList[len - 1][0]; |
| var yStart = style.pointList[0][1]; |
| var yEnd = style.pointList[len - 1][1]; |
| var delta = 0; |
| if (style.smooth === 'spline') { |
| delta = 0.2; // 偏移0.2弧度 |
| } |
| // 原谅我吧,这三角函数实在没想明白,只能这么笨了 |
| var rotate = Math.atan( |
| Math.abs((yEnd - yStart) / (xStart - xEnd) |
| )); |
| if (idx === 0) { |
| if (xEnd > xStart) { |
| if (yEnd > yStart) { |
| rotate = Math.PI * 2 - rotate + delta; |
| } |
| else { |
| rotate += delta; |
| } |
| } |
| else { |
| if (yEnd > yStart) { |
| rotate += Math.PI - delta; |
| } |
| else { |
| rotate = Math.PI - rotate - delta; |
| } |
| } |
| } |
| else { |
| if (xStart > xEnd) { |
| if (yStart > yEnd) { |
| rotate = Math.PI * 2 - rotate + delta; |
| } |
| else { |
| rotate += delta; |
| } |
| } |
| else { |
| if (yStart > yEnd) { |
| rotate += Math.PI - delta; |
| } |
| else { |
| rotate = Math.PI - rotate - delta; |
| } |
| } |
| } |
| |
| var halfRotate = Math.PI / 8; // 夹角 |
| var x = idx === 0 ? xStart : xEnd; |
| var y = idx === 0 ? yStart : yEnd; |
| var point= [ |
| [ |
| x + symbolSize * Math.cos(rotate - halfRotate), |
| y - symbolSize * Math.sin(rotate - halfRotate) |
| ], |
| [ |
| x + symbolSize * 0.6 * Math.cos(rotate), |
| y - symbolSize * 0.6 * Math.sin(rotate) |
| ], |
| [ |
| x + symbolSize * Math.cos(rotate + halfRotate), |
| y - symbolSize * Math.sin(rotate + halfRotate) |
| ] |
| ]; |
| ctx.moveTo(x, y); |
| for (var i = 0, l = point.length; i <l; i++) { |
| ctx.lineTo(point[i][0], point[i][1]); |
| } |
| ctx.lineTo(x, y); |
| }, |
| |
| getPointList : function (style) { |
| var pointList = [ |
| [style.xStart, style.yStart], |
| [style.xEnd, style.yEnd] |
| ]; |
| if (style.smooth === 'spline') { |
| var lastPointX = pointList[1][0]; |
| var lastPointY = pointList[1][1]; |
| pointList[3] = [lastPointX, lastPointY]; |
| pointList[1] = this.getOffetPoint(pointList[0], pointList[3]); |
| pointList[2] = this.getOffetPoint(pointList[3], pointList[0]); |
| pointList = smoothSpline(pointList, false); |
| // 修正最后一点在插值产生的偏移 |
| pointList[pointList.length - 1] = [lastPointX, lastPointY]; |
| } |
| return pointList; |
| }, |
| |
| /** |
| * {Array} start point |
| * {Array} end point |
| */ |
| getOffetPoint : function (sp, ep) { |
| var distance = Math.sqrt(Math.round( |
| (sp[0] - ep[0]) * (sp[0] - ep[0]) + (sp[1] - ep[1]) * (sp[1] - ep[1]) |
| )) / 3; |
| //console.log(delta); |
| var mp = [sp[0], sp[1]]; |
| var angle; |
| var deltaAngle = 0.2; // 偏移0.2弧度 |
| if (sp[0] != ep[0] && sp[1] != ep[1]) { |
| // 斜率存在 |
| var k = (ep[1] - sp[1]) / (ep[0] - sp[0]); |
| angle = Math.atan(k); |
| } |
| else if (sp[0] == ep[0]){ |
| // 垂直线 |
| angle = (sp[1] <= ep[1] ? 1 : -1) * Math.PI / 2; |
| } |
| else { |
| // 水平线 |
| angle = 0; |
| } |
| var dX; |
| var dY; |
| if (sp[0] <= ep[0]) { |
| angle -= deltaAngle; |
| dX = Math.round(Math.cos(angle) * distance); |
| dY = Math.round(Math.sin(angle) * distance); |
| mp[0] += dX; |
| mp[1] += dY; |
| } |
| else { |
| angle += deltaAngle; |
| dX = Math.round(Math.cos(angle) * distance); |
| dY = Math.round(Math.sin(angle) * distance); |
| mp[0] -= dX; |
| mp[1] -= dY; |
| } |
| return mp; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function (style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth = style.lineWidth || 1; |
| style.__rect = { |
| x : Math.min(style.xStart, style.xEnd) - lineWidth, |
| y : Math.min(style.yStart, style.yEnd) - lineWidth, |
| width : Math.abs(style.xStart - style.xEnd) |
| + lineWidth, |
| height : Math.abs(style.yStart - style.yEnd) |
| + lineWidth |
| }; |
| |
| return style.__rect; |
| }, |
| |
| isCover : function (x, y) { |
| var originPos = this.getTansform(x, y); |
| x = originPos[0]; |
| y = originPos[1]; |
| |
| // 快速预判并保留判断矩形 |
| var rect = this.style.__rect; |
| if (!rect) { |
| rect = this.style.__rect = this.getRect(this.style); |
| } |
| if (x >= rect.x |
| && x <= (rect.x + rect.width) |
| && y >= rect.y |
| && y <= (rect.y + rect.height) |
| ) { |
| // 矩形内 |
| return this.style.smooth !== 'spline' |
| ? area.isInside(lineInstance, this.style, x, y) |
| : area.isInside(brokenLineInstance, this.style, x, y); |
| } |
| |
| return false; |
| } |
| }; |
| |
| zrUtil.inherits(MarkLine, Base); |
| |
| return MarkLine; |
| }); |
| |
| // 由于大多数shape默认的isCover都是相同的逻辑 |
| // 所以在echarts里临时抽象一个module,用于isCover method |
| // TODO: 对zrender的isCover和getRect方法进行抽象,重新整理该逻辑 |
| |
| define('echarts/util/shape/normalIsCover',[],function () { |
| return function (x, y) { |
| var originPos = this.getTansform(x, y); |
| x = originPos[0]; |
| y = originPos[1]; |
| |
| // 快速预判并保留判断矩形 |
| var rect = this.style.__rect; |
| if (!rect) { |
| rect = this.style.__rect = this.getRect(this.style); |
| } |
| |
| return x >= rect.x |
| && x <= (rect.x + rect.width) |
| && y >= rect.y |
| && y <= (rect.y + rect.height); |
| }; |
| }); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:大规模散点图图形 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'symbol', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| pointList : {Array}, // 必须,二维数组,二维内容如下 |
| x : {number}, // 必须,横坐标 |
| y : {number}, // 必须,纵坐标数组 |
| size : {number}, // 必须,半宽 |
| type : {string=}, // 默认为'circle',图形类型 |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| */ |
| define('echarts/util/shape/Symbol',['require','zrender/shape/Base','zrender/shape/Polygon','zrender/tool/util','./normalIsCover'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var PolygonShape = require('zrender/shape/Polygon'); |
| var polygonInstance = new PolygonShape({}); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function Symbol(options) { |
| Base.call(this, options); |
| } |
| |
| Symbol.prototype = { |
| type : 'symbol', |
| /** |
| * 创建矩形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| var pointList = style.pointList; |
| var len = pointList.length; |
| if (len === 0) { |
| return; |
| } |
| |
| var subSize = 10000; |
| var subSetLength = Math.ceil(len / subSize); |
| var sub; |
| var subLen; |
| var isArray = pointList[0] instanceof Array; |
| var size = style.size ? style.size : 2; |
| var curSize = size; |
| var halfSize = size / 2; |
| var PI2 = Math.PI * 2; |
| var percent; |
| var x; |
| var y; |
| for (var j = 0; j < subSetLength; j++) { |
| ctx.beginPath(); |
| sub = j * subSize; |
| subLen = sub + subSize; |
| subLen = subLen > len ? len : subLen; |
| for (var i = sub; i < subLen; i++) { |
| if (style.random) { |
| percent = style['randomMap' + (i % 20)] / 100; |
| curSize = size * percent * percent; |
| halfSize = curSize / 2; |
| } |
| if (isArray) { |
| x = pointList[i][0]; |
| y = pointList[i][1]; |
| } |
| else { |
| x = pointList[i].x; |
| y = pointList[i].y; |
| } |
| if (curSize < 3) { |
| // 小于3像素视觉误差 |
| ctx.rect(x - halfSize, y - halfSize, curSize, curSize); |
| } |
| else { |
| // 大于3像素才考虑图形 |
| switch (style.iconType) { |
| case 'circle' : |
| ctx.moveTo(x, y); |
| ctx.arc(x, y, halfSize, 0, PI2, true); |
| break; |
| case 'diamond' : |
| ctx.moveTo(x, y - halfSize); |
| ctx.lineTo(x + halfSize / 3, y - halfSize / 3); |
| ctx.lineTo(x + halfSize, y); |
| ctx.lineTo(x + halfSize / 3, y + halfSize / 3); |
| ctx.lineTo(x, y + halfSize); |
| ctx.lineTo(x - halfSize / 3, y + halfSize / 3); |
| ctx.lineTo(x - halfSize, y); |
| ctx.lineTo(x - halfSize / 3, y - halfSize / 3); |
| ctx.lineTo(x, y - halfSize); |
| break; |
| default : |
| ctx.rect(x - halfSize, y - halfSize, curSize, curSize); |
| } |
| } |
| } |
| ctx.closePath(); |
| if (j < (subSetLength - 1)) { |
| switch (style.brushType) { |
| case 'both': |
| ctx.fill(); |
| style.lineWidth > 0 && ctx.stroke(); // js hint -_-" |
| break; |
| case 'stroke': |
| style.lineWidth > 0 && ctx.stroke(); |
| break; |
| default: |
| ctx.fill(); |
| } |
| } |
| } |
| }, |
| |
| /* 像素模式 |
| buildPath : function (ctx, style) { |
| var pointList = style.pointList; |
| var rect = this.getRect(style); |
| var ratio = window.devicePixelRatio || 1; |
| // console.log(rect) |
| // var ti = new Date(); |
| // bbox取整 |
| rect = { |
| x : Math.floor(rect.x), |
| y : Math.floor(rect.y), |
| width : Math.floor(rect.width), |
| height : Math.floor(rect.height) |
| }; |
| var pixels = ctx.getImageData( |
| rect.x * ratio, rect.y * ratio, |
| rect.width * ratio, rect.height * ratio |
| ); |
| var data = pixels.data; |
| var idx; |
| var zrColor = require('zrender/tool/color'); |
| var color = zrColor.toArray(style.color); |
| var r = color[0]; |
| var g = color[1]; |
| var b = color[2]; |
| var width = rect.width; |
| |
| for (var i = 1, l = pointList.length; i < l; i++) { |
| idx = ((Math.floor(pointList[i][0]) - rect.x) * ratio |
| + (Math.floor(pointList[i][1])- rect.y) * width * ratio * ratio |
| ) * 4; |
| data[idx] = r; |
| data[idx + 1] = g; |
| data[idx + 2] = b; |
| data[idx + 3] = 255; |
| } |
| ctx.putImageData(pixels, rect.x * ratio, rect.y * ratio); |
| // console.log(new Date() - ti); |
| return; |
| }, |
| */ |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function (style) { |
| return style.__rect || polygonInstance.getRect(style); |
| }, |
| |
| isCover : require('./normalIsCover') |
| }; |
| |
| zrUtil.inherits(Symbol, Base); |
| |
| return Symbol; |
| }); |
| |
| /** |
| * echarts通用私有数据服务 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/util/ecData',[],function() { |
| /** |
| * 打包私有数据 |
| * |
| * @param {shape} shape 修改目标 |
| * @param {Object} series |
| * @param {number} seriesIndex |
| * @param {number | Object} data |
| * @param {number} dataIndex |
| * @param {*=} special |
| * @param {*=} special2 |
| */ |
| function pack( |
| shape, series, seriesIndex, data, dataIndex, name, special, special2 |
| ) { |
| var value; |
| if (typeof data != 'undefined') { |
| value = data.value == null |
| ? data |
| : data.value; |
| } |
| |
| shape._echartsData = { |
| '_series' : series, |
| '_seriesIndex' : seriesIndex, |
| '_data' : data, |
| '_dataIndex' : dataIndex, |
| '_name' : name, |
| '_value' : value, |
| '_special' : special, |
| '_special2' : special2 |
| }; |
| return shape._echartsData; |
| } |
| |
| /** |
| * 从私有数据中获取特定项 |
| * @param {shape} shape |
| * @param {string} key |
| */ |
| function get(shape, key) { |
| var data = shape._echartsData; |
| if (!key) { |
| return data; |
| } |
| |
| switch (key) { |
| case 'series' : |
| case 'seriesIndex' : |
| case 'data' : |
| case 'dataIndex' : |
| case 'name' : |
| case 'value' : |
| case 'special' : |
| case 'special2' : |
| return data && data['_' + key]; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 修改私有数据中获取特定项 |
| * @param {shape} shape |
| * @param {string} key |
| * @param {*} value |
| */ |
| function set(shape, key, value) { |
| shape._echartsData = shape._echartsData || {}; |
| switch (key) { |
| case 'series' : // 当前系列值 |
| case 'seriesIndex' : // 系列数组位置索引 |
| case 'data' : // 当前数据值 |
| case 'dataIndex' : // 数据数组位置索引 |
| case 'name' : |
| case 'value' : |
| case 'special' : |
| case 'special2' : |
| shape._echartsData['_' + key] = value; |
| break; |
| } |
| } |
| |
| /** |
| * 私有数据克隆,把source拷贝到target上 |
| * @param {shape} source 源 |
| * @param {shape} target 目标 |
| */ |
| function clone(source, target) { |
| target._echartsData = { |
| '_series' : source._echartsData._series, |
| '_seriesIndex' : source._echartsData._seriesIndex, |
| '_data' : source._echartsData._data, |
| '_dataIndex' : source._echartsData._dataIndex, |
| '_name' : source._echartsData._name, |
| '_value' : source._echartsData._value, |
| '_special' : source._echartsData._special, |
| '_special2' : source._echartsData._special2 |
| }; |
| } |
| |
| return { |
| pack : pack, |
| set : set, |
| get : get, |
| clone : clone |
| }; |
| }); |
| /** |
| * echarts图表动画基类 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/util/ecAnimation',['require','zrender/tool/util','zrender/shape/Polygon'],function (require) { |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 折线型动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function pointList(zr, oldShape, newShape, duration, easing) { |
| var newPointList = newShape.style.pointList; |
| var newPointListLen = newPointList.length; |
| var oldPointList; |
| |
| if (!oldShape) { // add |
| oldPointList = []; |
| if (newShape._orient != 'vertical') { |
| var y = newPointList[0][1]; |
| for (var i = 0; i < newPointListLen; i++) { |
| oldPointList[i] = [newPointList[i][0], y]; |
| } |
| } |
| else { |
| var x = newPointList[0][0]; |
| for (var i = 0; i < newPointListLen; i++) { |
| oldPointList[i] = [x, newPointList[i][1]]; |
| } |
| } |
| |
| if (newShape.type == 'half-smooth-polygon') { |
| oldPointList[newPointListLen - 1] = zrUtil.clone(newPointList[newPointListLen - 1]); |
| oldPointList[newPointListLen - 2] = zrUtil.clone(newPointList[newPointListLen - 2]); |
| } |
| oldShape = {style : {pointList : oldPointList}}; |
| } |
| |
| oldPointList = oldShape.style.pointList; |
| var oldPointListLen = oldPointList.length; |
| if (oldPointListLen == newPointListLen) { |
| newShape.style.pointList = oldPointList; |
| } |
| else if (oldPointListLen < newPointListLen) { |
| // 原来短,新的长,补全 |
| newShape.style.pointList = oldPointList.concat(newPointList.slice(oldPointListLen)); |
| } |
| else { |
| // 原来长,新的短,截断 |
| newShape.style.pointList = oldPointList.slice(0, newPointListLen); |
| } |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { pointList: newPointList } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * 复制样式 |
| * |
| * @inner |
| * @param {Object} target 目标对象 |
| * @param {Object} source 源对象 |
| * @param {...string} props 复制的属性列表 |
| */ |
| function cloneStyle(target, source) { |
| var len = arguments.length; |
| for (var i = 2; i < len; i++) { |
| var prop = arguments[i]; |
| target.style[prop] = source.style[prop]; |
| } |
| } |
| |
| /** |
| * 方型动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function rectangle(zr, oldShape, newShape, duration, easing) { |
| var newShapeStyle = newShape.style; |
| if (!oldShape) { // add |
| oldShape = { |
| style : { |
| x : newShapeStyle.x, |
| y : newShape._orient == 'vertical' |
| ? newShapeStyle.y + newShapeStyle.height |
| : newShapeStyle.y, |
| width: newShape._orient == 'vertical' |
| ? newShapeStyle.width : 0, |
| height: newShape._orient != 'vertical' |
| ? newShapeStyle.height : 0 |
| } |
| }; |
| } |
| |
| var newX = newShapeStyle.x; |
| var newY = newShapeStyle.y; |
| var newWidth = newShapeStyle.width; |
| var newHeight = newShapeStyle.height; |
| cloneStyle( |
| newShape, oldShape, |
| 'x', 'y', 'width', 'height' |
| ); |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| x: newX, |
| y: newY, |
| width: newWidth, |
| height: newHeight |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * 蜡烛动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function candle(zr, oldShape, newShape, duration, easing) { |
| if (!oldShape) { // add |
| var y = newShape.style.y; |
| oldShape = {style : {y : [y[0], y[0], y[0], y[0]]}}; |
| } |
| |
| var newY = newShape.style.y; |
| newShape.style.y = oldShape.style.y; |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { y: newY } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * 环型动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function ring(zr, oldShape, newShape, duration, easing) { |
| var x = newShape.style.x; |
| var y = newShape.style.y; |
| var r0 = newShape.style.r0; |
| var r = newShape.style.r; |
| |
| if (newShape._animationAdd != 'r') { |
| newShape.style.r0 = 0; |
| newShape.style.r = 0; |
| newShape.rotation = [Math.PI*2, x, y]; |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| r0 : r0, |
| r : r |
| } |
| ) |
| .start(easing); |
| zr.animate(newShape.id, '') |
| .when( |
| Math.round(duration / 3 * 2), |
| { rotation : [0, x, y] } |
| ) |
| .start(easing); |
| } |
| else { |
| newShape.style.r0 = newShape.style.r; |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| r0 : r0 |
| } |
| ) |
| .start(easing); |
| } |
| } |
| |
| /** |
| * 扇形动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function sector(zr, oldShape, newShape, duration, easing) { |
| if (!oldShape) { // add |
| if (newShape._animationAdd != 'r') { |
| oldShape = { |
| style : { |
| startAngle : newShape.style.startAngle, |
| endAngle : newShape.style.startAngle |
| } |
| }; |
| } |
| else { |
| oldShape = {style : {r0 : newShape.style.r}}; |
| } |
| } |
| |
| var startAngle = newShape.style.startAngle; |
| var endAngle = newShape.style.endAngle; |
| |
| cloneStyle( |
| newShape, oldShape, |
| 'startAngle', 'endAngle' |
| ); |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| startAngle : startAngle, |
| endAngle : endAngle |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * 文本动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function text(zr, oldShape, newShape, duration, easing) { |
| if (!oldShape) { // add |
| oldShape = { |
| style : { |
| x : newShape.style.textAlign == 'left' |
| ? newShape.style.x + 100 |
| : newShape.style.x - 100, |
| y : newShape.style.y |
| } |
| }; |
| } |
| |
| var x = newShape.style.x; |
| var y = newShape.style.y; |
| |
| cloneStyle( |
| newShape, oldShape, |
| 'x', 'y' |
| ); |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| x : x, |
| y : y |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * 多边形动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function polygon(zr, oldShape, newShape, duration, easing) { |
| var rect = require('zrender/shape/Polygon').prototype.getRect(newShape.style); |
| var x = rect.x + rect.width / 2; |
| var y = rect.y + rect.height / 2; |
| |
| newShape.scale = [0.1, 0.1, x, y]; |
| zr.addShape(newShape); |
| zr.animate(newShape.id, '') |
| .when( |
| duration, |
| { |
| scale : [1, 1, x, y] |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * 和弦动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function chord(zr, oldShape, newShape, duration, easing) { |
| if (!oldShape) { // add |
| oldShape = { |
| style : { |
| source0 : 0, |
| source1 : 360, |
| target0 : 0, |
| target1 : 360 |
| } |
| }; |
| } |
| |
| var source0 = newShape.style.source0; |
| var source1 = newShape.style.source1; |
| var target0 = newShape.style.target0; |
| var target1 = newShape.style.target1; |
| |
| if (oldShape.style) { |
| cloneStyle( |
| newShape, oldShape, |
| 'source0', 'source1', 'target0', 'target1' |
| ); |
| } |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| source0 : source0, |
| source1 : source1, |
| target0 : target0, |
| target1 : target1 |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * gaugePointer动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function gaugePointer(zr, oldShape, newShape, duration, easing) { |
| if (!oldShape) { // add |
| oldShape = { |
| style : { |
| angle : newShape.style.startAngle |
| } |
| }; |
| } |
| |
| var angle = newShape.style.angle; |
| newShape.style.angle = oldShape.style.angle; |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| angle : angle |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * icon动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function icon(zr, oldShape, newShape, duration, easing) { |
| // 避免markPoint特效取值在动画帧上 |
| newShape.style._x = newShape.style.x; |
| newShape.style._y = newShape.style.y; |
| newShape.style._width = newShape.style.width; |
| newShape.style._height = newShape.style.height; |
| |
| if (!oldShape) { // add |
| var x = newShape._x || 0; |
| var y = newShape._y || 0; |
| newShape.scale = [0, 0, x, y]; |
| zr.addShape(newShape); |
| zr.animate(newShape.id, '') |
| .when( |
| duration, |
| {scale : [1, 1, x, y]} |
| ) |
| .start(easing || 'QuinticOut'); |
| } |
| else { // mod |
| rectangle(zr, oldShape, newShape, duration, easing); |
| } |
| } |
| |
| /** |
| * line动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function line(zr, oldShape, newShape, duration, easing) { |
| if (!oldShape) { |
| oldShape = { |
| style : { |
| xEnd : newShape.style.xStart, |
| yEnd : newShape.style.yStart |
| } |
| }; |
| } |
| |
| var xStart = newShape.style.xStart; |
| var xEnd = newShape.style.xEnd; |
| var yStart = newShape.style.yStart; |
| var yEnd = newShape.style.yEnd; |
| |
| cloneStyle( |
| newShape, oldShape, |
| 'xStart', 'xEnd', 'yStart', 'yEnd' |
| ); |
| |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| xStart: xStart, |
| xEnd: xEnd, |
| yStart: yStart, |
| yEnd: yEnd |
| } |
| ) |
| .start(easing); |
| } |
| |
| /** |
| * markline动画 |
| * |
| * @param {ZRender} zr |
| * @param {shape} oldShape |
| * @param {shape} newShape |
| * @param {number} duration |
| * @param {tring} easing |
| */ |
| function markline(zr, oldShape, newShape, duration, easing) { |
| if (!newShape.style.smooth) { |
| newShape.style.pointList = !oldShape |
| ? [ |
| [newShape.style.xStart, newShape.style.yStart], |
| [newShape.style.xStart, newShape.style.yStart] |
| ] |
| : oldShape.style.pointList; |
| zr.addShape(newShape); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| pointList : [ |
| [ |
| newShape.style.xStart, |
| newShape.style.yStart |
| ], |
| [ |
| newShape._x || 0, newShape._y || 0 |
| ] |
| ] |
| } |
| ) |
| .start(easing || 'QuinticOut'); |
| } |
| else { |
| // 曲线动画 |
| newShape.style.pointListLength = 1; |
| zr.addShape(newShape); |
| newShape.style.pointList = newShape.style.pointList |
| || newShape.getPointList(newShape.style); |
| zr.animate(newShape.id, 'style') |
| .when( |
| duration, |
| { |
| pointListLength : newShape.style.pointList.length |
| } |
| ) |
| .start(easing || 'QuinticOut'); |
| } |
| } |
| |
| return { |
| pointList : pointList, |
| rectangle : rectangle, |
| candle : candle, |
| ring : ring, |
| sector : sector, |
| text : text, |
| polygon : polygon, |
| chord : chord, |
| gaugePointer : gaugePointer, |
| icon : icon, |
| line : line, |
| markline : markline |
| }; |
| }); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:圆 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'circle', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,圆心横坐标 |
| y : {number}, // 必须,圆心纵坐标 |
| r : {number}, // 必须,圆半径 |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为top,附加文本位置。 |
| // inside | left | right | top | bottom |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'circle', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| r : 50, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Circle',['require','./Base','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| |
| function Circle(options) { |
| Base.call(this, options); |
| } |
| |
| Circle.prototype = { |
| type: 'circle', |
| /** |
| * 创建圆形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| ctx.arc(style.x, style.y, style.r, 0, Math.PI * 2, true); |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function (style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| style.__rect = { |
| x : Math.round(style.x - style.r - lineWidth / 2), |
| y : Math.round(style.y - style.r - lineWidth / 2), |
| width : style.r * 2 + lineWidth, |
| height : style.r * 2 + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Circle, Base); |
| return Circle; |
| } |
| ); |
| /** |
| * echarts图表特效基类 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/util/ecEffect',['require','../util/ecData','zrender/shape/Circle','zrender/shape/Image','../util/shape/Icon','../util/shape/Symbol','zrender/tool/env'],function (require) { |
| var ecData = require('../util/ecData'); |
| |
| var CircleShape = require('zrender/shape/Circle'); |
| var ImageShape = require('zrender/shape/Image'); |
| var IconShape = require('../util/shape/Icon'); |
| var SymbolShape = require('../util/shape/Symbol'); |
| |
| var canvasSupported = require('zrender/tool/env').canvasSupported; |
| |
| function point(zr, effectList, shape, zlevel) { |
| var effect = shape.effect; |
| var color = effect.color || shape.style.strokeColor || shape.style.color; |
| var shadowColor = effect.shadowColor || color; |
| var size = effect.scaleSize; |
| var shadowBlur = typeof effect.shadowBlur != 'undefined' |
| ? effect.shadowBlur : size; |
| |
| var effectShape = new IconShape({ |
| zlevel : zlevel, |
| style : { |
| brushType : 'stroke', |
| iconType : (shape.style.iconType != 'pin' |
| && shape.style.iconType != 'droplet') |
| ? shape.style.iconType |
| : 'circle', |
| x : shadowBlur + 1, // 线宽 |
| y : shadowBlur + 1, |
| n : shape.style.n, |
| width : shape.style.width * size, |
| height : shape.style.height * size, |
| lineWidth : 1, |
| strokeColor : color, |
| shadowColor : shadowColor, |
| shadowBlur : shadowBlur |
| }, |
| draggable : false, |
| hoverable : false |
| }); |
| |
| if (canvasSupported) { // 提高性能,换成image |
| effectShape.style.image = zr.shapeToImage( |
| effectShape, |
| effectShape.style.width + shadowBlur * 2 + 2, |
| effectShape.style.height + shadowBlur * 2 + 2 |
| ).style.image; |
| |
| effectShape = new ImageShape({ |
| zlevel : effectShape.zlevel, |
| style : effectShape.style, |
| draggable : false, |
| hoverable : false |
| }); |
| } |
| |
| ecData.clone(shape, effectShape); |
| |
| // 改变坐标,不能移到前面 |
| effectShape.position = shape.position; |
| effectList.push(effectShape); |
| zr.addShape(effectShape); |
| |
| var devicePixelRatio = window.devicePixelRatio || 1; |
| var offset = (effectShape.style.width / devicePixelRatio - shape.style.width) / 2; |
| effectShape.style.x = shape.style._x - offset; |
| effectShape.style.y = shape.style._y - offset; |
| var duration = (effect.period + Math.random() * 10) * 100; |
| |
| zr.modShape( |
| shape.id, |
| { invisible : true} |
| ); |
| |
| var centerX = effectShape.style.x + (effectShape.style.width) / 2 / devicePixelRatio; |
| var centerY = effectShape.style.y + (effectShape.style.height) / 2 / devicePixelRatio; |
| zr.modShape( |
| effectShape.id, |
| { |
| scale : [0.1, 0.1, centerX, centerY] |
| } |
| ); |
| |
| zr.animate(effectShape.id, '', effect.loop) |
| .when( |
| duration, |
| { |
| scale : [1, 1, centerX, centerY] |
| } |
| ) |
| .done(function() { |
| shape.effect.show = false; |
| zr.delShape(effectShape.id); |
| }) |
| .start(); |
| } |
| |
| function largePoint(zr, effectList, shape, zlevel) { |
| var effect = shape.effect; |
| var color = effect.color || shape.style.strokeColor || shape.style.color; |
| var size = effect.scaleSize; |
| var shadowColor = effect.shadowColor || color; |
| var shadowBlur = typeof effect.shadowBlur != 'undefined' |
| ? effect.shadowBlur : (size * 2); |
| var devicePixelRatio = window.devicePixelRatio || 1; |
| var effectShape = new SymbolShape({ |
| zlevel : zlevel, |
| position : shape.position, |
| scale : shape.scale, |
| style : { |
| pointList : shape.style.pointList, |
| iconType : shape.style.iconType, |
| color : color, |
| strokeColor : color, |
| shadowColor : shadowColor, |
| shadowBlur : shadowBlur * devicePixelRatio, |
| random : true, |
| brushType: 'fill', |
| lineWidth:1, |
| size : shape.style.size |
| }, |
| draggable : false, |
| hoverable : false |
| }); |
| |
| effectList.push(effectShape); |
| zr.addShape(effectShape); |
| zr.modShape( |
| shape.id, |
| { invisible : true} |
| ); |
| |
| var duration = Math.round(effect.period * 100); |
| var clip1 = {}; |
| var clip2 = {}; |
| for (var i = 0; i < 20; i++) { |
| effectShape.style['randomMap' + i] = 0; |
| clip1 = {}; |
| clip1['randomMap' + i] = 100; |
| clip2 = {}; |
| clip2['randomMap' + i] = 0; |
| effectShape.style['randomMap' + i] = Math.random() * 100; |
| zr.animate(effectShape.id, 'style', true) |
| .when(duration, clip1) |
| .when(duration * 2, clip2) |
| .when(duration * 3, clip1) |
| .when(duration * 4, clip1) |
| .delay(Math.random() * duration * i) |
| //.delay(duration / 15 * (15 - i + 1)) |
| .start(); |
| |
| } |
| } |
| |
| function line(zr, effectList, shape, zlevel) { |
| var effect = shape.effect; |
| var color = effect.color || shape.style.strokeColor || shape.style.color; |
| var shadowColor = effect.shadowColor || shape.style.strokeColor || color; |
| var size = shape.style.lineWidth * effect.scaleSize; |
| var shadowBlur = typeof effect.shadowBlur != 'undefined' |
| ? effect.shadowBlur : size; |
| |
| var effectShape = new CircleShape({ |
| zlevel : zlevel, |
| style : { |
| x : shadowBlur, |
| y : shadowBlur, |
| r : size, |
| color : color, |
| shadowColor : shadowColor, |
| shadowBlur : shadowBlur |
| }, |
| draggable : false, |
| hoverable : false |
| }); |
| |
| var offset; |
| if (canvasSupported) { // 提高性能,换成image |
| effectShape.style.image = zr.shapeToImage( |
| effectShape, |
| (size + shadowBlur) * 2, |
| (size + shadowBlur) * 2 |
| ).style.image; |
| effectShape = new ImageShape({ |
| zlevel : effectShape.zlevel, |
| style : effectShape.style, |
| draggable : false, |
| hoverable : false |
| }); |
| offset = shadowBlur; |
| } |
| else { |
| offset = 0; |
| } |
| |
| ecData.clone(shape, effectShape); |
| |
| // 改变坐标, 不能移到前面 |
| effectShape.position = shape.position; |
| effectList.push(effectShape); |
| zr.addShape(effectShape); |
| |
| effectShape.style.x = shape.style.xStart - offset; |
| effectShape.style.y = shape.style.yStart - offset; |
| var distance = (shape.style.xStart - shape.style.xEnd) |
| * (shape.style.xStart - shape.style.xEnd) |
| + |
| (shape.style.yStart - shape.style.yEnd) |
| * (shape.style.yStart - shape.style.yEnd); |
| var duration = Math.round(Math.sqrt(Math.round( |
| distance * effect.period * effect.period |
| ))); |
| if (!shape.style.smooth) { |
| // 直线 |
| zr.animate(effectShape.id, 'style', effect.loop) |
| .when( |
| duration, |
| { |
| x : shape._x - offset, |
| y : shape._y - offset |
| } |
| ) |
| .done(function() { |
| shape.effect.show = false; |
| zr.delShape(effectShape.id); |
| }) |
| .start(); |
| } |
| else { |
| // 曲线 |
| var pointList = shape.style.pointList || shape.getPointList(shape.style); |
| var len = pointList.length; |
| duration = Math.round(duration / len); |
| var deferred = zr.animate(effectShape.id, 'style', effect.loop); |
| var step = Math.ceil(len / 8); |
| for (var j = 0; j < len - step; j+= step) { |
| deferred.when( |
| duration * (j + 1), |
| { |
| x : pointList[j][0] - offset, |
| y : pointList[j][1] - offset |
| } |
| ); |
| } |
| deferred.when( |
| duration * len, |
| { |
| x : pointList[len - 1][0] - offset, |
| y : pointList[len - 1][1] - offset |
| } |
| ); |
| deferred.done(function() { |
| shape.effect.show = false; |
| zr.delShape(effectShape.id); |
| }); |
| deferred.start('spline'); |
| } |
| } |
| |
| return { |
| point : point, |
| largePoint : largePoint, |
| line : line |
| }; |
| }); |
| |
| /** |
| * 高精度数学运算 |
| */ |
| define('echarts/util/accMath',[],function() { |
| // 除法函数,用来得到精确的除法结果 |
| // 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。 |
| // 调用:accDiv(arg1,arg2) |
| // 返回值:arg1除以arg2的精确结果 |
| function accDiv(arg1, arg2) { |
| return accMul(arg1, 1 / arg2); |
| } |
| |
| // 乘法函数,用来得到精确的乘法结果 |
| // 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。 |
| // 调用:accMul(arg1,arg2) |
| // 返回值:arg1乘以arg2的精确结果 |
| function accMul(arg1, arg2) { |
| var m = 0; |
| var s1 = arg1.toString(); |
| var s2 = arg2.toString(); |
| try { |
| m += s1.split('.')[1].length; |
| } |
| catch(e) {} |
| |
| try { |
| m += s2.split('.')[1].length; |
| } |
| catch(e){} |
| |
| return (s1.replace('.', '') - 0) * (s2.replace('.', '') - 0) / Math.pow(10, m); |
| } |
| |
| // 加法函数,用来得到精确的加法结果 |
| // 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 |
| // 调用:accAdd(arg1,arg2) |
| // 返回值:arg1加上arg2的精确结果 |
| function accAdd(arg1, arg2) { |
| var r1 = 0; |
| var r2 = 0; |
| |
| try { |
| r1 = arg1.toString().split('.')[1].length; |
| } |
| catch(e) {} |
| |
| try { |
| r2 = arg2.toString().split('.')[1].length; |
| } |
| catch(e) {} |
| |
| var m = Math.pow(10, Math.max(r1, r2)); |
| return (Math.round(arg1 * m) + Math.round(arg2 * m)) / m; |
| } |
| |
| //减法函数,用来得到精确的减法结果 |
| //说明:javascript的减法结果会有误差,在两个浮点数减法的时候会比较明显。这个函数返回较为精确的减法结果。 |
| //调用:accSub(arg1,arg2) |
| //返回值:arg1减法arg2的精确结果 |
| function accSub(arg1,arg2) { |
| return accAdd(arg1, -arg2); |
| } |
| |
| return { |
| accDiv : accDiv, |
| accMul : accMul, |
| accAdd : accAdd, |
| accSub : accSub |
| }; |
| }); |
| /** |
| * echarts图表基类 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/base',['require','zrender/shape/Image','../util/shape/Icon','../util/shape/MarkLine','../util/shape/Symbol','../config','../util/ecData','../util/ecAnimation','../util/ecEffect','../util/accMath','zrender/tool/util','zrender/tool/area'],function (require) { |
| // 图形依赖 |
| var ImageShape = require('zrender/shape/Image'); |
| var IconShape = require('../util/shape/Icon'); |
| var MarkLineShape = require('../util/shape/MarkLine'); |
| var SymbolShape = require('../util/shape/Symbol'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var ecAnimation = require('../util/ecAnimation'); |
| var ecEffect = require('../util/ecEffect'); |
| var accMath = require('../util/accMath'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrArea = require('zrender/tool/area'); |
| |
| function Base(){ |
| var self = this; |
| this.selectedMap = {}; |
| this.lastShapeList = []; |
| this.shapeHandler = { |
| onclick : function () { |
| self.isClick = true; |
| }, |
| |
| ondragover : function (param) { |
| // 返回触发可计算特性的图形提示 |
| var calculableShape = param.target; |
| calculableShape.highlightStyle = calculableShape.highlightStyle || {}; |
| |
| // 备份特出特性 |
| var highlightStyle = calculableShape.highlightStyle; |
| var brushType = highlightStyle.brushTyep; |
| var strokeColor = highlightStyle.strokeColor; |
| var lineWidth = highlightStyle.lineWidth; |
| |
| highlightStyle.brushType = 'stroke'; |
| highlightStyle.strokeColor = self.ecTheme.calculableColor; |
| highlightStyle.lineWidth = calculableShape.type == 'icon' ? 30 : 10; |
| |
| self.zr.addHoverShape(calculableShape); |
| |
| setTimeout(function (){ |
| // 复位 |
| if (calculableShape.highlightStyle) { |
| calculableShape.highlightStyle.brushType = brushType; |
| calculableShape.highlightStyle.strokeColor = strokeColor; |
| calculableShape.highlightStyle.lineWidth = lineWidth; |
| } |
| },20); |
| }, |
| |
| ondrop : function (param) { |
| // 排除一些非数据的拖拽进入 |
| if (typeof ecData.get(param.dragged, 'data') != 'undefined') { |
| self.isDrop = true; |
| } |
| }, |
| |
| ondragend : function () { |
| self.isDragend = true; |
| } |
| }; |
| } |
| |
| /** |
| * 基类方法 |
| */ |
| Base.prototype = { |
| /** |
| * 图形拖拽特性 |
| */ |
| setCalculable : function (shape) { |
| shape.dragEnableTime = this.ecTheme.DRAG_ENABLE_TIME; |
| shape.ondragover = this.shapeHandler.ondragover; |
| shape.ondragend = this.shapeHandler.ondragend; |
| shape.ondrop = this.shapeHandler.ondrop; |
| return shape; |
| }, |
| |
| /** |
| * 数据项被拖拽进来 |
| */ |
| ondrop : function (param, status) { |
| if (!this.isDrop || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| var target = param.target; // 拖拽安放目标 |
| var dragged = param.dragged; // 当前被拖拽的图形对象 |
| |
| var seriesIndex = ecData.get(target, 'seriesIndex'); |
| var dataIndex = ecData.get(target, 'dataIndex'); |
| |
| var series = this.series; |
| var data; |
| var legend = this.component.legend; |
| if (dataIndex == -1) { |
| // 落到calculableCase上,数据被拖拽进某个饼图|雷达|漏斗,增加数据 |
| data = { |
| value : ecData.get(dragged, 'value'), |
| name : ecData.get(dragged, 'name') |
| }; |
| |
| // 修饼图数值不为负值 |
| if (this.type == ecConfig.CHART_TYPE_PIE && data.value < 0) { |
| data.value = 0; |
| } |
| |
| var hasFind = false; |
| var sData = series[seriesIndex].data; |
| for (var i = 0, l = sData.length; i < l; i++) { |
| if (sData[i].name == data.name && sData[i].value == '-') { |
| series[seriesIndex].data[i].value = data.value; |
| hasFind = true; |
| } |
| } |
| !hasFind && series[seriesIndex].data.push(data); |
| |
| legend && legend.add( |
| data.name, |
| dragged.style.color || dragged.style.strokeColor |
| ); |
| } |
| else { |
| // 落到数据item上,数据被拖拽到某个数据项上,数据修改 |
| data = this.option.series[seriesIndex].data[dataIndex] || '-'; |
| if (typeof data.value != 'undefined') { |
| if (data.value != '-') { |
| this.option.series[seriesIndex].data[dataIndex].value = |
| accMath.accAdd( |
| this.option.series[seriesIndex].data[dataIndex].value, |
| ecData.get(dragged, 'value') |
| ); |
| } |
| else { |
| this.option.series[seriesIndex].data[dataIndex].value = |
| ecData.get(dragged, 'value'); |
| } |
| |
| if (this.type == ecConfig.CHART_TYPE_FUNNEL |
| || this.type == ecConfig.CHART_TYPE_PIE |
| ) { |
| legend && legend.getRelatedAmount(data.name) == 1 |
| && this.component.legend.del(data.name); |
| data.name += this.option.nameConnector + ecData.get(dragged, 'name'); |
| legend && legend.add( |
| data.name, |
| dragged.style.color || dragged.style.strokeColor |
| ); |
| } |
| } |
| else { |
| if (data != '-') { |
| this.option.series[seriesIndex].data[dataIndex] = |
| accMath.accAdd( |
| this.option.series[seriesIndex].data[dataIndex], |
| ecData.get(dragged, 'value') |
| ); |
| } |
| else { |
| this.option.series[seriesIndex].data[dataIndex] = |
| ecData.get(dragged, 'value'); |
| } |
| } |
| } |
| |
| // 别status = {}赋值啊!! |
| status.dragIn = status.dragIn || true; |
| |
| // 处理完拖拽事件后复位 |
| this.isDrop = false; |
| |
| var self = this; |
| setTimeout(function(){ |
| self.zr.trigger('mousemove', param.event); |
| }, 300); |
| |
| return; |
| }, |
| |
| /** |
| * 数据项被拖拽出去 |
| */ |
| ondragend : function (param, status) { |
| if (!this.isDragend || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| var target = param.target; // 被拖拽图形元素 |
| |
| var seriesIndex = ecData.get(target, 'seriesIndex'); |
| var dataIndex = ecData.get(target, 'dataIndex'); |
| |
| var series = this.series; |
| |
| // 删除被拖拽走的数据 |
| if (typeof series[seriesIndex].data[dataIndex].value != 'undefined') { |
| series[seriesIndex].data[dataIndex].value = '-'; |
| // 清理可能有且唯一的legend data |
| var name = series[seriesIndex].data[dataIndex].name; |
| if (this.component.legend |
| && this.component.legend.getRelatedAmount(name) === 0 |
| ) { |
| this.component.legend.del(name); |
| } |
| } |
| else { |
| series[seriesIndex].data[dataIndex] = '-'; |
| } |
| |
| // 别status = {}赋值啊!! |
| status.dragOut = true; |
| status.needRefresh = true; |
| |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| |
| return; |
| }, |
| |
| /** |
| * 图例选择 |
| */ |
| onlegendSelected : function (param, status) { |
| var legendSelected = param.selected; |
| for (var itemName in this.selectedMap) { |
| if (this.selectedMap[itemName] != legendSelected[itemName]) { |
| // 有一项不一致都需要重绘 |
| status.needRefresh = true; |
| } |
| this.selectedMap[itemName] = legendSelected[itemName]; |
| } |
| return; |
| }, |
| |
| /** |
| * 添加文本 |
| */ |
| addLabel : function (tarShape, serie, data, name, orient) { |
| // 多级控制 |
| var queryTarget = [data, serie]; |
| var nLabel = this.deepMerge(queryTarget, 'itemStyle.normal.label'); |
| var eLabel = this.deepMerge(queryTarget, 'itemStyle.emphasis.label'); |
| |
| var nTextStyle = nLabel.textStyle || {}; |
| var eTextStyle = eLabel.textStyle || {}; |
| |
| if (nLabel.show) { |
| tarShape.style.text = this._getLabelText( |
| serie, data, name, 'normal' |
| ); |
| tarShape.style.textPosition = typeof nLabel.position == 'undefined' |
| ? (orient == 'horizontal' ? 'right' : 'top') |
| : nLabel.position; |
| tarShape.style.textColor = nTextStyle.color; |
| tarShape.style.textFont = this.getFont(nTextStyle); |
| } |
| if (eLabel.show) { |
| tarShape.highlightStyle.text = this._getLabelText( |
| serie, data, name, 'emphasis' |
| ); |
| tarShape.highlightStyle.textPosition = nLabel.show |
| ? tarShape.style.textPosition |
| : (typeof eLabel.position == 'undefined' |
| ? (orient == 'horizontal' ? 'right' : 'top') |
| : eLabel.position); |
| tarShape.highlightStyle.textColor = eTextStyle.color; |
| tarShape.highlightStyle.textFont = this.getFont(eTextStyle); |
| } |
| |
| return tarShape; |
| }, |
| |
| /** |
| * 根据lable.format计算label text |
| */ |
| _getLabelText : function (serie, data, name, status) { |
| var formatter = this.deepQuery( |
| [data, serie], |
| 'itemStyle.' + status + '.label.formatter' |
| ); |
| if (!formatter && status == 'emphasis') { |
| // emphasis时需要看看normal下是否有formatter |
| formatter = this.deepQuery( |
| [data, serie], |
| 'itemStyle.normal.label.formatter' |
| ); |
| } |
| |
| var value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| |
| if (formatter) { |
| if (typeof formatter == 'function') { |
| return formatter.call( |
| this.myChart, |
| serie.name, |
| name, |
| value |
| ); |
| } |
| else if (typeof formatter == 'string') { |
| formatter = formatter.replace('{a}','{a0}') |
| .replace('{b}','{b0}') |
| .replace('{c}','{c0}'); |
| formatter = formatter.replace('{a0}', serie.name) |
| .replace('{b0}', name) |
| .replace('{c0}', value); |
| |
| return formatter; |
| } |
| } |
| else { |
| return value; |
| } |
| }, |
| |
| /** |
| * 标线标注 |
| */ |
| buildMark : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (this.selectedMap[serie.name]) { |
| serie.markPoint && this._buildMarkPoint(seriesIndex); |
| serie.markLine && this._buildMarkLine(seriesIndex); |
| } |
| }, |
| |
| /** |
| * 标注逻辑 |
| */ |
| _buildMarkPoint : function (seriesIndex) { |
| var attachStyle = (this.markAttachStyle || {})[seriesIndex]; |
| var serie = this.series[seriesIndex]; |
| var _zlevelBase = this.getZlevelBase(); |
| var mpData; |
| var pos; |
| var markPoint = zrUtil.clone(serie.markPoint); |
| for (var i = 0, l = markPoint.data.length; i < l; i++) { |
| mpData = markPoint.data[i]; |
| pos = this.getMarkCoord(seriesIndex, mpData); |
| markPoint.data[i].x = typeof mpData.x != 'undefined' |
| ? mpData.x : pos[0]; |
| markPoint.data[i].y = typeof mpData.y != 'undefined' |
| ? mpData.y : pos[1]; |
| if (mpData.type |
| && (mpData.type == 'max' || mpData.type == 'min') |
| ) { |
| // 特殊值内置支持 |
| markPoint.data[i].value = pos[3]; |
| markPoint.data[i].name = mpData.name || mpData.type; |
| markPoint.data[i].symbolSize = markPoint.data[i].symbolSize |
| || (zrArea.getTextWidth(pos[3], this.getFont()) / 2 + 5); |
| } |
| } |
| |
| var shapeList = this._markPoint(seriesIndex, markPoint); |
| |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| shapeList[i].zlevel = _zlevelBase + 1; |
| for (var key in attachStyle) { |
| shapeList[i][key] = zrUtil.clone(attachStyle[key]); |
| } |
| this.shapeList.push(shapeList[i]); |
| } |
| // 个别特殊图表需要自己addShape |
| if (this.type == ecConfig.CHART_TYPE_FORCE |
| || this.type == ecConfig.CHART_TYPE_CHORD |
| ) { |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| this.zr.addShape(shapeList[i]); |
| } |
| } |
| }, |
| |
| /** |
| * 标线逻辑 |
| */ |
| _buildMarkLine : function (seriesIndex) { |
| var attachStyle = (this.markAttachStyle || {})[seriesIndex]; |
| var serie = this.series[seriesIndex]; |
| var _zlevelBase = this.getZlevelBase(); |
| var mlData; |
| var pos; |
| var markLine = zrUtil.clone(serie.markLine); |
| for (var i = 0, l = markLine.data.length; i < l; i++) { |
| mlData = markLine.data[i]; |
| if (mlData.type |
| && (mlData.type == 'max' || mlData.type == 'min' || mlData.type == 'average') |
| ) { |
| // 特殊值内置支持 |
| pos = this.getMarkCoord(seriesIndex, mlData); |
| markLine.data[i] = [zrUtil.clone(mlData), {}]; |
| markLine.data[i][0].name = mlData.name || mlData.type; |
| markLine.data[i][0].value = pos[3]; |
| pos = pos[2]; |
| mlData = [{},{}]; |
| } |
| else { |
| pos = [ |
| this.getMarkCoord(seriesIndex, mlData[0]), |
| this.getMarkCoord(seriesIndex, mlData[1]) |
| ]; |
| } |
| |
| markLine.data[i][0].x = typeof mlData[0].x != 'undefined' |
| ? mlData[0].x : pos[0][0]; |
| markLine.data[i][0].y = typeof mlData[0].y != 'undefined' |
| ? mlData[0].y : pos[0][1]; |
| markLine.data[i][1].x = typeof mlData[1].x != 'undefined' |
| ? mlData[1].x : pos[1][0]; |
| markLine.data[i][1].y = typeof mlData[1].y != 'undefined' |
| ? mlData[1].y : pos[1][1]; |
| } |
| |
| var shapeList = this._markLine(seriesIndex, markLine); |
| |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| shapeList[i].zlevel = _zlevelBase + 1; |
| for (var key in attachStyle) { |
| shapeList[i][key] = zrUtil.clone(attachStyle[key]); |
| } |
| this.shapeList.push(shapeList[i]); |
| } |
| // 个别特殊图表需要自己addShape |
| if (this.type == ecConfig.CHART_TYPE_FORCE |
| || this.type == ecConfig.CHART_TYPE_CHORD |
| ) { |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| this.zr.addShape(shapeList[i]); |
| } |
| } |
| }, |
| |
| /** |
| * 标注多级控制构造 |
| */ |
| _markPoint : function (seriesIndex, mpOption) { |
| var serie = this.series[seriesIndex]; |
| var component = this.component; |
| zrUtil.merge( |
| mpOption, |
| this.ecTheme.markPoint |
| ); |
| mpOption.name = serie.name; |
| |
| var pList = []; |
| var data = mpOption.data; |
| var itemShape; |
| |
| var dataRange = component.dataRange; |
| var legend = component.legend; |
| var color; |
| var value; |
| var queryTarget; |
| var nColor; |
| var eColor; |
| var effect; |
| var zrWidth = this.zr.getWidth(); |
| var zrHeight = this.zr.getHeight(); |
| |
| if (!mpOption.large) { |
| for (var i = 0, l = data.length; i < l; i++) { |
| value = typeof data[i] != 'undefined' && typeof data[i].value != 'undefined' |
| ? data[i].value |
| : ''; |
| // 图例 |
| if (legend) { |
| color = legend.getColor(serie.name); |
| } |
| // 值域 |
| if (dataRange) { |
| color = isNaN(value) ? color : dataRange.getColor(value); |
| |
| queryTarget = [data[i], mpOption]; |
| nColor = this.deepQuery( |
| queryTarget, 'itemStyle.normal.color' |
| ) || color; |
| eColor = this.deepQuery( |
| queryTarget, 'itemStyle.emphasis.color' |
| ) || nColor; |
| // 有值域,并且值域返回null且用户没有自己定义颜色,则隐藏这个mark |
| if (nColor == null && eColor == null) { |
| continue; |
| } |
| } |
| |
| // 标准化一些参数 |
| data[i].tooltip = data[i].tooltip |
| || {trigger:'item'}; // tooltip.trigger指定为item |
| data[i].name = typeof data[i].name != 'undefined' |
| ? data[i].name : ''; |
| data[i].value = value; |
| |
| // 复用getSymbolShape |
| itemShape = this.getSymbolShape( |
| mpOption, seriesIndex, // 系列 |
| data[i], i, data[i].name, // 数据 |
| this.parsePercent(data[i].x, zrWidth), // 坐标 |
| this.parsePercent(data[i].y, zrHeight), // 坐标 |
| 'pin', color, // 默认symbol和color |
| 'rgba(0,0,0,0)', |
| 'horizontal' // 走向,用于默认文字定位 |
| ); |
| itemShape._mark = 'point'; |
| |
| effect = this.deepMerge( |
| [data[i], mpOption], |
| 'effect' |
| ); |
| if (effect.show) { |
| itemShape.effect = effect; |
| } |
| |
| if (serie.type == ecConfig.CHART_TYPE_MAP) { |
| itemShape._geo = this.getMarkGeo(data[i]); |
| } |
| |
| // 重新pack一下数据 |
| ecData.pack( |
| itemShape, |
| serie, seriesIndex, |
| data[i], i, |
| data[i].name, |
| value |
| ); |
| pList.push(itemShape); |
| } |
| } |
| else { |
| // 大规模MarkPoint |
| itemShape = this.getLargeMarkPoingShape(seriesIndex, mpOption); |
| itemShape._mark = 'largePoint'; |
| itemShape && pList.push(itemShape); |
| } |
| return pList; |
| }, |
| |
| /** |
| * 标线多级控制构造 |
| */ |
| _markLine : function (seriesIndex, mlOption) { |
| var serie = this.series[seriesIndex]; |
| var component = this.component; |
| zrUtil.merge( |
| mlOption, |
| this.ecTheme.markLine |
| ); |
| // 标准化一些同时支持Array和String的参数 |
| mlOption.symbol = mlOption.symbol instanceof Array |
| ? mlOption.symbol.length > 1 |
| ? mlOption.symbol |
| : [mlOption.symbol[0], mlOption.symbol[0]] |
| : [mlOption.symbol, mlOption.symbol]; |
| mlOption.symbolSize = mlOption.symbolSize instanceof Array |
| ? mlOption.symbolSize.length > 1 |
| ? mlOption.symbolSize |
| : [mlOption.symbolSize[0], mlOption.symbolSize[0]] |
| : [mlOption.symbolSize, mlOption.symbolSize]; |
| mlOption.symbolRotate = mlOption.symbolRotate instanceof Array |
| ? mlOption.symbolRotate.length > 1 |
| ? mlOption.symbolRotate |
| : [mlOption.symbolRotate[0], mlOption.symbolRotate[0]] |
| : [mlOption.symbolRotate, mlOption.symbolRotate]; |
| |
| mlOption.name = serie.name; |
| |
| var pList = []; |
| var data = mlOption.data; |
| var itemShape; |
| |
| var dataRange = component.dataRange; |
| var legend = component.legend; |
| var color; |
| var value; |
| var queryTarget; |
| var nColor; |
| var eColor; |
| var effect; |
| var zrWidth = this.zr.getWidth(); |
| var zrHeight = this.zr.getHeight(); |
| var mergeData; |
| for (var i = 0, l = data.length; i < l; i++) { |
| // 图例 |
| if (legend) { |
| color = legend.getColor(serie.name); |
| } |
| // 组装一个mergeData |
| mergeData = this.deepMerge(data[i]); |
| value = typeof mergeData != 'undefined' && typeof mergeData.value != 'undefined' |
| ? mergeData.value |
| : ''; |
| // 值域 |
| if (dataRange) { |
| color = isNaN(value) ? color : dataRange.getColor(value); |
| |
| queryTarget = [mergeData, mlOption]; |
| nColor = this.deepQuery( |
| queryTarget, 'itemStyle.normal.color' |
| ) || color; |
| eColor = this.deepQuery( |
| queryTarget, 'itemStyle.emphasis.color' |
| ) || nColor; |
| // 有值域,并且值域返回null且用户没有自己定义颜色,则隐藏这个mark |
| if (nColor == null && eColor == null) { |
| continue; |
| } |
| } |
| |
| // 标准化一些参数 |
| data[i][0].tooltip = mergeData.tooltip |
| || {trigger:'item'}; // tooltip.trigger指定为item |
| data[i][0].name = typeof data[i][0].name != 'undefined' |
| ? data[i][0].name : ''; |
| data[i][1].name = typeof data[i][1].name != 'undefined' |
| ? data[i][1].name : ''; |
| data[i][0].value = typeof data[i][0].value != 'undefined' |
| ? data[i][0].value : ''; |
| |
| itemShape = this.getLineMarkShape( |
| mlOption, // markLine |
| seriesIndex, |
| data[i], // 数据 |
| i, |
| this.parsePercent(data[i][0].x, zrWidth), // 坐标 |
| this.parsePercent(data[i][0].y, zrHeight), // 坐标 |
| this.parsePercent(data[i][1].x, zrWidth), // 坐标 |
| this.parsePercent(data[i][1].y, zrHeight), // 坐标 |
| color // 默认symbol和color |
| ); |
| itemShape._mark = 'line'; |
| |
| effect = this.deepMerge( |
| [mergeData, mlOption], |
| 'effect' |
| ); |
| if (effect.show) { |
| itemShape.effect = effect; |
| } |
| |
| if (serie.type == ecConfig.CHART_TYPE_MAP) { |
| itemShape._geo = [ |
| this.getMarkGeo(data[i][0]), |
| this.getMarkGeo(data[i][1]) |
| ]; |
| } |
| |
| // 重新pack一下数据 |
| ecData.pack( |
| itemShape, |
| serie, seriesIndex, |
| data[i][0], i, |
| data[i][0].name + (data[i][1].name !== '' |
| ? (' > ' + data[i][1].name) |
| : ''), |
| value |
| ); |
| pList.push(itemShape); |
| } |
| //console.log(pList); |
| return pList; |
| }, |
| |
| getMarkCoord : function () { |
| // 无转换位置 |
| return [0, 0]; |
| }, |
| |
| /** |
| * symbol构造器 |
| */ |
| getSymbolShape : function ( |
| serie, seriesIndex, // 系列 |
| data, dataIndex, name, // 数据 |
| x, y, // 坐标 |
| symbol, color, // 默认symbol和color,来自legend或dataRange全局分配 |
| emptyColor, // 折线的emptySymbol用白色填充 |
| orient // 走向,用于默认文字定位 |
| ) { |
| var queryTarget = [data, serie]; |
| var value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| |
| symbol = this.deepQuery(queryTarget, 'symbol') || symbol; |
| var symbolSize = this.deepQuery(queryTarget, 'symbolSize'); |
| symbolSize = typeof symbolSize == 'function' |
| ? symbolSize(value) |
| : symbolSize; |
| var symbolRotate = this.deepQuery(queryTarget, 'symbolRotate'); |
| |
| var normal = this.deepMerge( |
| queryTarget, |
| 'itemStyle.normal' |
| ); |
| var emphasis = this.deepMerge( |
| queryTarget, |
| 'itemStyle.emphasis' |
| ); |
| var nBorderWidth = typeof normal.borderWidth != 'undefined' |
| ? normal.borderWidth |
| : (normal.lineStyle && normal.lineStyle.width); |
| if (typeof nBorderWidth == 'undefined') { |
| nBorderWidth = symbol.match('empty') ? 2 : 0; |
| } |
| var eBorderWidth = typeof emphasis.borderWidth != 'undefined' |
| ? emphasis.borderWidth |
| : (emphasis.lineStyle && emphasis.lineStyle.width); |
| if (typeof eBorderWidth == 'undefined') { |
| eBorderWidth = nBorderWidth + 2; |
| } |
| |
| var itemShape = new IconShape({ |
| style : { |
| iconType : symbol.replace('empty', '').toLowerCase(), |
| x : x - symbolSize, |
| y : y - symbolSize, |
| width : symbolSize * 2, |
| height : symbolSize * 2, |
| brushType : 'both', |
| color : symbol.match('empty') |
| ? emptyColor |
| : (this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data) |
| || color), |
| strokeColor : normal.borderColor |
| || this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data) |
| || color, |
| lineWidth: nBorderWidth |
| }, |
| highlightStyle : { |
| color : symbol.match('empty') |
| ? emptyColor |
| : this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data), |
| strokeColor : emphasis.borderColor |
| || normal.borderColor |
| || this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data) |
| || color, |
| lineWidth: eBorderWidth |
| }, |
| clickable : true |
| }); |
| |
| if (symbol.match('image')) { |
| itemShape.style.image = |
| symbol.replace(new RegExp('^image:\\/\\/'), ''); |
| itemShape = new ImageShape({ |
| style : itemShape.style, |
| highlightStyle : itemShape.highlightStyle, |
| clickable : true |
| }); |
| } |
| |
| if (typeof symbolRotate != 'undefined') { |
| itemShape.rotation = [ |
| symbolRotate * Math.PI / 180, x, y |
| ]; |
| } |
| |
| if (symbol.match('star')) { |
| itemShape.style.iconType = 'star'; |
| itemShape.style.n = |
| (symbol.replace('empty', '').replace('star','') - 0) || 5; |
| } |
| |
| if (symbol == 'none') { |
| itemShape.invisible = true; |
| itemShape.hoverable = false; |
| } |
| |
| /* |
| if (this.deepQuery([data, serie, option], 'calculable')) { |
| this.setCalculable(itemShape); |
| itemShape.draggable = true; |
| } |
| */ |
| |
| itemShape = this.addLabel( |
| itemShape, |
| serie, data, name, |
| orient |
| ); |
| |
| if (symbol.match('empty')) { |
| if (typeof itemShape.style.textColor == 'undefined') { |
| itemShape.style.textColor = itemShape.style.strokeColor; |
| } |
| if (typeof itemShape.highlightStyle.textColor == 'undefined') { |
| itemShape.highlightStyle.textColor = |
| itemShape.highlightStyle.strokeColor; |
| } |
| } |
| |
| ecData.pack( |
| itemShape, |
| serie, seriesIndex, |
| data, dataIndex, |
| name |
| ); |
| |
| itemShape._x = x; |
| itemShape._y = y; |
| |
| itemShape._dataIndex = dataIndex; |
| itemShape._seriesIndex = seriesIndex; |
| |
| return itemShape; |
| }, |
| |
| /** |
| * 标线构造器 |
| */ |
| getLineMarkShape : function ( |
| mlOption, // 系列 |
| seriesIndex, // 系列索引 |
| data, // 数据 |
| dataIndex, // 数据索引 |
| xStart, yStart, // 坐标 |
| xEnd, yEnd, // 坐标 |
| color // 默认color,来自legend或dataRange全局分配 |
| ) { |
| var value0 = typeof data[0] != 'undefined' |
| ? (typeof data[0].value != 'undefined' |
| ? data[0].value |
| : data[0]) |
| : '-'; |
| var value1 = typeof data[1] != 'undefined' |
| ? (typeof data[1].value != 'undefined' |
| ? data[1].value |
| : data[1]) |
| : '-'; |
| var symbol = [ |
| this.query(data[0], 'symbol') || mlOption.symbol[0], |
| this.query(data[1], 'symbol') || mlOption.symbol[1] |
| ]; |
| var symbolSize = [ |
| this.query(data[0], 'symbolSize') || mlOption.symbolSize[0], |
| this.query(data[1], 'symbolSize') || mlOption.symbolSize[1] |
| ]; |
| symbolSize[0] = typeof symbolSize[0] == 'function' |
| ? symbolSize[0](value0) |
| : symbolSize[0]; |
| symbolSize[1] = typeof symbolSize[1] == 'function' |
| ? symbolSize[1](value1) |
| : symbolSize[1]; |
| var symbolRotate = [ |
| this.query(data[0], 'symbolRotate') || mlOption.symbolRotate[0], |
| this.query(data[1], 'symbolRotate') || mlOption.symbolRotate[1] |
| ]; |
| //console.log(symbol, symbolSize, symbolRotate); |
| |
| var queryTarget = [data[0], mlOption]; |
| var normal = this.deepMerge( |
| queryTarget, |
| 'itemStyle.normal' |
| ); |
| normal.color = this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data); |
| var emphasis = this.deepMerge( |
| queryTarget, |
| 'itemStyle.emphasis' |
| ); |
| emphasis.color = this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data); |
| |
| var nlineStyle = normal.lineStyle; |
| var elineStyle = emphasis.lineStyle; |
| |
| var nBorderWidth = nlineStyle.width; |
| if (typeof nBorderWidth == 'undefined') { |
| nBorderWidth = normal.borderWidth; |
| } |
| var eBorderWidth = elineStyle.width; |
| if (typeof eBorderWidth == 'undefined') { |
| if (typeof emphasis.borderWidth != 'undefined') { |
| eBorderWidth = emphasis.borderWidth; |
| } |
| else { |
| eBorderWidth = nBorderWidth + 2; |
| } |
| } |
| |
| var itemShape = new MarkLineShape({ |
| style : { |
| smooth : mlOption.smooth ? 'spline' : false, |
| symbol : symbol, |
| symbolSize : symbolSize, |
| symbolRotate : symbolRotate, |
| //data : [data[0].name,data[1].name], |
| xStart : xStart, |
| yStart : yStart, // 坐标 |
| xEnd : xEnd, |
| yEnd : yEnd, // 坐标 |
| brushType : 'both', |
| lineType : nlineStyle.type, |
| shadowColor : nlineStyle.shadowColor |
| || nlineStyle.color |
| || normal.borderColor |
| || normal.color |
| || color, |
| shadowBlur: nlineStyle.shadowBlur, |
| shadowOffsetX: nlineStyle.shadowOffsetX, |
| shadowOffsetY: nlineStyle.shadowOffsetY, |
| color : normal.color || color, |
| strokeColor : nlineStyle.color |
| || normal.borderColor |
| || normal.color |
| || color, |
| lineWidth: nBorderWidth, |
| symbolBorderColor: normal.borderColor |
| || normal.color |
| || color, |
| symbolBorder: normal.borderWidth |
| }, |
| highlightStyle : { |
| shadowColor : elineStyle.shadowColor, |
| shadowBlur: elineStyle.shadowBlur, |
| shadowOffsetX: elineStyle.shadowOffsetX, |
| shadowOffsetY: elineStyle.shadowOffsetY, |
| color : emphasis.color|| normal.color || color, |
| strokeColor : elineStyle.color |
| || nlineStyle.color |
| || emphasis.borderColor |
| || normal.borderColor |
| || emphasis.color |
| || normal.color |
| || color, |
| lineWidth: eBorderWidth, |
| symbolBorderColor: emphasis.borderColor |
| || normal.borderColor |
| || emphasis.color |
| || normal.color |
| || color, |
| symbolBorder: typeof emphasis.borderWidth == 'undefined' |
| ? (normal.borderWidth + 2) |
| : (emphasis.borderWidth) |
| }, |
| clickable : true |
| }); |
| |
| itemShape = this.addLabel( |
| itemShape, |
| mlOption, |
| data[0], |
| data[0].name + ' : ' + data[1].name |
| ); |
| |
| itemShape._x = xEnd; |
| itemShape._y = yEnd; |
| |
| return itemShape; |
| }, |
| |
| /** |
| * 大规模标注构造器 |
| */ |
| getLargeMarkPoingShape : function(seriesIndex, mpOption) { |
| var serie = this.series[seriesIndex]; |
| var component = this.component; |
| var data = mpOption.data; |
| var itemShape; |
| |
| var dataRange = component.dataRange; |
| var legend = component.legend; |
| var color; |
| var value; |
| var queryTarget = [data[0], mpOption]; |
| var nColor; |
| var eColor; |
| var effect; |
| |
| // 图例 |
| if (legend) { |
| color = legend.getColor(serie.name); |
| } |
| // 值域 |
| if (dataRange) { |
| value = typeof data[0] != 'undefined' |
| ? (typeof data[0].value != 'undefined' |
| ? data[0].value |
| : data[0]) |
| : '-'; |
| color = isNaN(value) ? color : dataRange.getColor(value); |
| |
| nColor = this.deepQuery( |
| queryTarget, 'itemStyle.normal.color' |
| ) || color; |
| eColor = this.deepQuery( |
| queryTarget, 'itemStyle.emphasis.color' |
| ) || nColor; |
| // 有值域,并且值域返回null且用户没有自己定义颜色,则隐藏这个mark |
| if (nColor == null && eColor == null) { |
| return; |
| } |
| } |
| color = this.deepMerge(queryTarget, 'itemStyle.normal').color |
| || color; |
| |
| var symbol = this.deepQuery(queryTarget, 'symbol') || 'circle'; |
| symbol = symbol.replace('empty', '').replace(/\d/g, ''); |
| |
| effect = this.deepMerge( |
| [data[0], mpOption], |
| 'effect' |
| ); |
| |
| var devicePixelRatio = window.devicePixelRatio || 1; |
| |
| //console.log(data) |
| itemShape = new SymbolShape({ |
| style : { |
| pointList : data, |
| color : color, |
| strokeColor: color, |
| shadowColor : effect.shadowColor || color, |
| shadowBlur : (typeof effect.shadowBlur != 'undefined' ? effect.shadowBlur : 8) |
| * devicePixelRatio, |
| size : this.deepQuery(queryTarget, 'symbolSize'), |
| iconType : symbol, |
| brushType: 'fill', |
| lineWidth:1 |
| }, |
| draggable : false, |
| hoverable : false |
| }); |
| |
| if (effect.show) { |
| itemShape.effect = effect; |
| } |
| |
| return itemShape; |
| }, |
| |
| backupShapeList : function () { |
| if (this.shapeList && this.shapeList.length > 0) { |
| this.lastShapeList = this.shapeList; |
| this.shapeList = []; |
| } |
| else { |
| this.lastShapeList = []; |
| } |
| }, |
| |
| addShapeList : function () { |
| var maxLenth = this.option.animationThreshold / (this.canvasSupported ? 2 : 4); |
| var lastShapeList = this.lastShapeList; |
| var shapeList = this.shapeList; |
| var duration = lastShapeList.length > 0 |
| ? 500 : this.query(this.option, 'animationDuration'); |
| var easing = this.query(this.option, 'animationEasing'); |
| var key; |
| var oldMap = {}; |
| var newMap = {}; |
| if (this.option.animation |
| && !this.option.renderAsImage |
| && shapeList.length < maxLenth |
| && !this.motionlessOnce |
| ) { |
| // 通过已有的shape做动画过渡 |
| for (var i = 0, l = lastShapeList.length; i < l; i++) { |
| key = this._getAnimationKey(lastShapeList[i]); |
| if (key.match('undefined')) { |
| this.zr.delShape(lastShapeList[i].id); // 非关键元素直接删除 |
| } |
| else { |
| key += lastShapeList[i].type; |
| oldMap[key] = lastShapeList[i]; |
| } |
| } |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| key = this._getAnimationKey(shapeList[i]); |
| if (key.match('undefined')) { |
| this.zr.addShape(shapeList[i]); // 非关键元素直接添加 |
| } |
| else { |
| key += shapeList[i].type; |
| newMap[key] = shapeList[i]; |
| } |
| } |
| |
| for (key in oldMap) { |
| if (!newMap[key]) { |
| // 新的没有 删除 |
| this.zr.delShape(oldMap[key].id); |
| } |
| } |
| for (key in newMap) { |
| if (oldMap[key]) { |
| // 新旧都有 动画过渡 |
| this.zr.delShape(oldMap[key].id); |
| this._animateMod(oldMap[key], newMap[key], duration, easing); |
| } |
| else { |
| // 新有旧没有 添加并动画过渡 |
| //this._animateAdd(newMap[key], duration, easing); |
| this._animateMod(false, newMap[key], duration, easing); |
| } |
| } |
| this.zr.refresh(); |
| this.animationEffect(); |
| } |
| else { |
| this.motionlessOnce = false; |
| // clear old |
| this.zr.delShape(lastShapeList); |
| // 直接添加 |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| this.zr.addShape(shapeList[i]); |
| } |
| } |
| }, |
| |
| _getAnimationKey : function(shape) { |
| if (this.type != ecConfig.CHART_TYPE_MAP) { |
| return ecData.get(shape, 'seriesIndex') + '_' |
| + ecData.get(shape, 'dataIndex') |
| + (shape._mark ? shape._mark : '') |
| + (this.type == ecConfig.CHART_TYPE_RADAR |
| ? ecData.get(shape, 'special') : ''); |
| } |
| else { |
| return ecData.get(shape, 'seriesIndex') + '_' |
| + ecData.get(shape, 'dataIndex') |
| + (shape._mark ? shape._mark : 'undefined'); |
| } |
| }, |
| |
| /** |
| * 动画过渡 |
| */ |
| _animateMod : function (oldShape, newShape, duration, easing) { |
| switch (newShape.type) { |
| case 'broken-line' : |
| case 'half-smooth-polygon' : |
| ecAnimation.pointList(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'rectangle' : |
| ecAnimation.rectangle(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'icon' : |
| ecAnimation.icon(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'candle' : |
| if (duration > 500) { |
| ecAnimation.candle(this.zr, oldShape, newShape, duration, easing); |
| } |
| else { |
| this.zr.addShape(newShape); |
| } |
| break; |
| case 'ring' : |
| case 'sector' : |
| case 'circle' : |
| if (duration > 500) { |
| // 进入动画,加旋转 |
| ecAnimation.ring( |
| this.zr, |
| oldShape, |
| newShape, |
| duration + ((ecData.get(newShape, 'dataIndex') || 0) % 20 * 100), |
| easing |
| ); |
| } |
| else if (newShape.type == 'sector') { |
| ecAnimation.sector(this.zr, oldShape, newShape, duration, easing); |
| } |
| else { |
| this.zr.addShape(newShape); |
| } |
| break; |
| case 'text' : |
| ecAnimation.text(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'polygon' : |
| if (duration > 500) { |
| ecAnimation.polygon(this.zr, oldShape, newShape, duration, easing); |
| } |
| else { |
| ecAnimation.pointList(this.zr, oldShape, newShape, duration, easing); |
| } |
| break; |
| case 'chord' : |
| ecAnimation.chord(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'gauge-pointer' : |
| ecAnimation.gaugePointer(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'mark-line' : |
| ecAnimation.markline(this.zr, oldShape, newShape, duration, easing); |
| break; |
| case 'line' : |
| ecAnimation.line(this.zr, oldShape, newShape, duration, easing); |
| break; |
| default : |
| this.zr.addShape(newShape); |
| break; |
| } |
| }, |
| |
| /** |
| * 标注动画 |
| * @param {number} duration 时长 |
| * @param {string=} easing 缓动效果 |
| * @param {Array=} addShapeList 指定特效对象,不知道默认使用this.shapeList |
| */ |
| animationMark : function (duration , easing, addShapeList) { |
| var shapeList = addShapeList || this.shapeList; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (!shapeList[i]._mark) { |
| continue; |
| } |
| this._animateMod(false, shapeList[i], duration, easing); |
| } |
| this.animationEffect(addShapeList); |
| }, |
| |
| /** |
| * 特效动画 |
| * @param {Array=} addShapeList 指定特效对象,不知道默认使用this.shapeList |
| */ |
| animationEffect : function (addShapeList) { |
| !addShapeList && this.clearEffectShape(); |
| var shapeList = addShapeList || this.shapeList; |
| var zlevel = ecConfig.EFFECT_ZLEVEL; |
| if (this.canvasSupported) { |
| this.zr.modLayer( |
| zlevel, |
| { |
| motionBlur : true, |
| lastFrameAlpha : 0.95 |
| } |
| ); |
| } |
| var shape; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| shape = shapeList[i]; |
| if (!(shape._mark && shape.effect && shape.effect.show && ecEffect[shape._mark]) |
| ) { |
| continue; |
| } |
| ecEffect[shape._mark](this.zr, this.effectList, shape, zlevel); |
| this.effectList[this.effectList.length - 1]._mark = shape._mark; |
| } |
| }, |
| |
| clearEffectShape : function (clearMotionBlur) { |
| if (this.zr && this.effectList && this.effectList.length > 0) { |
| clearMotionBlur && this.zr.modLayer( |
| ecConfig.EFFECT_ZLEVEL, |
| { motionBlur : false} |
| ); |
| this.zr.delShape(this.effectList); |
| } |
| this.effectList = []; |
| }, |
| |
| /** |
| * 动态标线标注添加 |
| * @param {number} seriesIndex 系列索引 |
| * @param {Object} markData 标线标注对象,支持多个 |
| * @param {string} markType 标线标注类型 |
| */ |
| addMark : function (seriesIndex, markData, markType) { |
| var serie = this.series[seriesIndex]; |
| if (this.selectedMap[serie.name]) { |
| var duration = 500; |
| var easing = this.query(this.option, 'animationEasing'); |
| // 备份,复用_buildMarkX |
| var oriMarkData = serie[markType].data; |
| var lastLength = this.shapeList.length; |
| |
| serie[markType].data = markData.data; |
| this['_build' + markType.replace('m', 'M')](seriesIndex); |
| for (var i = lastLength, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| this.zr.refresh(); |
| |
| if (this.option.animation && !this.option.renderAsImage) { |
| this.animationMark(duration, easing, this.shapeList.slice(lastLength)); |
| } |
| // 还原,复用_buildMarkX |
| serie[markType].data = oriMarkData; |
| } |
| }, |
| |
| /** |
| * 动态标线标注删除 |
| * @param {number} seriesIndex 系列索引 |
| * @param {string} markName 标线标注名称 |
| * @param {string} markType 标线标注类型 |
| */ |
| delMark : function (seriesIndex, markName, markType) { |
| markType = markType.replace('mark', '').replace('large', '').toLowerCase(); |
| var serie = this.series[seriesIndex]; |
| if (this.selectedMap[serie.name]) { |
| var needRefresh = false; |
| var shapeList = [this.shapeList, this.effectList]; |
| var len = 2; |
| while(len--) { |
| for (var i = 0, l = shapeList[len].length; i < l; i++) { |
| if (shapeList[len][i]._mark == markType |
| && ecData.get(shapeList[len][i], 'seriesIndex') == seriesIndex |
| && ecData.get(shapeList[len][i], 'name') == markName |
| ) { |
| this.zr.delShape(shapeList[len][i].id); |
| shapeList[len].splice(i, 1); |
| needRefresh = true; |
| break; |
| } |
| } |
| } |
| |
| needRefresh && this.zr.refresh(); |
| } |
| } |
| }; |
| |
| return Base; |
| }); |
| |
| /** |
| * echart图表库 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| */ |
| define('echarts/chart',[],function (/*require*/) { //chart |
| var self = {}; |
| |
| var _chartLibrary = {}; //echart图表库 |
| |
| /** |
| * 定义图形实现 |
| * @param {Object} name |
| * @param {Object} clazz 图形实现 |
| */ |
| self.define = function (name, clazz) { |
| _chartLibrary[name] = clazz; |
| return self; |
| }; |
| |
| /** |
| * 获取图形实现 |
| * @param {Object} name |
| */ |
| self.get = function (name) { |
| return _chartLibrary[name]; |
| }; |
| |
| return self; |
| }); |
| /** |
| * echarts组件:孤岛数据 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/island',['require','../component/base','./base','zrender/shape/Circle','../config','../util/ecData','zrender/tool/util','zrender/tool/event','zrender/tool/color','../util/accMath','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var CircleShape = require('zrender/shape/Circle'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrEvent = require('zrender/tool/event'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表选项 |
| */ |
| function Island(ecTheme, messageCenter, zr, option, myChart) { |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, {}, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this._nameConnector; |
| this._valueConnector; |
| this._zrHeight = this.zr.getHeight(); |
| this._zrWidth = this.zr.getWidth(); |
| |
| var self = this; |
| /** |
| * 滚轮改变孤岛数据值 |
| */ |
| self.shapeHandler.onmousewheel = function (param) { |
| var shape = param.target; |
| |
| var event = param.event; |
| var delta = zrEvent.getDelta(event); |
| delta = delta > 0 ? (-1) : 1; |
| shape.style.r -= delta; |
| shape.style.r = shape.style.r < 5 ? 5 : shape.style.r; |
| |
| var value = ecData.get(shape, 'value'); |
| var dvalue = value * self.option.island.calculateStep; |
| if (dvalue > 1) { |
| value = Math.round(value - dvalue * delta); |
| } |
| else { |
| value = (value - dvalue * delta).toFixed(2) - 0; |
| } |
| |
| var name = ecData.get(shape, 'name'); |
| shape.style.text = name + ':' + value; |
| |
| ecData.set(shape, 'value', value); |
| ecData.set(shape, 'name', name); |
| |
| self.zr.modShape(shape.id); |
| self.zr.refresh(); |
| zrEvent.stop(event); |
| }; |
| } |
| |
| Island.prototype = { |
| type : ecConfig.CHART_TYPE_ISLAND, |
| /** |
| * 孤岛合并 |
| * |
| * @param {string} tarShapeIndex 目标索引 |
| * @param {Object} srcShape 源目标,合入目标后删除 |
| */ |
| _combine : function (tarShape, srcShape) { |
| var zrColor = require('zrender/tool/color'); |
| var accMath = require('../util/accMath'); |
| var value = accMath.accAdd( |
| ecData.get(tarShape, 'value'), |
| ecData.get(srcShape, 'value') |
| ); |
| var name = ecData.get(tarShape, 'name') |
| + this._nameConnector |
| + ecData.get(srcShape, 'name'); |
| |
| tarShape.style.text = name + this._valueConnector + value; |
| |
| ecData.set(tarShape, 'value', value); |
| ecData.set(tarShape, 'name', name); |
| tarShape.style.r = this.option.island.r; |
| tarShape.style.color = zrColor.mix( |
| tarShape.style.color, |
| srcShape.style.color |
| ); |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| newOption.island = this.reformOption(newOption.island); |
| this.option = newOption; |
| |
| this._nameConnector = this.option.nameConnector; |
| this._valueConnector = this.option.valueConnector; |
| } |
| }, |
| |
| getOption : function () { |
| return this.option; |
| }, |
| |
| resize : function () { |
| var newWidth = this.zr.getWidth(); |
| var newHieght = this.zr.getHeight(); |
| var xScale = newWidth / (this._zrWidth || newWidth); |
| var yScale = newHieght / (this._zrHeight || newHieght); |
| if (xScale == 1 && yScale == 1) { |
| return; |
| } |
| this._zrWidth = newWidth; |
| this._zrHeight = newHieght; |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.modShape( |
| this.shapeList[i].id, |
| { |
| style: { |
| x: Math.round(this.shapeList[i].style.x * xScale), |
| y: Math.round(this.shapeList[i].style.y * yScale) |
| } |
| } |
| ); |
| } |
| }, |
| |
| add : function (shape) { |
| var name = ecData.get(shape, 'name'); |
| var value = ecData.get(shape, 'value'); |
| var seriesName = typeof ecData.get(shape, 'series') != 'undefined' |
| ? ecData.get(shape, 'series').name |
| : ''; |
| var font = this.getFont(this.option.island.textStyle); |
| var islandShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| x : shape.style.x, |
| y : shape.style.y, |
| r : this.option.island.r, |
| color : shape.style.color || shape.style.strokeColor, |
| text : name + this._valueConnector + value, |
| textFont : font |
| }, |
| draggable : true, |
| hoverable : true, |
| onmousewheel : this.shapeHandler.onmousewheel, |
| _type : 'island' |
| }; |
| if (islandShape.style.color == '#fff') { |
| islandShape.style.color = shape.style.strokeColor; |
| } |
| this.setCalculable(islandShape); |
| islandShape.dragEnableTime = 0; |
| ecData.pack( |
| islandShape, |
| {name:seriesName}, -1, |
| value, -1, |
| name |
| ); |
| islandShape = new CircleShape(islandShape); |
| this.shapeList.push(islandShape); |
| this.zr.addShape(islandShape); |
| }, |
| |
| del : function (shape) { |
| this.zr.delShape(shape.id); |
| var newShapeList = []; |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| if (this.shapeList[i].id != shape.id) { |
| newShapeList.push(this.shapeList[i]); |
| } |
| } |
| this.shapeList = newShapeList; |
| }, |
| |
| /** |
| * 数据项被拖拽进来, 重载基类方法 |
| */ |
| ondrop : function (param, status) { |
| if (!this.isDrop || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| // 拖拽产生孤岛数据合并 |
| var target = param.target; // 拖拽安放目标 |
| var dragged = param.dragged; // 当前被拖拽的图形对象 |
| |
| this._combine(target, dragged); |
| this.zr.modShape(target.id); |
| |
| status.dragIn = true; |
| |
| // 处理完拖拽事件后复位 |
| this.isDrop = false; |
| |
| return; |
| }, |
| |
| /** |
| * 数据项被拖拽出去, 重载基类方法 |
| */ |
| ondragend : function (param, status) { |
| var target = param.target; // 拖拽安放目标 |
| if (!this.isDragend) { |
| // 拖拽的不是孤岛数据,如果没有图表接受孤岛数据,需要新增孤岛数据 |
| if (!status.dragIn) { |
| target.style.x = zrEvent.getX(param.event); |
| target.style.y = zrEvent.getY(param.event); |
| this.add(target); |
| status.needRefresh = true; |
| } |
| } |
| else { |
| // 拖拽的是孤岛数据,如果有图表接受了孤岛数据,需要删除孤岛数据 |
| if (status.dragIn) { |
| this.del(target); |
| status.needRefresh = true; |
| } |
| } |
| |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| |
| return; |
| } |
| }; |
| |
| zrUtil.inherits(Island, ChartBase); |
| zrUtil.inherits(Island, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('island', Island); |
| |
| return Island; |
| }); |
| /** |
| * echart组件库 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component',[],function (/*require*/) { // component |
| var self = {}; |
| |
| var _componentLibrary = {}; // echart组件库 |
| |
| /** |
| * 定义图形实现 |
| * @param {Object} name |
| * @param {Object} clazz 图形实现 |
| */ |
| self.define = function (name, clazz) { |
| _componentLibrary[name] = clazz; |
| return self; |
| }; |
| |
| /** |
| * 获取图形实现 |
| * @param {Object} name |
| */ |
| self.get = function (name) { |
| return _componentLibrary[name]; |
| }; |
| |
| return self; |
| }); |
| /** |
| * echarts组件:提示框 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/dataView',['require','./base','../config','zrender/tool/util','../component'],function (require) { |
| var Base = require('./base'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 提示框参数 |
| * @param {HtmlElement} dom 目标对象 |
| */ |
| function DataView(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.dom = myChart.dom; |
| |
| // dataview dom & css |
| this._tDom = document.createElement('div'); |
| this._textArea = document.createElement('textArea'); |
| this._buttonRefresh = document.createElement('button'); |
| this._buttonClose = document.createElement('button'); |
| this._hasShow = false; |
| |
| // 缓存一些高宽数据 |
| this._zrHeight = zr.getHeight(); |
| this._zrWidth = zr.getWidth(); |
| |
| this._tDom.className = 'echarts-dataview', |
| this.hide(); |
| this.dom.firstChild.appendChild(this._tDom); |
| |
| if (window.addEventListener) { |
| this._tDom.addEventListener('click', this._stop); |
| this._tDom.addEventListener('mousewheel', this._stop); |
| this._tDom.addEventListener('mousemove', this._stop); |
| this._tDom.addEventListener('mousedown', this._stop); |
| this._tDom.addEventListener('mouseup', this._stop); |
| |
| // mobile支持 |
| this._tDom.addEventListener('touchstart', this._stop); |
| this._tDom.addEventListener('touchmove', this._stop); |
| this._tDom.addEventListener('touchend', this._stop); |
| } |
| else { |
| this._tDom.attachEvent('onclick', this._stop); |
| this._tDom.attachEvent('onmousewheel', this._stop); |
| this._tDom.attachEvent('onmousemove', this._stop); |
| this._tDom.attachEvent('onmousedown', this._stop); |
| this._tDom.attachEvent('onmouseup', this._stop); |
| } |
| } |
| |
| DataView.prototype = { |
| type : ecConfig.COMPONENT_TYPE_DATAVIEW, |
| _lang : ['Data View', 'close', 'refresh'], |
| // 通用样式 |
| _gCssText : 'position:absolute;' |
| + 'display:block;' |
| + 'overflow:hidden;' |
| + 'transition:height 0.8s,background-color 1s;' |
| + '-moz-transition:height 0.8s,background-color 1s;' |
| + '-webkit-transition:height 0.8s,background-color 1s;' |
| + '-o-transition:height 0.8s,background-color 1s;' |
| + 'z-index:1;' |
| + 'left:0;' |
| + 'top:0;', |
| hide : function () { |
| this._sizeCssText = 'width:' + this._zrWidth + 'px;' |
| + 'height:' + 0 + 'px;' |
| + 'background-color:#f0ffff;'; |
| this._tDom.style.cssText = this._gCssText + this._sizeCssText; |
| // 这是个很恶心的事情 |
| /* |
| this.dom.onselectstart = function () { |
| return false; |
| }; |
| */ |
| }, |
| |
| show : function (newOption) { |
| this._hasShow = true; |
| var lang = this.query(this.option, 'toolbox.feature.dataView.lang') |
| || this._lang; |
| |
| this.option = newOption; |
| |
| this._tDom.innerHTML = '<p style="padding:8px 0;margin:0 0 10px 0;' |
| + 'border-bottom:1px solid #eee">' |
| + (lang[0] || this._lang[0]) |
| + '</p>'; |
| |
| this._textArea.style.cssText = |
| 'display:block;margin:0 0 8px 0;padding:4px 6px;overflow:auto;' |
| + 'width:' + (this._zrWidth - 15) + 'px;' |
| + 'height:' + (this._zrHeight - 100) + 'px;'; |
| var customContent = this.query( |
| this.option, 'toolbox.feature.dataView.optionToContent' |
| ); |
| if (typeof customContent != 'function') { |
| this._textArea.value = this._optionToContent(); |
| } |
| else { |
| this._textArea.value = customContent(this.option); |
| } |
| this._tDom.appendChild(this._textArea); |
| |
| this._buttonClose.style.cssText = 'float:right;padding:1px 6px;'; |
| this._buttonClose.innerHTML = lang[1] || this._lang[1]; |
| var self = this; |
| this._buttonClose.onclick = function (){ |
| self.hide(); |
| }; |
| this._tDom.appendChild(this._buttonClose); |
| |
| if (this.query(this.option, 'toolbox.feature.dataView.readOnly') |
| === false |
| ) { |
| this._buttonRefresh.style.cssText = |
| 'float:right;margin-right:10px;padding:1px 6px;'; |
| this._buttonRefresh.innerHTML = lang[2] || this._lang[2]; |
| this._buttonRefresh.onclick = function (){ |
| self._save(); |
| }; |
| this._tDom.appendChild(this._buttonRefresh); |
| this._textArea.readOnly = false; |
| this._textArea.style.cursor = 'default'; |
| } |
| else { |
| this._textArea.readOnly = true; |
| this._textArea.style.cursor = 'text'; |
| } |
| |
| this._sizeCssText = 'width:' + this._zrWidth + 'px;' |
| + 'height:' + this._zrHeight + 'px;' |
| + 'background-color:#fff;'; |
| this._tDom.style.cssText = this._gCssText + this._sizeCssText; |
| // 这是个很恶心的事情 |
| /* |
| this.dom.onselectstart = function () { |
| return true; |
| }; |
| */ |
| }, |
| |
| _optionToContent : function () { |
| var i; |
| var j; |
| var k; |
| var len; |
| var data; |
| var valueList; |
| var axisList = []; |
| var content = ''; |
| if (this.option.xAxis) { |
| if (this.option.xAxis instanceof Array) { |
| axisList = this.option.xAxis; |
| } else { |
| axisList = [this.option.xAxis]; |
| } |
| for (i = 0, len = axisList.length; i < len; i++) { |
| // 横纵默认为类目 |
| if ((axisList[i].type || 'category') == 'category') { |
| valueList = []; |
| for (j = 0, k = axisList[i].data.length; j < k; j++) { |
| data = axisList[i].data[j]; |
| valueList.push( |
| typeof data.value != 'undefined' |
| ? data.value : data |
| ); |
| } |
| content += valueList.join(', ') + '\n\n'; |
| } |
| } |
| } |
| |
| if (this.option.yAxis) { |
| if (this.option.yAxis instanceof Array) { |
| axisList = this.option.yAxis; |
| } else { |
| axisList = [this.option.yAxis]; |
| } |
| for (i = 0, len = axisList.length; i < len; i++) { |
| if (axisList[i].type == 'category') { |
| valueList = []; |
| for (j = 0, k = axisList[i].data.length; j < k; j++) { |
| data = axisList[i].data[j]; |
| valueList.push( |
| typeof data.value != 'undefined' |
| ? data.value : data |
| ); |
| } |
| content += valueList.join(', ') + '\n\n'; |
| } |
| } |
| } |
| |
| var series = this.option.series; |
| var itemName; |
| for (i = 0, len = series.length; i < len; i++) { |
| valueList = []; |
| for (j = 0, k = series[i].data.length; j < k; j++) { |
| data = series[i].data[j]; |
| if (series[i].type == ecConfig.CHART_TYPE_PIE |
| || series[i].type == ecConfig.CHART_TYPE_MAP |
| ) { |
| itemName = (data.name || '-') + ':'; |
| } |
| else { |
| itemName = ''; |
| } |
| |
| if (series[i].type == ecConfig.CHART_TYPE_SCATTER) { |
| data = typeof data.value != 'undefined' |
| ? data.value |
| : data; |
| data = data.join(', '); |
| } |
| valueList.push( |
| itemName |
| + (typeof data.value != 'undefined' ? data.value : data) |
| ); |
| } |
| content += (series[i].name || '-') + ' : \n'; |
| content += valueList.join( |
| series[i].type == ecConfig.CHART_TYPE_SCATTER ? '\n': ', ' |
| ); |
| content += '\n\n'; |
| } |
| |
| return content; |
| }, |
| |
| _save : function () { |
| var text = this._textArea.value; |
| var customContent = this.query( |
| this.option, 'toolbox.feature.dataView.contentToOption' |
| ); |
| if (typeof customContent != 'function') { |
| text = text.split('\n'); |
| var content = []; |
| for (var i = 0, l = text.length; i < l; i++) { |
| text[i] = this._trim(text[i]); |
| if (text[i] !== '') { |
| content.push(text[i]); |
| } |
| } |
| this._contentToOption(content); |
| } |
| else { |
| customContent(text, this.option); |
| } |
| |
| this.hide(); |
| |
| var self = this; |
| setTimeout( |
| function (){ |
| self.messageCenter && self.messageCenter.dispatch( |
| ecConfig.EVENT.DATA_VIEW_CHANGED, |
| null, |
| {option : self.option}, |
| self.myChart |
| ); |
| }, |
| // 有动画,所以高级浏览器时间更长点 |
| self.canvasSupported ? 800 : 100 |
| ); |
| }, |
| |
| _contentToOption : function (content) { |
| var i; |
| var j; |
| var k; |
| var len; |
| var data; |
| var axisList = []; |
| |
| var contentIdx = 0; |
| var contentValueList; |
| var value; |
| |
| if (this.option.xAxis) { |
| if (this.option.xAxis instanceof Array) { |
| axisList = this.option.xAxis; |
| } else { |
| axisList = [this.option.xAxis]; |
| } |
| for (i = 0, len = axisList.length; i < len; i++) { |
| // 横纵默认为类目 |
| if ((axisList[i].type || 'category') == 'category' |
| ) { |
| contentValueList = content[contentIdx].split(','); |
| for (j = 0, k = axisList[i].data.length; j < k; j++) { |
| value = this._trim(contentValueList[j] || ''); |
| data = axisList[i].data[j]; |
| if (typeof axisList[i].data[j].value != 'undefined' |
| ) { |
| axisList[i].data[j].value = value; |
| } |
| else { |
| axisList[i].data[j] = value; |
| } |
| } |
| contentIdx++; |
| } |
| } |
| } |
| |
| if (this.option.yAxis) { |
| if (this.option.yAxis instanceof Array) { |
| axisList = this.option.yAxis; |
| } else { |
| axisList = [this.option.yAxis]; |
| } |
| for (i = 0, len = axisList.length; i < len; i++) { |
| if (axisList[i].type == 'category') { |
| contentValueList = content[contentIdx].split(','); |
| for (j = 0, k = axisList[i].data.length; j < k; j++) { |
| value = this._trim(contentValueList[j] || ''); |
| data = axisList[i].data[j]; |
| if (typeof axisList[i].data[j].value != 'undefined' |
| ) { |
| axisList[i].data[j].value = value; |
| } |
| else { |
| axisList[i].data[j] = value; |
| } |
| } |
| contentIdx++; |
| } |
| } |
| } |
| |
| var series = this.option.series; |
| for (i = 0, len = series.length; i < len; i++) { |
| contentIdx++; |
| if (series[i].type == ecConfig.CHART_TYPE_SCATTER) { |
| for (var j = 0, k = series[i].data.length; j < k; j++) { |
| contentValueList = content[contentIdx]; |
| value = contentValueList.replace(' ','').split(','); |
| if (typeof series[i].data[j].value != 'undefined' |
| ) { |
| series[i].data[j].value = value; |
| } |
| else { |
| series[i].data[j] = value; |
| } |
| contentIdx++; |
| } |
| } |
| else { |
| contentValueList = content[contentIdx].split(','); |
| for (var j = 0, k = series[i].data.length; j < k; j++) { |
| value = (contentValueList[j] || '').replace(/.*:/,''); |
| value = this._trim(value); |
| value = (value != '-' && value !== '') |
| ? (value - 0) |
| : '-'; |
| if (typeof series[i].data[j].value != 'undefined' |
| ) { |
| series[i].data[j].value = value; |
| } |
| else { |
| series[i].data[j] = value; |
| } |
| } |
| contentIdx++; |
| } |
| } |
| }, |
| |
| _trim : function (str){ |
| var trimer = new RegExp( |
| '(^[\\s\\t\\xa0\\u3000]+)|([\\u3000\\xa0\\s\\t]+\x24)', 'g' |
| ); |
| return str.replace(trimer, ''); |
| }, |
| |
| // 阻塞zrender事件 |
| _stop : function (e){ |
| e = e || window.event; |
| if (e.stopPropagation) { |
| e.stopPropagation(); |
| } |
| else { |
| e.cancelBubble = true; |
| } |
| }, |
| |
| /** |
| * zrender事件响应:窗口大小改变 |
| */ |
| resize : function () { |
| this._zrHeight = this.zr.getHeight(); |
| this._zrWidth = this.zr.getWidth(); |
| if (this._tDom.offsetHeight > 10) { |
| this._sizeCssText = 'width:' + this._zrWidth + 'px;' |
| + 'height:' + this._zrHeight + 'px;' |
| + 'background-color:#fff;'; |
| this._tDom.style.cssText = this._gCssText + this._sizeCssText; |
| this._textArea.style.cssText = 'display:block;margin:0 0 8px 0;' |
| + 'padding:4px 6px;overflow:auto;' |
| + 'width:' + (this._zrWidth - 15) + 'px;' |
| + 'height:' + (this._zrHeight - 100) + 'px;'; |
| } |
| }, |
| |
| /** |
| * 释放后实例不可用,重载基类方法 |
| */ |
| dispose : function () { |
| if (window.removeEventListener) { |
| this._tDom.removeEventListener('click', this._stop); |
| this._tDom.removeEventListener('mousewheel', this._stop); |
| this._tDom.removeEventListener('mousemove', this._stop); |
| this._tDom.removeEventListener('mousedown', this._stop); |
| this._tDom.removeEventListener('mouseup', this._stop); |
| |
| // mobile支持 |
| this._tDom.removeEventListener('touchstart', this._stop); |
| this._tDom.removeEventListener('touchmove', this._stop); |
| this._tDom.removeEventListener('touchend', this._stop); |
| } |
| else { |
| this._tDom.detachEvent('onclick', this._stop); |
| this._tDom.detachEvent('onmousewheel', this._stop); |
| this._tDom.detachEvent('onmousemove', this._stop); |
| this._tDom.detachEvent('onmousedown', this._stop); |
| this._tDom.detachEvent('onmouseup', this._stop); |
| } |
| |
| this._buttonRefresh.onclick = null; |
| this._buttonClose.onclick = null; |
| |
| if (this._hasShow) { |
| this._tDom.removeChild(this._textArea); |
| this._tDom.removeChild(this._buttonRefresh); |
| this._tDom.removeChild(this._buttonClose); |
| } |
| |
| this._textArea = null; |
| this._buttonRefresh = null; |
| this._buttonClose = null; |
| |
| this.dom.firstChild.removeChild(this._tDom); |
| this._tDom = null; |
| } |
| }; |
| |
| zrUtil.inherits(DataView, Base); |
| |
| require('../component').define('dataView', DataView); |
| |
| return DataView; |
| }); |
| /** |
| * echarts组件:工具箱 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/toolbox',['require','./base','zrender/shape/Line','zrender/shape/Image','zrender/shape/Rectangle','../util/shape/Icon','../config','zrender/tool/util','zrender/config','zrender/tool/event','./dataView','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var LineShape = require('zrender/shape/Line'); |
| var ImageShape = require('zrender/shape/Image'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var IconShape = require('../util/shape/Icon'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrConfig = require('zrender/config'); |
| var zrEvent = require('zrender/tool/event'); |
| |
| var _MAGICTYPE_STACK = 'stack'; |
| var _MAGICTYPE_TILED = 'tiled'; |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {HtmlElement} dom 目标对象 |
| * @param {ECharts} myChart 当前图表实例 |
| */ |
| function Toolbox(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.dom = myChart.dom; |
| |
| this._magicType = {}; |
| //this._magicMap; |
| this._isSilence = false; |
| |
| this._iconList; |
| this._iconShapeMap = {}; |
| //this._itemGroupLocation; |
| this._featureTitle = {}; // 文字 |
| this._featureIcon = {}; // 图标 |
| this._featureColor = {}; // 颜色 |
| this._enableColor = 'red'; |
| this._disableColor = '#ccc'; |
| // this._markStart; |
| // this._marking; |
| // this._markShape; |
| // this._zoomStart; |
| // this._zooming; |
| // this._zoomShape; |
| // this._zoomQueue; |
| // this._dataView; |
| this._markShapeList = []; |
| var self = this; |
| self._onMark = function (param) { |
| self.__onMark(param); |
| }; |
| self._onMarkUndo = function (param) { |
| self.__onMarkUndo(param); |
| }; |
| self._onMarkClear = function (param) { |
| self.__onMarkClear(param); |
| }; |
| self._onDataZoom = function (param) { |
| self.__onDataZoom(param); |
| }; |
| self._onDataZoomReset = function (param) { |
| self.__onDataZoomReset(param); |
| }; |
| self._onDataView = function (param) { |
| self.__onDataView(param); |
| }; |
| self._onRestore = function (param) { |
| self.__onRestore(param); |
| }; |
| self._onSaveAsImage = function (param) { |
| self.__onSaveAsImage(param); |
| }; |
| self._onMagicType = function (param) { |
| self.__onMagicType(param); |
| }; |
| self._onCustomHandler = function (param) { |
| self.__onCustomHandler(param); |
| }; |
| self._onmousemove = function (param) { |
| return self.__onmousemove(param); |
| }; |
| |
| self._onmousedown = function (param) { |
| return self.__onmousedown(param); |
| }; |
| |
| self._onmouseup = function (param) { |
| return self.__onmouseup(param); |
| }; |
| |
| self._onclick = function (param) { |
| return self.__onclick(param); |
| }; |
| } |
| |
| Toolbox.prototype = { |
| type : ecConfig.COMPONENT_TYPE_TOOLBOX, |
| _buildShape : function () { |
| this._iconList = []; |
| var toolboxOption = this.option.toolbox; |
| this._enableColor = toolboxOption.effectiveColor; |
| this._disableColor = toolboxOption.disableColor; |
| var feature = toolboxOption.feature; |
| var iconName = []; |
| for (var key in feature){ |
| if (feature[key].show) { |
| switch (key) { |
| case 'mark' : |
| iconName.push({key : key, name : 'mark'}); |
| iconName.push({key : key, name : 'markUndo'}); |
| iconName.push({key : key, name : 'markClear'}); |
| break; |
| case 'magicType' : |
| for (var i = 0, l = feature[key].type.length; i < l; i++) { |
| feature[key].title[feature[key].type[i] + 'Chart'] |
| = feature[key].title[feature[key].type[i]]; |
| iconName.push({key : key, name : feature[key].type[i] + 'Chart'}); |
| } |
| break; |
| case 'dataZoom' : |
| iconName.push({key : key, name : 'dataZoom'}); |
| iconName.push({key : key, name : 'dataZoomReset'}); |
| break; |
| case 'saveAsImage' : |
| if (this.canvasSupported) { |
| iconName.push({key : key, name : 'saveAsImage'}); |
| } |
| break; |
| default : |
| iconName.push({key : key, name : key}); |
| break; |
| } |
| } |
| } |
| if (iconName.length > 0) { |
| var name; |
| var key; |
| for (var i = 0, l = iconName.length; i < l; i++) { |
| name = iconName[i].name; |
| key = iconName[i].key; |
| this._iconList.push(name); |
| this._featureTitle[name] = feature[key].title[name] || feature[key].title; |
| if (feature[key].icon) { |
| this._featureIcon[name] = feature[key].icon[name] || feature[key].icon; |
| } |
| if (feature[key].color) { |
| this._featureColor[name] = feature[key].color[name] || feature[key].color; |
| } |
| } |
| this._itemGroupLocation = this._getItemGroupLocation(); |
| |
| this._buildBackground(); |
| this._buildItem(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| if (this._iconShapeMap['mark']) { |
| this._iconDisable(this._iconShapeMap['markUndo']); |
| this._iconDisable(this._iconShapeMap['markClear']); |
| } |
| if (this._iconShapeMap['dataZoomReset'] && this._zoomQueue.length === 0) { |
| this._iconDisable(this._iconShapeMap['dataZoomReset']); |
| } |
| } |
| }, |
| |
| /** |
| * 构建所有图例元素 |
| */ |
| _buildItem : function () { |
| var toolboxOption = this.option.toolbox; |
| var iconLength = this._iconList.length; |
| var lastX = this._itemGroupLocation.x; |
| var lastY = this._itemGroupLocation.y; |
| var itemSize = toolboxOption.itemSize; |
| var itemGap = toolboxOption.itemGap; |
| var itemShape; |
| |
| var color = toolboxOption.color instanceof Array |
| ? toolboxOption.color : [toolboxOption.color]; |
| |
| var textFont = this.getFont(toolboxOption.textStyle); |
| var textPosition; |
| var textAlign; |
| var textBaseline; |
| if (toolboxOption.orient == 'horizontal') { |
| textPosition = this._itemGroupLocation.y / this.zr.getHeight() < 0.5 |
| ? 'bottom' : 'top'; |
| textAlign = this._itemGroupLocation.x / this.zr.getWidth() < 0.5 |
| ? 'left' : 'right'; |
| textBaseline = this._itemGroupLocation.y / this.zr.getHeight() < 0.5 |
| ? 'top' : 'bottom'; |
| } |
| else { |
| textPosition = this._itemGroupLocation.x / this.zr.getWidth() < 0.5 |
| ? 'right' : 'left'; |
| /* |
| textAlign = this._itemGroupLocation.x / this.zr.getWidth() < 0.5 |
| ? 'right' : 'left'; |
| textBaseline = 'top'; |
| */ |
| } |
| |
| this._iconShapeMap = {}; |
| var self = this; |
| |
| for (var i = 0; i < iconLength; i++) { |
| // 图形 |
| itemShape = { |
| type : 'icon', |
| zlevel : this._zlevelBase, |
| style : { |
| x : lastX, |
| y : lastY, |
| width : itemSize, |
| height : itemSize, |
| iconType : this._iconList[i], |
| lineWidth : 1, |
| strokeColor : this._featureColor[this._iconList[i]] |
| || color[i % color.length], |
| brushType: 'stroke' |
| }, |
| highlightStyle : { |
| lineWidth : 1, |
| text : toolboxOption.showTitle |
| ? this._featureTitle[this._iconList[i]] |
| : undefined, |
| textFont : textFont, |
| textPosition : textPosition, |
| strokeColor : this._featureColor[this._iconList[i]] |
| || color[i % color.length] |
| }, |
| hoverable : true, |
| clickable : true |
| }; |
| |
| if (this._featureIcon[this._iconList[i]]) { |
| itemShape.style.image = this._featureIcon[this._iconList[i]].replace( |
| new RegExp('^image:\\/\\/'), '' |
| ); |
| itemShape.style.opacity = 0.8; |
| itemShape.highlightStyle.opacity = 1; |
| itemShape.type = 'image'; |
| } |
| |
| if (toolboxOption.orient == 'horizontal') { |
| // 修正左对齐第一个或右对齐最后一个 |
| if (i === 0 && textAlign == 'left') { |
| itemShape.highlightStyle.textPosition = 'specific'; |
| itemShape.highlightStyle.textAlign = textAlign; |
| itemShape.highlightStyle.textBaseline = textBaseline; |
| itemShape.highlightStyle.textX = lastX; |
| itemShape.highlightStyle.textY = textBaseline == 'top' |
| ? lastY + itemSize + 10 |
| : lastY - 10; |
| } |
| if (i == iconLength - 1 && textAlign == 'right') { |
| itemShape.highlightStyle.textPosition = 'specific'; |
| itemShape.highlightStyle.textAlign = textAlign; |
| itemShape.highlightStyle.textBaseline = textBaseline; |
| itemShape.highlightStyle.textX = lastX + itemSize; |
| itemShape.highlightStyle.textY = textBaseline == 'top' |
| ? lastY + itemSize + 10 |
| : lastY - 10; |
| } |
| } |
| |
| switch(this._iconList[i]) { |
| case 'mark': |
| itemShape.onclick = self._onMark; |
| break; |
| case 'markUndo': |
| itemShape.onclick = self._onMarkUndo; |
| break; |
| case 'markClear': |
| itemShape.onclick = self._onMarkClear; |
| break; |
| case 'dataZoom': |
| itemShape.onclick = self._onDataZoom; |
| break; |
| case 'dataZoomReset': |
| itemShape.onclick = self._onDataZoomReset; |
| break; |
| case 'dataView' : |
| if (!this._dataView) { |
| var DataView = require('./dataView'); |
| this._dataView = new DataView( |
| this.ecTheme, this.messageCenter, this.zr, this.option, this.myChart |
| ); |
| } |
| itemShape.onclick = self._onDataView; |
| break; |
| case 'restore': |
| itemShape.onclick = self._onRestore; |
| break; |
| case 'saveAsImage': |
| itemShape.onclick = self._onSaveAsImage; |
| break; |
| default: |
| if (this._iconList[i].match('Chart')) { |
| itemShape._name = this._iconList[i].replace('Chart', ''); |
| /* |
| if (this._magicType[itemShape._name]) { |
| itemShape.style.strokeColor = this._enableColor; |
| } |
| */ |
| itemShape.onclick = self._onMagicType; |
| } |
| else { |
| itemShape.onclick = self._onCustomHandler; |
| } |
| break; |
| } |
| |
| if (itemShape.type == 'icon') { |
| itemShape = new IconShape(itemShape); |
| } |
| else if (itemShape.type == 'image') { |
| itemShape = new ImageShape(itemShape); |
| } |
| this.shapeList.push(itemShape); |
| this._iconShapeMap[this._iconList[i]] = itemShape; |
| |
| if (toolboxOption.orient == 'horizontal') { |
| lastX += itemSize + itemGap; |
| } |
| else { |
| lastY += itemSize + itemGap; |
| } |
| } |
| }, |
| |
| _buildBackground : function () { |
| var toolboxOption = this.option.toolbox; |
| var pTop = toolboxOption.padding[0]; |
| var pRight = toolboxOption.padding[1]; |
| var pBottom = toolboxOption.padding[2]; |
| var pLeft = toolboxOption.padding[3]; |
| |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : this._itemGroupLocation.x - pLeft, |
| y : this._itemGroupLocation.y - pTop, |
| width : this._itemGroupLocation.width + pLeft + pRight, |
| height : this._itemGroupLocation.height + pTop + pBottom, |
| brushType : toolboxOption.borderWidth === 0 |
| ? 'fill' : 'both', |
| color : toolboxOption.backgroundColor, |
| strokeColor : toolboxOption.borderColor, |
| lineWidth : toolboxOption.borderWidth |
| } |
| })); |
| }, |
| |
| /** |
| * 根据选项计算图例实体的位置坐标 |
| */ |
| _getItemGroupLocation : function () { |
| var toolboxOption = this.option.toolbox; |
| var iconLength = this._iconList.length; |
| var itemGap = toolboxOption.itemGap; |
| var itemSize = toolboxOption.itemSize; |
| var totalWidth = 0; |
| var totalHeight = 0; |
| |
| if (toolboxOption.orient == 'horizontal') { |
| // 水平布局,计算总宽度,别忘减去最后一个的itemGap |
| totalWidth = (itemSize + itemGap) * iconLength - itemGap; |
| totalHeight = itemSize; |
| } |
| else { |
| // 垂直布局,计算总高度 |
| totalHeight = (itemSize + itemGap) * iconLength - itemGap; |
| totalWidth = itemSize; |
| } |
| |
| var x; |
| var zrWidth = this.zr.getWidth(); |
| switch (toolboxOption.x) { |
| case 'center' : |
| x = Math.floor((zrWidth - totalWidth) / 2); |
| break; |
| case 'left' : |
| x = toolboxOption.padding[3] + toolboxOption.borderWidth; |
| break; |
| case 'right' : |
| x = zrWidth |
| - totalWidth |
| - toolboxOption.padding[1] |
| - toolboxOption.borderWidth; |
| break; |
| default : |
| x = toolboxOption.x - 0; |
| x = isNaN(x) ? 0 : x; |
| break; |
| } |
| |
| var y; |
| var zrHeight = this.zr.getHeight(); |
| switch (toolboxOption.y) { |
| case 'top' : |
| y = toolboxOption.padding[0] + toolboxOption.borderWidth; |
| break; |
| case 'bottom' : |
| y = zrHeight |
| - totalHeight |
| - toolboxOption.padding[2] |
| - toolboxOption.borderWidth; |
| break; |
| case 'center' : |
| y = Math.floor((zrHeight - totalHeight) / 2); |
| break; |
| default : |
| y = toolboxOption.y - 0; |
| y = isNaN(y) ? 0 : y; |
| break; |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : totalWidth, |
| height : totalHeight |
| }; |
| }, |
| |
| __onmousemove : function (param) { |
| if (this._marking) { |
| this._markShape.style.xEnd = zrEvent.getX(param.event); |
| this._markShape.style.yEnd = zrEvent.getY(param.event); |
| this.zr.addHoverShape(this._markShape); |
| } |
| if (this._zooming) { |
| this._zoomShape.style.width = |
| zrEvent.getX(param.event) - this._zoomShape.style.x; |
| this._zoomShape.style.height = |
| zrEvent.getY(param.event) - this._zoomShape.style.y; |
| this.zr.addHoverShape(this._zoomShape); |
| this.dom.style.cursor = 'crosshair'; |
| } |
| if (this._zoomStart |
| && (this.dom.style.cursor != 'pointer' && this.dom.style.cursor != 'move') |
| ) { |
| this.dom.style.cursor = 'crosshair'; |
| } |
| }, |
| |
| __onmousedown : function (param) { |
| if (param.target) { |
| return; |
| } |
| this._zooming = true; |
| var x = zrEvent.getX(param.event); |
| var y = zrEvent.getY(param.event); |
| var zoomOption = this.option.dataZoom || {}; |
| this._zoomShape = new RectangleShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| x : x, |
| y : y, |
| width : 1, |
| height : 1, |
| brushType: 'both' |
| }, |
| highlightStyle : { |
| lineWidth : 2, |
| color: zoomOption.fillerColor |
| || ecConfig.dataZoom.fillerColor, |
| strokeColor : zoomOption.handleColor |
| || ecConfig.dataZoom.handleColor, |
| brushType: 'both' |
| } |
| }); |
| this.zr.addHoverShape(this._zoomShape); |
| return true; // 阻塞全局事件 |
| }, |
| |
| __onmouseup : function (/*param*/) { |
| if (!this._zoomShape |
| || Math.abs(this._zoomShape.style.width) < 10 |
| || Math.abs(this._zoomShape.style.height) < 10 |
| ) { |
| this._zooming = false; |
| return true; |
| } |
| if (this._zooming && this.component.dataZoom) { |
| this._zooming = false; |
| |
| var zoom = this.component.dataZoom.rectZoom(this._zoomShape.style); |
| if (zoom) { |
| this._zoomQueue.push({ |
| start : zoom.start, |
| end : zoom.end, |
| start2 : zoom.start2, |
| end2 : zoom.end2 |
| }); |
| this._iconEnable(this._iconShapeMap['dataZoomReset']); |
| this.zr.refresh(); |
| } |
| } |
| return true; // 阻塞全局事件 |
| }, |
| |
| __onclick : function (param) { |
| if (param.target) { |
| return; |
| } |
| if (this._marking) { |
| this._marking = false; |
| this._markShapeList.push(this._markShape); |
| this._iconEnable(this._iconShapeMap['markUndo']); |
| this._iconEnable(this._iconShapeMap['markClear']); |
| this.zr.addShape(this._markShape); |
| this.zr.refresh(); |
| } |
| else if (this._markStart) { |
| this._marking = true; |
| var x = zrEvent.getX(param.event); |
| var y = zrEvent.getY(param.event); |
| this._markShape = new LineShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| xStart : x, |
| yStart : y, |
| xEnd : x, |
| yEnd : y, |
| lineWidth : this.query( |
| this.option, |
| 'toolbox.feature.mark.lineStyle.width' |
| ), |
| strokeColor : this.query( |
| this.option, |
| 'toolbox.feature.mark.lineStyle.color' |
| ), |
| lineType : this.query( |
| this.option, |
| 'toolbox.feature.mark.lineStyle.type' |
| ) |
| } |
| }); |
| this.zr.addHoverShape(this._markShape); |
| } |
| }, |
| |
| __onMark : function (param) { |
| var target = param.target; |
| if (this._marking || this._markStart) { |
| // 取消 |
| this._resetMark(); |
| this.zr.refresh(); |
| } |
| else { |
| // 启用Mark |
| this._resetZoom(); // mark与dataZoom互斥 |
| |
| this.zr.modShape(target.id, {style: {strokeColor: this._enableColor}}); |
| this.zr.refresh(); |
| this._markStart = true; |
| var self = this; |
| setTimeout(function (){ |
| self.zr |
| && self.zr.on(zrConfig.EVENT.CLICK, self._onclick) |
| && self.zr.on(zrConfig.EVENT.MOUSEMOVE, self._onmousemove); |
| }, 10); |
| } |
| return true; // 阻塞全局事件 |
| }, |
| |
| __onMarkUndo : function () { |
| if (this._marking) { |
| this._marking = false; |
| } else { |
| var len = this._markShapeList.length; |
| if (len >= 1) { |
| var target = this._markShapeList[len - 1]; |
| this.zr.delShape(target.id); |
| this.zr.refresh(); |
| this._markShapeList.pop(); |
| if (len == 1) { |
| this._iconDisable(this._iconShapeMap['markUndo']); |
| this._iconDisable(this._iconShapeMap['markClear']); |
| } |
| } |
| } |
| return true; |
| }, |
| |
| __onMarkClear : function () { |
| if (this._marking) { |
| this._marking = false; |
| } |
| var len = this._markShapeList.length; |
| if (len > 0) { |
| while(len--) { |
| this.zr.delShape(this._markShapeList.pop().id); |
| } |
| this._iconDisable(this._iconShapeMap['markUndo']); |
| this._iconDisable(this._iconShapeMap['markClear']); |
| this.zr.refresh(); |
| } |
| return true; |
| }, |
| |
| __onDataZoom : function (param) { |
| var target = param.target; |
| if (this._zooming || this._zoomStart) { |
| // 取消 |
| this._resetZoom(); |
| this.zr.refresh(); |
| this.dom.style.cursor = 'default'; |
| } |
| else { |
| // 启用Zoom |
| this._resetMark(); // mark与dataZoom互斥 |
| |
| this.zr.modShape(target.id, {style: {strokeColor: this._enableColor}}); |
| this.zr.refresh(); |
| this._zoomStart = true; |
| var self = this; |
| setTimeout(function (){ |
| self.zr |
| && self.zr.on(zrConfig.EVENT.MOUSEDOWN, self._onmousedown) |
| && self.zr.on(zrConfig.EVENT.MOUSEUP, self._onmouseup) |
| && self.zr.on(zrConfig.EVENT.MOUSEMOVE, self._onmousemove); |
| }, 10); |
| |
| this.dom.style.cursor = 'crosshair'; |
| } |
| return true; // 阻塞全局事件 |
| }, |
| |
| __onDataZoomReset : function () { |
| if (this._zooming) { |
| this._zooming = false; |
| } |
| this._zoomQueue.pop(); |
| //console.log(this._zoomQueue) |
| if (this._zoomQueue.length > 0) { |
| this.component.dataZoom.absoluteZoom( |
| this._zoomQueue[this._zoomQueue.length - 1] |
| ); |
| } |
| else { |
| this.component.dataZoom.rectZoom(); |
| this._iconDisable(this._iconShapeMap['dataZoomReset']); |
| this.zr.refresh(); |
| } |
| |
| return true; |
| }, |
| |
| _resetMark : function () { |
| this._marking = false; |
| if (this._markStart) { |
| this._markStart = false; |
| if (this._iconShapeMap['mark']) { |
| // 还原图标为未生效状态 |
| this.zr.modShape( |
| this._iconShapeMap['mark'].id, |
| { |
| style: { |
| strokeColor: this._iconShapeMap['mark'] |
| .highlightStyle |
| .strokeColor |
| } |
| } |
| ); |
| } |
| |
| this.zr.un(zrConfig.EVENT.CLICK, this._onclick); |
| this.zr.un(zrConfig.EVENT.MOUSEMOVE, this._onmousemove); |
| } |
| }, |
| |
| _resetZoom : function () { |
| this._zooming = false; |
| if (this._zoomStart) { |
| this._zoomStart = false; |
| if (this._iconShapeMap['dataZoom']) { |
| // 还原图标为未生效状态 |
| this.zr.modShape( |
| this._iconShapeMap['dataZoom'].id, |
| { |
| style: { |
| strokeColor: this._iconShapeMap['dataZoom'] |
| .highlightStyle |
| .strokeColor |
| } |
| } |
| ); |
| } |
| |
| this.zr.un(zrConfig.EVENT.MOUSEDOWN, this._onmousedown); |
| this.zr.un(zrConfig.EVENT.MOUSEUP, this._onmouseup); |
| this.zr.un(zrConfig.EVENT.MOUSEMOVE, this._onmousemove); |
| } |
| }, |
| |
| _iconDisable : function (target) { |
| if (target.type != 'image') { |
| this.zr.modShape(target.id, { |
| hoverable : false, |
| clickable : false, |
| style : { |
| strokeColor : this._disableColor |
| } |
| }); |
| } |
| else { |
| this.zr.modShape(target.id, { |
| hoverable : false, |
| clickable : false, |
| style : { |
| opacity : 0.3 |
| } |
| }); |
| } |
| }, |
| |
| _iconEnable : function (target) { |
| if (target.type != 'image') { |
| this.zr.modShape(target.id, { |
| hoverable : true, |
| clickable : true, |
| style : { |
| strokeColor : target.highlightStyle.strokeColor |
| } |
| }); |
| } |
| else { |
| this.zr.modShape(target.id, { |
| hoverable : true, |
| clickable : true, |
| style : { |
| opacity : 0.8 |
| } |
| }); |
| } |
| }, |
| |
| __onDataView : function () { |
| this._dataView.show(this.option); |
| return true; |
| }, |
| |
| __onRestore : function (){ |
| this._resetMark(); |
| this._resetZoom(); |
| this.messageCenter.dispatch(ecConfig.EVENT.RESTORE, null, null, this.myChart); |
| return true; |
| }, |
| |
| __onSaveAsImage : function () { |
| var saveOption = this.option.toolbox.feature.saveAsImage; |
| var imgType = saveOption.type || 'png'; |
| if (imgType != 'png' && imgType != 'jpeg') { |
| imgType = 'png'; |
| } |
| |
| var image; |
| if (!this.myChart.isConnected()) { |
| image = this.zr.toDataURL( |
| 'image/' + imgType, |
| this.option.backgroundColor |
| && this.option.backgroundColor.replace(' ','') == 'rgba(0,0,0,0)' |
| ? '#fff' : this.option.backgroundColor |
| ); |
| } |
| else { |
| image = this.myChart.getConnectedDataURL(imgType); |
| } |
| |
| var downloadDiv = document.createElement('div'); |
| downloadDiv.id = '__echarts_download_wrap__'; |
| downloadDiv.style.cssText = 'position:fixed;' |
| + 'z-index:99999;' |
| + 'display:block;' |
| + 'top:0;left:0;' |
| + 'background-color:rgba(33,33,33,0.5);' |
| + 'text-align:center;' |
| + 'width:100%;' |
| + 'height:100%;' |
| + 'line-height:' |
| + document.documentElement.clientHeight + 'px;'; |
| |
| var downloadLink = document.createElement('a'); |
| //downloadLink.onclick = _saveImageForIE; |
| downloadLink.href = image; |
| downloadLink.setAttribute( |
| 'download', |
| (saveOption.name |
| ? saveOption.name |
| : (this.option.title && (this.option.title.text || this.option.title.subtext)) |
| ? (this.option.title.text || this.option.title.subtext) |
| : 'ECharts') |
| + '.' + imgType |
| ); |
| downloadLink.innerHTML = '<img style="vertical-align:middle" src="' + image |
| + '" title="' |
| + (!!(window.attachEvent |
| && navigator.userAgent.indexOf('Opera') === -1) |
| ? '右键->图片另存为' |
| : (saveOption.lang ? saveOption.lang[0] : '点击保存')) |
| + '"/>'; |
| |
| downloadDiv.appendChild(downloadLink); |
| document.body.appendChild(downloadDiv); |
| downloadLink = null; |
| downloadDiv = null; |
| |
| setTimeout(function (){ |
| var _d = document.getElementById('__echarts_download_wrap__'); |
| if (_d) { |
| _d.onclick = function () { |
| var d = document.getElementById( |
| '__echarts_download_wrap__' |
| ); |
| d.onclick = null; |
| d.innerHTML = ''; |
| document.body.removeChild(d); |
| d = null; |
| }; |
| _d = null; |
| } |
| }, 500); |
| |
| /* |
| function _saveImageForIE() { |
| window.win = window.open(image); |
| win.document.execCommand("SaveAs"); |
| win.close() |
| } |
| */ |
| return; |
| }, |
| |
| __onMagicType : function (param) { |
| this._resetMark(); |
| var itemName = param.target._name; |
| if (!this._magicType[itemName]) { |
| // 启用 |
| this._magicType[itemName] = true; |
| // 折柱互斥 |
| if (itemName == ecConfig.CHART_TYPE_LINE) { |
| this._magicType[ecConfig.CHART_TYPE_BAR] = false; |
| } |
| else if (itemName == ecConfig.CHART_TYPE_BAR) { |
| this._magicType[ecConfig.CHART_TYPE_LINE] = false; |
| } |
| // 堆积平铺互斥 |
| if (itemName == _MAGICTYPE_STACK) { |
| this._magicType[_MAGICTYPE_TILED] = false; |
| } |
| else if (itemName == _MAGICTYPE_TILED) { |
| this._magicType[_MAGICTYPE_STACK] = false; |
| } |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.MAGIC_TYPE_CHANGED, |
| param.event, |
| {magicType : this._magicType}, |
| this.myChart |
| ); |
| } |
| |
| return true; |
| }, |
| |
| setMagicType : function (magicType) { |
| this._resetMark(); |
| this._magicType = magicType; |
| |
| !this._isSilence && this.messageCenter.dispatch( |
| ecConfig.EVENT.MAGIC_TYPE_CHANGED, |
| null, |
| {magicType : this._magicType}, |
| this.myChart |
| ); |
| }, |
| |
| // 用户自定义扩展toolbox方法 |
| __onCustomHandler : function (param) { |
| var target = param.target.style.iconType; |
| var featureHandler = this.option.toolbox.feature[target].onclick; |
| if (typeof featureHandler === 'function') { |
| featureHandler(this.option); |
| } |
| }, |
| |
| // 重置备份还原状态等 |
| reset : function (newOption, isRestore) { |
| isRestore && this.clear(); |
| |
| if (this.query(newOption, 'toolbox.show') |
| && this.query(newOption, 'toolbox.feature.magicType.show') |
| ) { |
| var magicType = newOption.toolbox.feature.magicType.type; |
| var len = magicType.length; |
| this._magicMap = {}; // 标识可控类型 |
| while (len--) { |
| this._magicMap[magicType[len]] = true; |
| } |
| |
| len = newOption.series.length; |
| var oriType; // 备份还原可控类型 |
| var axis; |
| while (len--) { |
| oriType = newOption.series[len].type; |
| if (this._magicMap[oriType]) { |
| axis = newOption.xAxis instanceof Array |
| ? newOption.xAxis[newOption.series[len].xAxisIndex || 0] |
| : newOption.xAxis; |
| if (axis && (axis.type || 'category') == 'category') { |
| axis.__boundaryGap = typeof axis.boundaryGap != 'undefined' |
| ? axis.boundaryGap : true; |
| } |
| axis = newOption.yAxis instanceof Array |
| ? newOption.yAxis[newOption.series[len].yAxisIndex || 0] |
| : newOption.yAxis; |
| if (axis && axis.type == 'category') { |
| axis.__boundaryGap = typeof axis.boundaryGap != 'undefined' |
| ? axis.boundaryGap : true; |
| } |
| newOption.series[len].__type = oriType; |
| // 避免不同类型图表类型的样式污染 |
| newOption.series[len].__itemStyle = zrUtil.clone( |
| newOption.series[len].itemStyle || {} |
| ); |
| } |
| |
| if (this._magicMap[_MAGICTYPE_STACK] || this._magicMap[_MAGICTYPE_TILED]) { |
| newOption.series[len].__stack = newOption.series[len].stack; |
| } |
| } |
| } |
| |
| this._magicType = isRestore ? {} : (this._magicType || {}); |
| for (var itemName in this._magicType) { |
| if (this._magicType[itemName]) { |
| this.option = newOption; |
| this.getMagicOption(); |
| break; |
| } |
| } |
| |
| // 框选缩放 |
| var zoomOption = newOption.dataZoom; |
| if (zoomOption && zoomOption.show) { |
| var start = typeof zoomOption.start != 'undefined' |
| && zoomOption.start >= 0 |
| && zoomOption.start <= 100 |
| ? zoomOption.start : 0; |
| var end = typeof zoomOption.end != 'undefined' |
| && zoomOption.end >= 0 |
| && zoomOption.end <= 100 |
| ? zoomOption.end : 100; |
| if (start > end) { |
| // 大小颠倒自动翻转 |
| start = start + end; |
| end = start - end; |
| start = start - end; |
| } |
| this._zoomQueue = [{ |
| start : start, |
| end : end, |
| start2 : 0, |
| end2 : 100 |
| }]; |
| } |
| else { |
| this._zoomQueue = []; |
| } |
| }, |
| |
| getMagicOption : function (){ |
| var axis; |
| if (this._magicType[ecConfig.CHART_TYPE_LINE] |
| || this._magicType[ecConfig.CHART_TYPE_BAR] |
| ) { |
| // 图表类型有切换 |
| var boundaryGap = this._magicType[ecConfig.CHART_TYPE_LINE] ? false : true; |
| for (var i = 0, l = this.option.series.length; i < l; i++) { |
| if (this._magicMap[this.option.series[i].type]) { |
| this.option.series[i].type = this._magicType[ecConfig.CHART_TYPE_LINE] |
| ? ecConfig.CHART_TYPE_LINE |
| : ecConfig.CHART_TYPE_BAR; |
| // 避免不同类型图表类型的样式污染 |
| this.option.series[i].itemStyle = zrUtil.clone( |
| this.option.series[i].__itemStyle |
| ); |
| |
| axis = this.option.xAxis instanceof Array |
| ? this.option.xAxis[this.option.series[i].xAxisIndex || 0] |
| : this.option.xAxis; |
| if (axis && (axis.type || 'category') == 'category') { |
| axis.boundaryGap = boundaryGap ? true : axis.__boundaryGap; |
| } |
| axis = this.option.yAxis instanceof Array |
| ? this.option.yAxis[this.option.series[i].yAxisIndex || 0] |
| : this.option.yAxis; |
| if (axis && axis.type == 'category') { |
| axis.boundaryGap = boundaryGap ? true : axis.__boundaryGap; |
| } |
| } |
| } |
| } |
| |
| if (this._magicType[_MAGICTYPE_STACK] || this._magicType[_MAGICTYPE_TILED]) { |
| // 有堆积平铺切换 |
| for (var i = 0, l = this.option.series.length; i < l; i++) { |
| if (this._magicType[_MAGICTYPE_STACK]) { |
| // 启用堆积 |
| this.option.series[i].stack = '_ECHARTS_STACK_KENER_2014_'; |
| } |
| else if (this._magicType[_MAGICTYPE_TILED]) { |
| // 启用平铺 |
| this.option.series[i].stack = null; |
| } |
| } |
| } |
| |
| return this.option; |
| }, |
| |
| silence : function (s) { |
| this._isSilence = s; |
| }, |
| |
| resize : function () { |
| this._resetMark(); |
| this.clear(); |
| if (this.option && this.option.toolbox && this.option.toolbox.show) { |
| this._buildShape(); |
| } |
| if (this._dataView) { |
| this._dataView.resize(); |
| } |
| }, |
| |
| hideDataView : function () { |
| if (this._dataView) { |
| this._dataView.hide(); |
| } |
| }, |
| |
| clear : function(notMark) { |
| if (this.zr) { |
| this.zr.delShape(this.shapeList); |
| this.shapeList = []; |
| |
| if (!notMark) { |
| this.zr.delShape(this._markShapeList); |
| this._markShapeList = []; |
| } |
| } |
| }, |
| |
| /** |
| * 释放后实例不可用 |
| */ |
| dispose : function () { |
| if (this._dataView) { |
| this._dataView.dispose(); |
| this._dataView = null; |
| } |
| this.clear(); |
| this.shapeList = null; |
| this._markShapeList = null; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this._resetMark(); |
| this._resetZoom(); |
| |
| newOption.toolbox = this.reformOption(newOption.toolbox); |
| // 补全padding属性 |
| newOption.toolbox.padding = this.reformCssArray( |
| newOption.toolbox.padding |
| ); |
| this.option = newOption; |
| |
| this.clear(true); |
| |
| if (newOption.toolbox.show) { |
| this._buildShape(); |
| } |
| |
| this.hideDataView(); |
| } |
| } |
| }; |
| |
| zrUtil.inherits(Toolbox, Base); |
| |
| require('../component').define('toolbox', Toolbox); |
| |
| return Toolbox; |
| }); |
| |
| /** |
| * echarts组件:图表标题 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/title',['require','./base','zrender/shape/Text','zrender/shape/Rectangle','../config','zrender/tool/util','zrender/tool/area','zrender/tool/color','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrArea = require('zrender/tool/area'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表参数 |
| */ |
| function Title(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.refresh(option); |
| } |
| |
| Title.prototype = { |
| type : ecConfig.COMPONENT_TYPE_TITLE, |
| _buildShape : function () { |
| // 标题元素组的位置参数,通过计算所得x, y, width, height |
| this._itemGroupLocation = this._getItemGroupLocation(); |
| |
| this._buildBackground(); |
| this._buildItem(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| }, |
| |
| /** |
| * 构建所有标题元素 |
| */ |
| _buildItem : function () { |
| var text = this.titleOption.text; |
| var link = this.titleOption.link; |
| var target = this.titleOption.target; |
| var subtext = this.titleOption.subtext; |
| var sublink = this.titleOption.sublink; |
| var subtarget = this.titleOption.subtarget; |
| var font = this.getFont(this.titleOption.textStyle); |
| var subfont = this.getFont(this.titleOption.subtextStyle); |
| |
| var x = this._itemGroupLocation.x; |
| var y = this._itemGroupLocation.y; |
| var width = this._itemGroupLocation.width; |
| var height = this._itemGroupLocation.height; |
| |
| var textShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| y : y, |
| color : this.titleOption.textStyle.color, |
| text: text, |
| textFont: font, |
| textBaseline: 'top' |
| }, |
| highlightStyle: { |
| color : zrColor.lift(this.titleOption.textStyle.color, 1), |
| brushType: 'fill' |
| }, |
| hoverable: false |
| }; |
| if (link) { |
| textShape.hoverable = true; |
| textShape.clickable = true; |
| textShape.onclick = function (){ |
| if (!target || target != 'self') { |
| window.open(link); |
| } |
| else { |
| window.location = link; |
| } |
| }; |
| } |
| |
| var subtextShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| y : y + height, |
| color : this.titleOption.subtextStyle.color, |
| text: subtext, |
| textFont: subfont, |
| textBaseline: 'bottom' |
| }, |
| highlightStyle: { |
| color : zrColor.lift(this.titleOption.subtextStyle.color, 1), |
| brushType: 'fill' |
| }, |
| hoverable: false |
| }; |
| if (sublink) { |
| subtextShape.hoverable = true; |
| subtextShape.clickable = true; |
| subtextShape.onclick = function (){ |
| if (!subtarget || subtarget != 'self') { |
| window.open(sublink); |
| } |
| else { |
| window.location = sublink; |
| } |
| }; |
| } |
| |
| switch (this.titleOption.x) { |
| case 'center' : |
| textShape.style.x = subtextShape.style.x = x + width / 2; |
| textShape.style.textAlign = subtextShape.style.textAlign |
| = 'center'; |
| break; |
| case 'left' : |
| textShape.style.x = subtextShape.style.x = x; |
| textShape.style.textAlign = subtextShape.style.textAlign |
| = 'left'; |
| break; |
| case 'right' : |
| textShape.style.x = subtextShape.style.x = x + width; |
| textShape.style.textAlign = subtextShape.style.textAlign |
| = 'right'; |
| break; |
| default : |
| x = this.titleOption.x - 0; |
| x = isNaN(x) ? 0 : x; |
| textShape.style.x = subtextShape.style.x = x; |
| break; |
| } |
| |
| if (this.titleOption.textAlign) { |
| textShape.style.textAlign = subtextShape.style.textAlign |
| = this.titleOption.textAlign; |
| } |
| |
| this.shapeList.push(new TextShape(textShape)); |
| subtext !== '' && this.shapeList.push(new TextShape(subtextShape)); |
| }, |
| |
| _buildBackground : function () { |
| var pTop = this.titleOption.padding[0]; |
| var pRight = this.titleOption.padding[1]; |
| var pBottom = this.titleOption.padding[2]; |
| var pLeft = this.titleOption.padding[3]; |
| |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : this._itemGroupLocation.x - pLeft, |
| y : this._itemGroupLocation.y - pTop, |
| width : this._itemGroupLocation.width + pLeft + pRight, |
| height : this._itemGroupLocation.height + pTop + pBottom, |
| brushType : this.titleOption.borderWidth === 0 |
| ? 'fill' : 'both', |
| color : this.titleOption.backgroundColor, |
| strokeColor : this.titleOption.borderColor, |
| lineWidth : this.titleOption.borderWidth |
| } |
| })); |
| }, |
| |
| /** |
| * 根据选项计算标题实体的位置坐标 |
| */ |
| _getItemGroupLocation : function () { |
| var text = this.titleOption.text; |
| var subtext = this.titleOption.subtext; |
| var font = this.getFont(this.titleOption.textStyle); |
| var subfont = this.getFont(this.titleOption.subtextStyle); |
| |
| var totalWidth = Math.max( |
| zrArea.getTextWidth(text, font), |
| zrArea.getTextWidth(subtext, subfont) |
| ); |
| var totalHeight = zrArea.getTextHeight(text, font) |
| + (subtext === '' |
| ? 0 |
| : (this.titleOption.itemGap |
| + zrArea.getTextHeight(subtext, subfont)) |
| ); |
| |
| var x; |
| var zrWidth = this.zr.getWidth(); |
| switch (this.titleOption.x) { |
| case 'center' : |
| x = Math.floor((zrWidth - totalWidth) / 2); |
| break; |
| case 'left' : |
| x = this.titleOption.padding[3] + this.titleOption.borderWidth; |
| break; |
| case 'right' : |
| x = zrWidth |
| - totalWidth |
| - this.titleOption.padding[1] |
| - this.titleOption.borderWidth; |
| break; |
| default : |
| x = this.titleOption.x - 0; |
| x = isNaN(x) ? 0 : x; |
| break; |
| } |
| |
| var y; |
| var zrHeight = this.zr.getHeight(); |
| switch (this.titleOption.y) { |
| case 'top' : |
| y = this.titleOption.padding[0] + this.titleOption.borderWidth; |
| break; |
| case 'bottom' : |
| y = zrHeight |
| - totalHeight |
| - this.titleOption.padding[2] |
| - this.titleOption.borderWidth; |
| break; |
| case 'center' : |
| y = Math.floor((zrHeight - totalHeight) / 2); |
| break; |
| default : |
| y = this.titleOption.y - 0; |
| y = isNaN(y) ? 0 : y; |
| break; |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : totalWidth, |
| height : totalHeight |
| }; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| |
| this.option.title = this.reformOption(this.option.title); |
| // 补全padding属性 |
| this.option.title.padding = this.reformCssArray( |
| this.option.title.padding |
| ); |
| |
| this.titleOption = this.option.title; |
| this.titleOption.textStyle = zrUtil.merge( |
| this.titleOption.textStyle, |
| this.ecTheme.textStyle |
| ); |
| this.titleOption.subtextStyle = zrUtil.merge( |
| this.titleOption.subtextStyle, |
| this.ecTheme.textStyle |
| ); |
| } |
| |
| this.clear(); |
| this._buildShape(); |
| } |
| }; |
| |
| zrUtil.inherits(Title, Base); |
| |
| require('../component').define('title', Title); |
| |
| return Title; |
| }); |
| |
| |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:十字准星 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'cross', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| rect : {Object}, // 必须,对角框 |
| x : {number}, // 必须,横坐标 |
| y : {number}, // 必须,纵坐标 |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| */ |
| define('echarts/util/shape/Cross',['require','zrender/shape/Base','zrender/shape/Line','zrender/tool/util','./normalIsCover'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var LineShape = require('zrender/shape/Line'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function Cross(options) { |
| Base.call(this, options); |
| } |
| |
| Cross.prototype = { |
| type : 'cross', |
| |
| /** |
| * 创建矩形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| var rect = style.rect; |
| style.xStart = rect.x; |
| style.xEnd = rect.x + rect.width; |
| style.yStart = style.yEnd = style.y; |
| LineShape.prototype.buildPath(ctx, style); |
| style.xStart = style.xEnd = style.x; |
| style.yStart = rect.y; |
| style.yEnd = rect.y + rect.height; |
| LineShape.prototype.buildPath(ctx, style); |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function (style) { |
| return style.rect; |
| }, |
| |
| isCover : require('./normalIsCover') |
| }; |
| |
| zrUtil.inherits(Cross, Base); |
| |
| return Cross; |
| }); |
| |
| /** |
| * echarts组件:提示框 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/tooltip',['require','./base','../util/shape/Cross','zrender/shape/Line','zrender/shape/Rectangle','../config','../util/ecData','zrender/config','zrender/tool/event','zrender/tool/area','zrender/tool/color','zrender/tool/util','zrender/shape/Base','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var CrossShape = require('../util/shape/Cross'); |
| var LineShape = require('zrender/shape/Line'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var rectangleInstance = new RectangleShape({}); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrConfig = require('zrender/config'); |
| var zrEvent = require('zrender/tool/event'); |
| var zrArea = require('zrender/tool/area'); |
| var zrColor = require('zrender/tool/color'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrShapeBase = require('zrender/shape/Base'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 提示框参数 |
| * @param {HtmlElement} dom 目标对象 |
| * @param {ECharts} myChart 当前图表实例 |
| */ |
| function Tooltip(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.dom = myChart.dom; |
| |
| var self = this; |
| self._onmousemove = function (param) { |
| return self.__onmousemove(param); |
| }; |
| self._onglobalout = function (param) { |
| return self.__onglobalout(param); |
| }; |
| |
| this.zr.on(zrConfig.EVENT.MOUSEMOVE, self._onmousemove); |
| this.zr.on(zrConfig.EVENT.GLOBALOUT, self._onglobalout); |
| |
| self._hide = function (param) { |
| return self.__hide(param); |
| }; |
| self._tryShow = function(param) { |
| return self.__tryShow(param); |
| }; |
| self._refixed = function(param) { |
| return self.__refixed(param); |
| }; |
| |
| self._setContent = function(ticket, res) { |
| return self.__setContent(ticket, res); |
| }; |
| |
| this._tDom = this._tDom || document.createElement('div'); |
| // 避免拖拽时页面选中的尴尬 |
| this._tDom.onselectstart = function() { |
| return false; |
| }; |
| this._tDom.style.position = 'absolute'; // 不是多余的,别删! |
| this.hasAppend = false; |
| |
| this._axisLineShape && this.zr.delShape(this._axisLineShape.id); |
| this._axisLineShape = new LineShape({ |
| zlevel: this._zlevelBase, |
| invisible : true, |
| hoverable: false |
| }); |
| this.shapeList.push(this._axisLineShape); |
| this.zr.addShape(this._axisLineShape); |
| |
| this._axisShadowShape && this.zr.delShape(this._axisShadowShape.id); |
| this._axisShadowShape = new LineShape({ |
| zlevel: 1, // grid上,chart下 |
| invisible : true, |
| hoverable: false |
| }); |
| this.shapeList.push(this._axisShadowShape); |
| this.zr.addShape(this._axisShadowShape); |
| |
| this._axisCrossShape && this.zr.delShape(this._axisCrossShape.id); |
| this._axisCrossShape = new CrossShape({ |
| zlevel: this._zlevelBase, |
| invisible : true, |
| hoverable: false |
| }); |
| this.shapeList.push(this._axisCrossShape); |
| this.zr.addShape(this._axisCrossShape); |
| |
| this.showing = false; |
| this.refresh(option); |
| } |
| |
| Tooltip.prototype = { |
| type : ecConfig.COMPONENT_TYPE_TOOLTIP, |
| // 通用样式 |
| _gCssText : 'position:absolute;' |
| + 'display:block;' |
| + 'border-style:solid;' |
| + 'white-space:nowrap;', |
| /** |
| * 根据配置设置dom样式 |
| */ |
| _style : function (opt) { |
| if (!opt) { |
| return ''; |
| } |
| var cssText = []; |
| if (opt.transitionDuration) { |
| var transitionText = 'left ' + opt.transitionDuration + 's,' |
| + 'top ' + opt.transitionDuration + 's'; |
| cssText.push( |
| 'transition:' + transitionText |
| ); |
| cssText.push( |
| '-moz-transition:' + transitionText |
| ); |
| cssText.push( |
| '-webkit-transition:' + transitionText |
| ); |
| cssText.push( |
| '-o-transition:' + transitionText |
| ); |
| } |
| |
| if (opt.backgroundColor) { |
| // for sb ie~ |
| cssText.push( |
| 'background-Color:' + zrColor.toHex( |
| opt.backgroundColor |
| ) |
| ); |
| cssText.push('filter:alpha(opacity=70)'); |
| cssText.push('background-Color:' + opt.backgroundColor); |
| } |
| |
| if (typeof opt.borderWidth != 'undefined') { |
| cssText.push('border-width:' + opt.borderWidth + 'px'); |
| } |
| |
| if (typeof opt.borderColor != 'undefined') { |
| cssText.push('border-color:' + opt.borderColor); |
| } |
| |
| if (typeof opt.borderRadius != 'undefined') { |
| cssText.push( |
| 'border-radius:' + opt.borderRadius + 'px' |
| ); |
| cssText.push( |
| '-moz-border-radius:' + opt.borderRadius + 'px' |
| ); |
| cssText.push( |
| '-webkit-border-radius:' + opt.borderRadius + 'px' |
| ); |
| cssText.push( |
| '-o-border-radius:' + opt.borderRadius + 'px' |
| ); |
| } |
| |
| var textStyle = opt.textStyle; |
| if (textStyle) { |
| textStyle.color && cssText.push('color:' + textStyle.color); |
| textStyle.decoration && cssText.push( |
| 'text-decoration:' + textStyle.decoration |
| ); |
| textStyle.align && cssText.push( |
| 'text-align:' + textStyle.align |
| ); |
| textStyle.fontFamily && cssText.push( |
| 'font-family:' + textStyle.fontFamily |
| ); |
| textStyle.fontSize && cssText.push( |
| 'font-size:' + textStyle.fontSize + 'px' |
| ); |
| textStyle.fontSize && cssText.push( |
| 'line-height:' + Math.round(textStyle.fontSize*3/2) + 'px' |
| ); |
| textStyle.fontStyle && cssText.push( |
| 'font-style:' + textStyle.fontStyle |
| ); |
| textStyle.fontWeight && cssText.push( |
| 'font-weight:' + textStyle.fontWeight |
| ); |
| } |
| |
| |
| var padding = opt.padding; |
| if (typeof padding != 'undefined') { |
| padding = this.reformCssArray(padding); |
| cssText.push( |
| 'padding:' + padding[0] + 'px ' |
| + padding[1] + 'px ' |
| + padding[2] + 'px ' |
| + padding[3] + 'px' |
| ); |
| } |
| |
| cssText = cssText.join(';') + ';'; |
| |
| return cssText; |
| }, |
| |
| __hide : function () { |
| if (this._tDom) { |
| this._tDom.style.display = 'none'; |
| } |
| var needRefresh = false; |
| if (!this._axisLineShape.invisible) { |
| this._axisLineShape.invisible = true; |
| this.zr.modShape(this._axisLineShape.id); |
| needRefresh = true; |
| } |
| if (!this._axisShadowShape.invisible) { |
| this._axisShadowShape.invisible = true; |
| this.zr.modShape(this._axisShadowShape.id); |
| needRefresh = true; |
| } |
| if (!this._axisCrossShape.invisible) { |
| this._axisCrossShape.invisible = true; |
| this.zr.modShape(this._axisCrossShape.id); |
| needRefresh = true; |
| } |
| if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { |
| this.zr.delShape(this._lastTipShape.tipShape); |
| this._lastTipShape = false; |
| this.shapeList.length = 2; |
| } |
| needRefresh && this.zr.refresh(); |
| this.showing = false; |
| }, |
| |
| _show : function (position, x, y, specialCssText) { |
| var domHeight = this._tDom.offsetHeight; |
| var domWidth = this._tDom.offsetWidth; |
| if (position) { |
| if (typeof position == 'function') { |
| position = position([x, y]); |
| } |
| if (position instanceof Array) { |
| x = position[0]; |
| y = position[1]; |
| } |
| } |
| if (x + domWidth > this._zrWidth) { |
| // 太靠右 |
| //x = this._zrWidth - domWidth; |
| x -= (domWidth + 40); |
| } |
| if (y + domHeight > this._zrHeight) { |
| // 太靠下 |
| //y = this._zrHeight - domHeight; |
| y -= (domHeight - 20); |
| } |
| if (y < 20) { |
| y = 0; |
| } |
| this._tDom.style.cssText = this._gCssText |
| + this._defaultCssText |
| + (specialCssText ? specialCssText : '') |
| + 'left:' + x + 'px;top:' + y + 'px;'; |
| |
| if (domHeight < 10 || domWidth < 10) { |
| // this._zrWidth - x < 100 || this._zrHeight - y < 100 |
| setTimeout(this._refixed, 20); |
| } |
| this.showing = true; |
| }, |
| |
| __refixed : function () { |
| if (this._tDom) { |
| var cssText = ''; |
| var domHeight = this._tDom.offsetHeight; |
| var domWidth = this._tDom.offsetWidth; |
| if (this._tDom.offsetLeft + domWidth > this._zrWidth) { |
| cssText += 'left:' + (this._zrWidth - domWidth - 20) + 'px;'; |
| } |
| if (this._tDom.offsetTop + domHeight > this._zrHeight) { |
| cssText += 'top:' + (this._zrHeight - domHeight - 10) + 'px;'; |
| } |
| if (cssText !== '') { |
| this._tDom.style.cssText += cssText; |
| } |
| } |
| }, |
| |
| __tryShow : function () { |
| var needShow; |
| var trigger; |
| if (!this._curTarget) { |
| // 坐标轴事件 |
| this._findPolarTrigger() || this._findAxisTrigger(); |
| } |
| else { |
| // 数据项事件 |
| if (this._curTarget._type == 'island' && this.option.tooltip.show) { |
| this._showItemTrigger(); |
| return; |
| } |
| var serie = ecData.get(this._curTarget, 'series'); |
| var data = ecData.get(this._curTarget, 'data'); |
| needShow = this.deepQuery( |
| [data, serie, this.option], |
| 'tooltip.show' |
| ); |
| if (typeof serie == 'undefined' |
| || typeof data == 'undefined' |
| || needShow === false |
| ) { |
| // 不响应tooltip的数据对象延时隐藏 |
| clearTimeout(this._hidingTicket); |
| clearTimeout(this._showingTicket); |
| this._hidingTicket = setTimeout(this._hide, this._hideDelay); |
| } |
| else { |
| trigger = this.deepQuery( |
| [data, serie, this.option], |
| 'tooltip.trigger' |
| ); |
| |
| trigger == 'axis' |
| ? this._showAxisTrigger( |
| serie.xAxisIndex, serie.yAxisIndex, |
| ecData.get(this._curTarget, 'dataIndex') |
| ) |
| : this._showItemTrigger(); |
| } |
| } |
| }, |
| |
| /** |
| * 直角系 |
| */ |
| _findAxisTrigger : function () { |
| if (!this.component.xAxis || !this.component.yAxis) { |
| this._hidingTicket = setTimeout(this._hide, this._hideDelay); |
| return; |
| } |
| var series = this.option.series; |
| var xAxisIndex; |
| var yAxisIndex; |
| for (var i = 0, l = series.length; i < l; i++) { |
| // 找到第一个axis触发tooltip的系列 |
| if (this.deepQuery( |
| [series[i], this.option], 'tooltip.trigger' |
| ) == 'axis' |
| ) { |
| xAxisIndex = series[i].xAxisIndex || 0; |
| yAxisIndex = series[i].yAxisIndex || 0; |
| if (this.component.xAxis.getAxis(xAxisIndex) |
| && this.component.xAxis.getAxis(xAxisIndex).type |
| == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| // 横轴为类目轴 |
| this._showAxisTrigger(xAxisIndex, yAxisIndex, |
| this._getNearestDataIndex( |
| 'x', this.component.xAxis.getAxis(xAxisIndex) |
| ) |
| ); |
| return; |
| } |
| else if (this.component.yAxis.getAxis(yAxisIndex) |
| && this.component.yAxis.getAxis(yAxisIndex).type |
| == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| // 纵轴为类目轴 |
| this._showAxisTrigger(xAxisIndex, yAxisIndex, |
| this._getNearestDataIndex( |
| 'y', this.component.yAxis.getAxis(yAxisIndex) |
| ) |
| ); |
| return; |
| } |
| else { |
| // 双数值轴 |
| this._showAxisTrigger(xAxisIndex, yAxisIndex, -1); |
| return; |
| } |
| } |
| } |
| if (this.option.tooltip.axisPointer.type == 'cross') { |
| this._showAxisTrigger(-1, -1, -1); |
| } |
| }, |
| |
| /** |
| * 极坐标 |
| */ |
| _findPolarTrigger : function () { |
| if (!this.component.polar) { |
| return false; |
| } |
| var x = zrEvent.getX(this._event); |
| var y = zrEvent.getY(this._event); |
| var polarIndex = this.component.polar.getNearestIndex([x, y]); |
| var valueIndex; |
| if (polarIndex) { |
| valueIndex = polarIndex.valueIndex; |
| polarIndex = polarIndex.polarIndex; |
| } |
| else { |
| polarIndex = -1; |
| } |
| |
| if (polarIndex != -1) { |
| return this._showPolarTrigger(polarIndex, valueIndex); |
| } |
| |
| return false; |
| }, |
| |
| /** |
| * 根据坐标轴事件带的属性获取最近的axisDataIndex |
| */ |
| _getNearestDataIndex : function (direction, categoryAxis) { |
| var dataIndex = -1; |
| var x = zrEvent.getX(this._event); |
| var y = zrEvent.getY(this._event); |
| if (direction == 'x') { |
| // 横轴为类目轴 |
| var left; |
| var right; |
| var xEnd = this.component.grid.getXend(); |
| var curCoord = categoryAxis.getCoordByIndex(dataIndex); |
| while (curCoord < xEnd) { |
| if (curCoord <= x) { |
| left = curCoord; |
| } |
| if (curCoord >= x) { |
| break; |
| } |
| curCoord = categoryAxis.getCoordByIndex(++dataIndex); |
| right = curCoord; |
| } |
| if (x - left < right - x) { |
| dataIndex -= dataIndex !== 0 ? 1 : 0; |
| } |
| else { |
| // 离右边近,看是否为最后一个 |
| if (typeof categoryAxis.getNameByIndex(dataIndex) |
| == 'undefined' |
| ) { |
| dataIndex -= 1; |
| } |
| } |
| return dataIndex; |
| } |
| else { |
| // 纵轴为类目轴 |
| var top; |
| var bottom; |
| var yStart = this.component.grid.getY(); |
| var curCoord = categoryAxis.getCoordByIndex(dataIndex); |
| while (curCoord > yStart) { |
| if (curCoord >= y) { |
| bottom = curCoord; |
| } |
| if (curCoord <= y) { |
| break; |
| } |
| curCoord = categoryAxis.getCoordByIndex(++dataIndex); |
| top = curCoord; |
| } |
| |
| if (y - top > bottom - y) { |
| dataIndex -= dataIndex !== 0 ? 1 : 0; |
| } |
| else { |
| // 离上方边近,看是否为最后一个 |
| if (typeof categoryAxis.getNameByIndex(dataIndex) |
| == 'undefined' |
| ) { |
| dataIndex -= 1; |
| } |
| } |
| return dataIndex; |
| } |
| return -1; |
| }, |
| |
| /** |
| * 直角系 |
| */ |
| _showAxisTrigger : function (xAxisIndex, yAxisIndex, dataIndex) { |
| !this._event.connectTrigger && this.messageCenter.dispatch( |
| ecConfig.EVENT.TOOLTIP_IN_GRID, |
| this._event, |
| null, |
| this.myChart |
| ); |
| if (typeof this.component.xAxis == 'undefined' |
| || typeof this.component.yAxis == 'undefined' |
| || typeof xAxisIndex == 'undefined' |
| || typeof yAxisIndex == 'undefined' |
| // || dataIndex < 0 |
| ) { |
| // 不响应tooltip的数据对象延时隐藏 |
| clearTimeout(this._hidingTicket); |
| clearTimeout(this._showingTicket); |
| this._hidingTicket = setTimeout(this._hide, this._hideDelay); |
| return; |
| } |
| var series = this.option.series; |
| var seriesArray = []; |
| var seriesIndex = []; |
| var categoryAxis; |
| var x; |
| var y; |
| |
| var formatter; |
| var position; |
| var showContent; |
| var specialCssText = ''; |
| if (this.option.tooltip.trigger == 'axis') { |
| if (this.option.tooltip.show === false) { |
| return; |
| } |
| formatter = this.option.tooltip.formatter; |
| position = this.option.tooltip.position; |
| } |
| |
| if (xAxisIndex != -1 |
| && this.component.xAxis.getAxis(xAxisIndex).type |
| == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| // 横轴为类目轴,找到所有用这条横轴并且axis触发的系列数据 |
| categoryAxis = this.component.xAxis.getAxis(xAxisIndex); |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (!this._isSelected(series[i].name)) { |
| continue; |
| } |
| if (series[i].xAxisIndex == xAxisIndex |
| && this.deepQuery([series[i], this.option], 'tooltip.trigger') == 'axis' |
| ) { |
| showContent = this.query(series[i], 'tooltip.showContent') |
| || showContent; |
| formatter = this.query(series[i], 'tooltip.formatter') |
| || formatter; |
| position = this.query(series[i], 'tooltip.position') |
| || position; |
| |
| specialCssText += this._style(this.query(series[i], 'tooltip')); |
| seriesArray.push(series[i]); |
| seriesIndex.push(i); |
| } |
| } |
| // 寻找高亮元素 |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.TOOLTIP_HOVER, |
| this._event, |
| { |
| seriesIndex : seriesIndex, |
| dataIndex : dataIndex |
| }, |
| this.myChart |
| ); |
| y = zrEvent.getY(this._event); |
| x = this.subPixelOptimize( |
| categoryAxis.getCoordByIndex(dataIndex), |
| this._axisLineWidth |
| ); |
| this._styleAxisPointer( |
| seriesArray, |
| x, this.component.grid.getY(), |
| x, this.component.grid.getYend(), |
| categoryAxis.getGap(), x, y |
| ); |
| } |
| else if (yAxisIndex != -1 |
| && this.component.yAxis.getAxis(yAxisIndex).type |
| == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| // 纵轴为类目轴,找到所有用这条纵轴并且axis触发的系列数据 |
| categoryAxis = this.component.yAxis.getAxis(yAxisIndex); |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (!this._isSelected(series[i].name)) { |
| continue; |
| } |
| if (series[i].yAxisIndex == yAxisIndex |
| && this.deepQuery([series[i], this.option], 'tooltip.trigger') == 'axis' |
| ) { |
| showContent = this.query(series[i], 'tooltip.showContent') |
| || showContent; |
| formatter = this.query(series[i], 'tooltip.formatter') |
| || formatter; |
| position = this.query(series[i], 'tooltip.position') |
| || position; |
| specialCssText += this._style(this.query(series[i], 'tooltip')); |
| seriesArray.push(series[i]); |
| seriesIndex.push(i); |
| } |
| } |
| // 寻找高亮元素 |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.TOOLTIP_HOVER, |
| this._event, |
| { |
| seriesIndex : seriesIndex, |
| dataIndex : dataIndex |
| }, |
| this.myChart |
| ); |
| x = zrEvent.getX(this._event); |
| y = this.subPixelOptimize( |
| categoryAxis.getCoordByIndex(dataIndex), |
| this._axisLineWidth |
| ); |
| this._styleAxisPointer( |
| seriesArray, |
| this.component.grid.getX(), y, |
| this.component.grid.getXend(), y, |
| categoryAxis.getGap(), x, y |
| ); |
| } |
| else { |
| // 双数值轴 |
| x = zrEvent.getX(this._event); |
| y = zrEvent.getY(this._event); |
| this._styleAxisPointer( |
| series, |
| this.component.grid.getX(), y, |
| this.component.grid.getXend(), y, |
| 0, x, y |
| ); |
| if (dataIndex >= 0) { |
| this._showItemTrigger(); |
| } |
| else { |
| clearTimeout(this._hidingTicket); |
| clearTimeout(this._showingTicket); |
| this._tDom.style.display = 'none'; |
| } |
| } |
| |
| if (seriesArray.length > 0) { |
| var data; |
| if (typeof formatter == 'function') { |
| var params = []; |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| data = seriesArray[i].data[dataIndex]; |
| data = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| |
| params.push([ |
| seriesArray[i].name || '', |
| categoryAxis.getNameByIndex(dataIndex), |
| data |
| ]); |
| } |
| this._curTicket = 'axis:' + dataIndex; |
| this._tDom.innerHTML = formatter.call( |
| this.myChart, params, this._curTicket, this._setContent |
| ); |
| } |
| else if (typeof formatter == 'string') { |
| this._curTicket = NaN; |
| formatter = formatter.replace('{a}','{a0}') |
| .replace('{b}','{b0}') |
| .replace('{c}','{c0}'); |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| formatter = formatter.replace( |
| '{a' + i + '}', |
| this._encodeHTML(seriesArray[i].name || '') |
| ); |
| formatter = formatter.replace( |
| '{b' + i + '}', |
| this._encodeHTML(categoryAxis.getNameByIndex(dataIndex)) |
| ); |
| data = seriesArray[i].data[dataIndex]; |
| data = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| formatter = formatter.replace( |
| '{c' + i + '}', |
| data instanceof Array |
| ? data : this.numAddCommas(data) |
| ); |
| } |
| this._tDom.innerHTML = formatter; |
| } |
| else { |
| this._curTicket = NaN; |
| formatter = this._encodeHTML( |
| categoryAxis.getNameByIndex(dataIndex) |
| ); |
| |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| formatter += '<br/>' |
| + this._encodeHTML(seriesArray[i].name || '') |
| + ' : '; |
| data = seriesArray[i].data[dataIndex]; |
| data = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| formatter += data instanceof Array |
| ? data : this.numAddCommas(data); |
| } |
| this._tDom.innerHTML = formatter; |
| } |
| |
| if (showContent === false || !this.option.tooltip.showContent) { |
| // 只用tooltip的行为,不显示主体 |
| return; |
| } |
| |
| if (!this.hasAppend) { |
| this._tDom.style.left = this._zrWidth / 2 + 'px'; |
| this._tDom.style.top = this._zrHeight / 2 + 'px'; |
| this.dom.firstChild.appendChild(this._tDom); |
| this.hasAppend = true; |
| } |
| this._show(position, x + 10, y + 10, specialCssText); |
| } |
| }, |
| |
| /** |
| * 极坐标 |
| */ |
| _showPolarTrigger : function (polarIndex, dataIndex) { |
| if (typeof this.component.polar == 'undefined' |
| || typeof polarIndex == 'undefined' |
| || typeof dataIndex == 'undefined' |
| || dataIndex < 0 |
| ) { |
| return false; |
| } |
| var series = this.option.series; |
| var seriesArray = []; |
| |
| var formatter; |
| var position; |
| var showContent; |
| var specialCssText = ''; |
| if (this.option.tooltip.trigger == 'axis') { |
| if (this.option.tooltip.show === false) { |
| return false; |
| } |
| formatter = this.option.tooltip.formatter; |
| position = this.option.tooltip.position; |
| } |
| var indicatorName = this.option.polar[polarIndex].indicator[dataIndex].text; |
| |
| // 找到所有用这个极坐标并且axis触发的系列数据 |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (!this._isSelected(series[i].name)) { |
| continue; |
| } |
| if (series[i].polarIndex == polarIndex |
| && this.deepQuery([series[i], this.option], 'tooltip.trigger') == 'axis' |
| ) { |
| showContent = this.query(series[i], 'tooltip.showContent') |
| || showContent; |
| formatter = this.query(series[i], 'tooltip.formatter') |
| || formatter; |
| position = this.query(series[i], 'tooltip.position') |
| || position; |
| specialCssText += this._style(this.query(series[i], 'tooltip')); |
| seriesArray.push(series[i]); |
| } |
| } |
| if (seriesArray.length > 0) { |
| var polarData; |
| var data; |
| var params = []; |
| |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| polarData = seriesArray[i].data; |
| for (var j = 0, k = polarData.length; j < k; j++) { |
| data = polarData[j]; |
| if (!this._isSelected(data.name)) { |
| continue; |
| } |
| data = typeof data != 'undefined' |
| ? data |
| : {name:'', value: {dataIndex:'-'}}; |
| |
| params.push([ |
| seriesArray[i].name || '', |
| data.name, |
| typeof data.value[dataIndex].value != 'undefined' |
| ? data.value[dataIndex].value : data.value[dataIndex], |
| indicatorName |
| ]); |
| } |
| } |
| if (params.length <= 0) { |
| return; |
| } |
| if (typeof formatter == 'function') { |
| this._curTicket = 'axis:' + dataIndex; |
| this._tDom.innerHTML = formatter.call( |
| this.myChart, params, this._curTicket, this._setContent |
| ); |
| } |
| else if (typeof formatter == 'string') { |
| formatter = formatter.replace('{a}','{a0}') |
| .replace('{b}','{b0}') |
| .replace('{c}','{c0}') |
| .replace('{d}','{d0}'); |
| for (var i = 0, l = params.length; i < l; i++) { |
| formatter = formatter.replace( |
| '{a' + i + '}', |
| this._encodeHTML(params[i][0]) |
| ); |
| formatter = formatter.replace( |
| '{b' + i + '}', |
| this._encodeHTML(params[i][1]) |
| ); |
| formatter = formatter.replace( |
| '{c' + i + '}', |
| this.numAddCommas(params[i][2]) |
| ); |
| formatter = formatter.replace( |
| '{d' + i + '}', |
| this._encodeHTML(params[i][3]) |
| ); |
| } |
| this._tDom.innerHTML = formatter; |
| } |
| else { |
| formatter = this._encodeHTML(params[0][1]) + '<br/>' |
| + this._encodeHTML(params[0][3]) + ' : ' |
| + this.numAddCommas(params[0][2]); |
| for (var i = 1, l = params.length; i < l; i++) { |
| formatter += '<br/>' + this._encodeHTML(params[i][1]) |
| + '<br/>'; |
| formatter += this._encodeHTML(params[i][3]) + ' : ' |
| + this.numAddCommas(params[i][2]); |
| } |
| this._tDom.innerHTML = formatter; |
| } |
| |
| if (showContent === false || !this.option.tooltip.showContent) { |
| // 只用tooltip的行为,不显示主体 |
| return; |
| } |
| |
| if (!this.hasAppend) { |
| this._tDom.style.left = this._zrWidth / 2 + 'px'; |
| this._tDom.style.top = this._zrHeight / 2 + 'px'; |
| this.dom.firstChild.appendChild(this._tDom); |
| this.hasAppend = true; |
| } |
| this._show( |
| position, |
| zrEvent.getX(this._event), |
| zrEvent.getY(this._event), |
| specialCssText |
| ); |
| return true; |
| } |
| }, |
| |
| _showItemTrigger : function () { |
| if (!this._curTarget) { |
| return; |
| } |
| var serie = ecData.get(this._curTarget, 'series'); |
| var data = ecData.get(this._curTarget, 'data'); |
| var name = ecData.get(this._curTarget, 'name'); |
| var value = ecData.get(this._curTarget, 'value'); |
| var special = ecData.get(this._curTarget, 'special'); |
| var special2 = ecData.get(this._curTarget, 'special2'); |
| // 从低优先级往上找到trigger为item的formatter和样式 |
| var formatter; |
| var position; |
| var showContent; |
| var specialCssText = ''; |
| var indicator; |
| var html = ''; |
| if (this._curTarget._type != 'island') { |
| // 全局 |
| if (this.option.tooltip.trigger == 'item') { |
| formatter = this.option.tooltip.formatter; |
| position = this.option.tooltip.position; |
| } |
| // 系列 |
| if (this.query(serie, 'tooltip.trigger') == 'item') { |
| showContent = this.query(serie, 'tooltip.showContent') |
| || showContent; |
| formatter = this.query(serie, 'tooltip.formatter') |
| || formatter; |
| position = this.query(serie, 'tooltip.position') |
| || position; |
| specialCssText += this._style(this.query(serie, 'tooltip')); |
| } |
| // 数据项 |
| showContent = this.query(data, 'tooltip.showContent') |
| || showContent; |
| formatter = this.query(data, 'tooltip.formatter') |
| || formatter; |
| position = this.query(data, 'tooltip.position') |
| || position; |
| specialCssText += this._style(this.query(data, 'tooltip')); |
| } |
| else { |
| showContent = this.deepQuery([data, serie, this.option], 'tooltip.showContent'); |
| formatter = this.deepQuery([data, serie, this.option], 'tooltip.islandFormatter'); |
| position = this.deepQuery([data, serie, this.option], 'tooltip.islandPosition'); |
| } |
| |
| if (typeof formatter == 'function') { |
| this._curTicket = (serie.name || '') |
| + ':' |
| + ecData.get(this._curTarget, 'dataIndex'); |
| this._tDom.innerHTML = formatter.call( |
| this.myChart, |
| [ |
| serie.name || '', |
| name, |
| value, |
| special, |
| special2, |
| data |
| ], |
| this._curTicket, |
| this._setContent |
| ); |
| } |
| else if (typeof formatter == 'string') { |
| this._curTicket = NaN; |
| formatter = formatter.replace('{a}','{a0}') |
| .replace('{b}','{b0}') |
| .replace('{c}','{c0}'); |
| formatter = formatter.replace('{a0}', this._encodeHTML(serie.name || '')) |
| .replace('{b0}', this._encodeHTML(name)) |
| .replace( |
| '{c0}', |
| value instanceof Array ? value : this.numAddCommas(value) |
| ); |
| |
| formatter = formatter.replace('{d}','{d0}') |
| .replace('{d0}', special || ''); |
| formatter = formatter.replace('{e}','{e0}') |
| .replace('{e0}', ecData.get(this._curTarget, 'special2') || ''); |
| |
| this._tDom.innerHTML = formatter; |
| } |
| else { |
| this._curTicket = NaN; |
| if (serie.type == ecConfig.CHART_TYPE_SCATTER) { |
| this._tDom.innerHTML = (typeof serie.name != 'undefined' |
| ? (this._encodeHTML(serie.name) + '<br/>') |
| : '' |
| ) |
| + (name === '' ? '' : (this._encodeHTML(name) + ' : ')) |
| + value |
| + (typeof special == 'undefined' |
| ? '' : (' (' + special + ')') |
| ); |
| } |
| else if (serie.type == ecConfig.CHART_TYPE_RADAR && special) { |
| indicator = special; |
| html += this._encodeHTML(name === '' ? (serie.name || '') : name); |
| html += html === '' ? '' : '<br />'; |
| for (var i = 0 ; i < indicator.length; i ++) { |
| html += this._encodeHTML(indicator[i].text) + ' : ' |
| + this.numAddCommas(value[i]) + '<br />'; |
| } |
| this._tDom.innerHTML = html; |
| } |
| else if (serie.type == ecConfig.CHART_TYPE_CHORD) { |
| if (typeof special2 == 'undefined') { |
| // 外环上 |
| this._tDom.innerHTML = this._encodeHTML(name) + ' (' |
| + this.numAddCommas(value) + ')'; |
| } |
| else { |
| var name1 = this._encodeHTML(name); |
| var name2 = this._encodeHTML(special); |
| // 内部弦上 |
| this._tDom.innerHTML = (typeof serie.name != 'undefined' |
| ? (this._encodeHTML(serie.name) + '<br/>') : '' |
| ) |
| + name1 + ' -> ' + name2 |
| + ' (' + this.numAddCommas(value) + ')' |
| + '<br />' |
| + name2 + ' -> ' + name1 |
| + ' (' + this.numAddCommas(special2) + ')'; |
| } |
| } |
| else { |
| this._tDom.innerHTML = (typeof serie.name != 'undefined' |
| ? (this._encodeHTML(serie.name) + '<br/>') : '' |
| ) |
| + this._encodeHTML(name) + ' : ' |
| + this.numAddCommas(value) + |
| (typeof special == 'undefined' |
| ? '' : (' ('+ this.numAddCommas(special) +')') |
| ); |
| } |
| } |
| |
| if (!this._axisLineShape.invisible |
| || !this._axisShadowShape.invisible |
| ) { |
| this._axisLineShape.invisible = true; |
| this.zr.modShape(this._axisLineShape.id); |
| this._axisShadowShape.invisible = true; |
| this.zr.modShape(this._axisShadowShape.id); |
| this.zr.refresh(); |
| } |
| |
| if (showContent === false || !this.option.tooltip.showContent) { |
| // 只用tooltip的行为,不显示主体 |
| return; |
| } |
| |
| if (!this.hasAppend) { |
| this._tDom.style.left = this._zrWidth / 2 + 'px'; |
| this._tDom.style.top = this._zrHeight / 2 + 'px'; |
| this.dom.firstChild.appendChild(this._tDom); |
| this.hasAppend = true; |
| } |
| |
| this._show( |
| position, |
| zrEvent.getX(this._event) + 20, |
| zrEvent.getY(this._event) - 20, |
| specialCssText |
| ); |
| }, |
| |
| /** |
| * 设置坐标轴指示器样式 |
| */ |
| _styleAxisPointer : function (seriesArray, xStart, yStart, xEnd, yEnd, gap, x, y) { |
| if (seriesArray.length > 0) { |
| var queryTarget; |
| var curType; |
| var axisPointer = this.option.tooltip.axisPointer; |
| var pointType = axisPointer.type; |
| var style = { |
| line : {}, |
| cross : {}, |
| shadow : {} |
| }; |
| for (var pType in style) { |
| style[pType].color = axisPointer[pType + 'Style'].color; |
| style[pType].width = axisPointer[pType + 'Style'].width; |
| style[pType].type = axisPointer[pType + 'Style'].type; |
| } |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| if (this.deepQuery( |
| [seriesArray[i], this.option], 'tooltip.trigger' |
| ) == 'axis' |
| ) { |
| queryTarget = seriesArray[i]; |
| curType = this.query(queryTarget, 'tooltip.axisPointer.type'); |
| pointType = curType || pointType; |
| if (curType) { |
| style[curType].color = this.query( |
| queryTarget, |
| 'tooltip.axisPointer.' + curType + 'Style.color' |
| ) || style[curType].color; |
| style[curType].width = this.query( |
| queryTarget, |
| 'tooltip.axisPointer.' + curType + 'Style.width' |
| ) || style[curType].width; |
| style[curType].type = this.query( |
| queryTarget, |
| 'tooltip.axisPointer.' + curType + 'Style.type' |
| ) || style[curType].type; |
| } |
| } |
| } |
| |
| if (pointType == 'line') { |
| this._axisLineShape.style = { |
| xStart : xStart, |
| yStart : yStart, |
| xEnd : xEnd, |
| yEnd : yEnd, |
| strokeColor : style.line.color, |
| lineWidth : style.line.width, |
| lineType : style.line.type |
| }; |
| this._axisLineShape.invisible = false; |
| this.zr.modShape(this._axisLineShape.id); |
| } |
| else if (pointType == 'cross') { |
| this._axisCrossShape.style = { |
| brushType: 'stroke', |
| rect : this.component.grid.getArea(), |
| x : x, |
| y : y, |
| text : ('( ' |
| + this.component.xAxis.getAxis(0).getValueFromCoord(x) |
| + ' , ' |
| + this.component.yAxis.getAxis(0).getValueFromCoord(y) |
| + ' )' |
| ).replace(' , ', ' ').replace(' , ', ' '), |
| textPosition : 'specific', |
| strokeColor : style.cross.color, |
| lineWidth : style.cross.width, |
| lineType : style.cross.type |
| }; |
| if (this.component.grid.getXend() - x > 100) { // 右侧有空间 |
| this._axisCrossShape.style.textAlign = 'left'; |
| this._axisCrossShape.style.textX = x + 10; |
| } |
| else { |
| this._axisCrossShape.style.textAlign = 'right'; |
| this._axisCrossShape.style.textX = x - 10; |
| } |
| if (y - this.component.grid.getY() > 50) { // 上方有空间 |
| this._axisCrossShape.style.textBaseline = 'bottom'; |
| this._axisCrossShape.style.textY = y - 10; |
| } |
| else { |
| this._axisCrossShape.style.textBaseline = 'top'; |
| this._axisCrossShape.style.textY = y + 10; |
| } |
| this._axisCrossShape.invisible = false; |
| this.zr.modShape(this._axisCrossShape.id); |
| } |
| else if (pointType == 'shadow') { |
| if (typeof style.shadow.width == 'undefined' |
| || style.shadow.width == 'auto' |
| || isNaN(style.shadow.width) |
| ) { |
| style.shadow.width = gap; |
| } |
| if (xStart == xEnd) { |
| // 纵向 |
| if (Math.abs(this.component.grid.getX() - xStart) < 2) { |
| // 最左边 |
| style.shadow.width /= 2; |
| xStart = xEnd = xEnd + style.shadow.width / 2; |
| } |
| else if (Math.abs(this.component.grid.getXend() - xStart) < 2) { |
| // 最右边 |
| style.shadow.width /= 2; |
| xStart = xEnd = xEnd - style.shadow.width / 2; |
| } |
| } |
| else if (yStart == yEnd) { |
| // 横向 |
| if (Math.abs(this.component.grid.getY() - yStart) < 2) { |
| // 最上边 |
| style.shadow.width /= 2; |
| yStart = yEnd = yEnd + style.shadow.width / 2; |
| } |
| else if (Math.abs(this.component.grid.getYend() - yStart) < 2) { |
| // 最右边 |
| style.shadow.width /= 2; |
| yStart = yEnd = yEnd - style.shadow.width / 2; |
| } |
| } |
| this._axisShadowShape.style = { |
| xStart : xStart, |
| yStart : yStart, |
| xEnd : xEnd, |
| yEnd : yEnd, |
| strokeColor : style.shadow.color, |
| lineWidth : style.shadow.width |
| }; |
| this._axisShadowShape.invisible = false; |
| this.zr.modShape(this._axisShadowShape.id); |
| } |
| this.zr.refresh(); |
| } |
| }, |
| |
| __onmousemove : function (param) { |
| clearTimeout(this._hidingTicket); |
| clearTimeout(this._showingTicket); |
| var target = param.target; |
| var mx = zrEvent.getX(param.event); |
| var my = zrEvent.getY(param.event); |
| if (!target) { |
| // 判断是否落到直角系里,axis触发的tooltip |
| this._curTarget = false; |
| this._event = param.event; |
| // this._event._target = this._event.target || this._event.toElement; |
| this._event.zrenderX = mx; |
| this._event.zrenderY = my; |
| if (this._needAxisTrigger |
| && this.component.grid |
| && zrArea.isInside(rectangleInstance, this.component.grid.getArea(), mx, my) |
| ) { |
| this._showingTicket = setTimeout(this._tryShow, this._showDelay); |
| } |
| else if (this._needAxisTrigger |
| && this.component.polar |
| && this.component.polar.isInside([mx, my]) != -1 |
| ) { |
| this._showingTicket = setTimeout(this._tryShow, this._showDelay); |
| } |
| else { |
| !this._event.connectTrigger && this.messageCenter.dispatch( |
| ecConfig.EVENT.TOOLTIP_OUT_GRID, |
| this._event, |
| null, |
| this.myChart |
| ); |
| this._hidingTicket = setTimeout(this._hide, this._hideDelay); |
| } |
| } |
| else { |
| this._curTarget = target; |
| this._event = param.event; |
| // this._event._target = this._event.target || this._event.toElement; |
| this._event.zrenderX = mx; |
| this._event.zrenderY = my; |
| var polarIndex; |
| if (this._needAxisTrigger |
| && this.component.polar |
| && (polarIndex = this.component.polar.isInside([mx, my])) != -1 |
| ) { |
| // 看用这个polar的系列数据是否是axis触发,如果是设置_curTarget为nul |
| var series = this.option.series; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].polarIndex == polarIndex |
| && this.deepQuery( |
| [series[i], this.option], 'tooltip.trigger' |
| ) == 'axis' |
| ) { |
| this._curTarget = null; |
| break; |
| } |
| } |
| |
| } |
| this._showingTicket = setTimeout(this._tryShow, this._showDelay); |
| } |
| }, |
| |
| /** |
| * zrender事件响应:鼠标离开绘图区域 |
| */ |
| __onglobalout : function () { |
| clearTimeout(this._hidingTicket); |
| clearTimeout(this._showingTicket); |
| this._hidingTicket = setTimeout(this._hide, this._hideDelay); |
| }, |
| |
| /** |
| * 异步回调填充内容 |
| */ |
| __setContent : function (ticket, content) { |
| if (!this._tDom) { |
| return; |
| } |
| if (ticket == this._curTicket) { |
| this._tDom.innerHTML = content; |
| } |
| |
| setTimeout(this._refixed, 20); |
| }, |
| |
| ontooltipHover : function (param, tipShape) { |
| if (!this._lastTipShape // 不存在或者存在但dataIndex发生变化才需要重绘 |
| || (this._lastTipShape && this._lastTipShape.dataIndex != param.dataIndex) |
| ) { |
| if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { |
| this.zr.delShape(this._lastTipShape.tipShape); |
| this.shapeList.length = 2; |
| } |
| for (var i = 0, l = tipShape.length; i < l; i++) { |
| tipShape[i].zlevel = this._zlevelBase; |
| tipShape[i].style = zrShapeBase.prototype.getHighlightStyle( |
| tipShape[i].style, |
| tipShape[i].highlightStyle |
| ); |
| tipShape[i].draggable = false; |
| tipShape[i].hoverable = false; |
| tipShape[i].clickable = false; |
| tipShape[i].ondragend = null; |
| tipShape[i].ondragover = null; |
| tipShape[i].ondrop = null; |
| this.shapeList.push(tipShape[i]); |
| this.zr.addShape(tipShape[i]); |
| } |
| this._lastTipShape = { |
| dataIndex : param.dataIndex, |
| tipShape : tipShape |
| }; |
| } |
| }, |
| |
| ondragend : function () { |
| this._hide(); |
| }, |
| |
| /** |
| * 图例选择 |
| */ |
| onlegendSelected : function (param) { |
| this._selectedMap = param.selected; |
| }, |
| |
| _setSelectedMap : function () { |
| if (this.component.legend) { |
| this._selectedMap = zrUtil.clone(this.component.legend.getSelectedMap()); |
| } |
| else { |
| this._selectedMap = {}; |
| } |
| }, |
| |
| _isSelected : function (itemName) { |
| if (typeof this._selectedMap[itemName] != 'undefined') { |
| return this._selectedMap[itemName]; |
| } |
| else { |
| return true; // 没在legend里定义的都为true啊~ |
| } |
| }, |
| |
| /** |
| * 模拟tooltip hover方法 |
| * {object} params 参数 |
| * {seriesIndex: 0, seriesName:'', dataInex:0} line、bar、scatter、k、radar |
| * {seriesIndex: 0, seriesName:'', name:''} map、pie、chord |
| */ |
| showTip : function (params) { |
| if (!params) { |
| return; |
| } |
| |
| var seriesIndex; |
| var series = this.option.series; |
| if (typeof params.seriesIndex != 'undefined') { |
| seriesIndex = params.seriesIndex; |
| } |
| else { |
| var seriesName = params.seriesName; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].name == seriesName) { |
| seriesIndex = i; |
| break; |
| } |
| } |
| } |
| |
| var serie = series[seriesIndex]; |
| if (typeof serie == 'undefined') { |
| return; |
| } |
| var chart = this.myChart.chart[serie.type]; |
| var isAxisTrigger = this.deepQuery( |
| [serie, this.option], 'tooltip.trigger' |
| ) == 'axis'; |
| |
| if (!chart) { |
| return; |
| } |
| |
| if (isAxisTrigger) { |
| // axis trigger |
| var dataIndex = params.dataIndex; |
| switch (chart.type) { |
| case ecConfig.CHART_TYPE_LINE : |
| case ecConfig.CHART_TYPE_BAR : |
| case ecConfig.CHART_TYPE_K : |
| if (typeof this.component.xAxis == 'undefined' |
| || typeof this.component.yAxis == 'undefined' |
| || serie.data.length <= dataIndex |
| ) { |
| return; |
| } |
| var xAxisIndex = serie.xAxisIndex || 0; |
| var yAxisIndex = serie.yAxisIndex || 0; |
| if (this.component.xAxis.getAxis(xAxisIndex).type |
| == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| // 横轴是类目 |
| this._event = { |
| zrenderX : this.component.xAxis.getAxis(xAxisIndex) |
| .getCoordByIndex(dataIndex), |
| zrenderY : this.component.grid.getY() |
| + (this.component.grid.getYend() |
| - this.component.grid.getY() |
| ) / 4 |
| }; |
| } |
| else { |
| // 纵轴是类目 |
| this._event = { |
| zrenderX : this.component.grid.getX() |
| + (this.component.grid.getXend() |
| - this.component.grid.getX() |
| ) / 4, |
| zrenderY : this.component.yAxis.getAxis(yAxisIndex) |
| .getCoordByIndex(dataIndex) |
| }; |
| } |
| this._showAxisTrigger( |
| xAxisIndex, |
| yAxisIndex, |
| dataIndex |
| ); |
| break; |
| case ecConfig.CHART_TYPE_RADAR : |
| if (typeof this.component.polar == 'undefined' |
| || serie.data[0].value.length <= dataIndex |
| ) { |
| return; |
| } |
| var polarIndex = serie.polarIndex || 0; |
| var vector = this.component.polar.getVector( |
| polarIndex, dataIndex, 'max' |
| ); |
| this._event = { |
| zrenderX : vector[0], |
| zrenderY : vector[1] |
| }; |
| this._showPolarTrigger( |
| polarIndex, |
| dataIndex |
| ); |
| break; |
| } |
| } |
| else { |
| // item trigger |
| var shapeList = chart.shapeList; |
| var x; |
| var y; |
| switch (chart.type) { |
| case ecConfig.CHART_TYPE_LINE : |
| case ecConfig.CHART_TYPE_BAR : |
| case ecConfig.CHART_TYPE_K : |
| case ecConfig.CHART_TYPE_SCATTER : |
| var dataIndex = params.dataIndex; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (ecData.get(shapeList[i], 'seriesIndex') == seriesIndex |
| && ecData.get(shapeList[i], 'dataIndex') == dataIndex |
| ) { |
| this._curTarget = shapeList[i]; |
| x = shapeList[i].style.x; |
| y = chart.type != ecConfig.CHART_TYPE_K |
| ? shapeList[i].style.y : shapeList[i].style.y[0]; |
| break; |
| } |
| } |
| break; |
| case ecConfig.CHART_TYPE_RADAR : |
| var dataIndex = params.dataIndex; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (shapeList[i].type == 'polygon' |
| && ecData.get(shapeList[i], 'seriesIndex') == seriesIndex |
| && ecData.get(shapeList[i], 'dataIndex') == dataIndex |
| ) { |
| this._curTarget = shapeList[i]; |
| var vector = this.component.polar.getCenter( |
| serie.polarIndex || 0 |
| ); |
| x = vector[0]; |
| y = vector[1]; |
| break; |
| } |
| } |
| break; |
| case ecConfig.CHART_TYPE_PIE : |
| var name = params.name; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (shapeList[i].type == 'sector' |
| && ecData.get(shapeList[i], 'seriesIndex') == seriesIndex |
| && ecData.get(shapeList[i], 'name') == name |
| ) { |
| this._curTarget = shapeList[i]; |
| var style = this._curTarget.style; |
| var midAngle = (style.startAngle + style.endAngle) |
| / 2 * Math.PI / 180; |
| x = this._curTarget.style.x + Math.cos(midAngle) * style.r / 1.5; |
| y = this._curTarget.style.y - Math.sin(midAngle) * style.r / 1.5; |
| break; |
| } |
| } |
| break; |
| case ecConfig.CHART_TYPE_MAP : |
| var name = params.name; |
| var mapType = serie.mapType; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (shapeList[i].type == 'text' |
| && shapeList[i]._mapType == mapType |
| && shapeList[i].style._name == name |
| ) { |
| this._curTarget = shapeList[i]; |
| x = this._curTarget.style.x + this._curTarget.position[0]; |
| y = this._curTarget.style.y + this._curTarget.position[1]; |
| break; |
| } |
| } |
| break; |
| case ecConfig.CHART_TYPE_CHORD: |
| var name = params.name; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (shapeList[i].type == 'sector' |
| && ecData.get(shapeList[i], 'name') == name |
| ) { |
| this._curTarget = shapeList[i]; |
| var style = this._curTarget.style; |
| var midAngle = (style.startAngle + style.endAngle) |
| / 2 * Math.PI / 180; |
| x = this._curTarget.style.x + Math.cos(midAngle) * (style.r - 2); |
| y = this._curTarget.style.y - Math.sin(midAngle) * (style.r - 2); |
| this.zr.trigger( |
| zrConfig.EVENT.MOUSEMOVE, |
| { |
| zrenderX : x, |
| zrenderY : y |
| } |
| ); |
| return; |
| } |
| } |
| break; |
| case ecConfig.CHART_TYPE_FORCE: |
| var name = params.name; |
| for (var i = 0, l = shapeList.length; i < l; i++) { |
| if (shapeList[i].type == 'circle' |
| && ecData.get(shapeList[i], 'name') == name |
| ) { |
| this._curTarget = shapeList[i]; |
| x = this._curTarget.position[0]; |
| y = this._curTarget.position[1]; |
| break; |
| } |
| } |
| break; |
| } |
| if (typeof x != 'undefined' && typeof y != 'undefined') { |
| this._event = { |
| zrenderX : x, |
| zrenderY : y |
| }; |
| this.zr.addHoverShape(this._curTarget); |
| this.zr.refreshHover(); |
| this._showItemTrigger(); |
| } |
| } |
| }, |
| |
| /** |
| * 关闭,公开接口 |
| */ |
| hideTip : function () { |
| this._hide(); |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| // this._selectedMap; |
| // this._defaultCssText; // css样式缓存 |
| // this._needAxisTrigger; // 坐标轴触发 |
| // this._curTarget; |
| // this._event; |
| // this._curTicket; // 异步回调标识,用来区分多个请求 |
| |
| // 缓存一些高宽数据 |
| this._zrHeight = this.zr.getHeight(); |
| this._zrWidth = this.zr.getWidth(); |
| |
| if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { |
| this.zr.delShape(this._lastTipShape.tipShape); |
| } |
| this._lastTipShape = false; |
| this.shapeList.length = 2; |
| |
| if (newOption) { |
| this.option = newOption; |
| this.option.tooltip = this.reformOption(this.option.tooltip); |
| this.option.tooltip.textStyle = zrUtil.merge( |
| this.option.tooltip.textStyle, |
| this.ecTheme.textStyle |
| ); |
| // 补全padding属性 |
| this.option.tooltip.padding = this.reformCssArray( |
| this.option.tooltip.padding |
| ); |
| |
| this._needAxisTrigger = false; |
| if (this.option.tooltip.trigger == 'axis') { |
| this._needAxisTrigger = true; |
| } |
| |
| var series = this.option.series; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (this.query(series[i], 'tooltip.trigger') == 'axis') { |
| this._needAxisTrigger = true; |
| break; |
| } |
| } |
| // this._hidingTicket; |
| // this._showingTicket; |
| this._showDelay = this.option.tooltip.showDelay; // 显示延迟 |
| this._hideDelay = this.option.tooltip.hideDelay; // 隐藏延迟 |
| this._defaultCssText = this._style(this.option.tooltip); |
| |
| this._setSelectedMap(); |
| this._axisLineWidth = this.option.tooltip.axisPointer.lineStyle.width; |
| } |
| if (this.showing) { |
| this._tryShow(); |
| } |
| }, |
| |
| /** |
| * 释放后实例不可用,重载基类方法 |
| */ |
| dispose : function () { |
| if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { |
| this.zr.delShape(this._lastTipShape.tipShape); |
| } |
| this.clear(); |
| this.shapeList = null; |
| |
| clearTimeout(this._hidingTicket); |
| clearTimeout(this._showingTicket); |
| this.zr.un(zrConfig.EVENT.MOUSEMOVE, this._onmousemove); |
| this.zr.un(zrConfig.EVENT.GLOBALOUT, this._onglobalout); |
| |
| if (this.hasAppend) { |
| this.dom.firstChild.removeChild(this._tDom); |
| } |
| this._tDom = null; |
| }, |
| |
| /** |
| * html转码的方法 |
| */ |
| _encodeHTML : function (source) { |
| return String(source) |
| .replace(/&/g, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/"/g, '"') |
| .replace(/'/g, '''); |
| } |
| }; |
| |
| zrUtil.inherits(Tooltip, Base); |
| |
| require('../component').define('tooltip', Tooltip); |
| |
| return Tooltip; |
| }); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:圆环 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'ring', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,圆心横坐标 |
| y : {number}, // 必须,圆心纵坐标 |
| r0 : {number}, // 必须,内圆半径 |
| r : {number}, // 必须,外圆半径 |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为outside,附加文本位置。 |
| // outside | inside |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'ring', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| r : 50, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Ring',['require','./Base','../tool/util'],function (require) { |
| var Base = require('./Base'); |
| |
| function Ring(options) { |
| Base.call(this, options); |
| } |
| |
| Ring.prototype = { |
| type: 'ring', |
| |
| /** |
| * 创建圆环路径,依赖扇形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| // 非零环绕填充优化 |
| ctx.arc(style.x, style.y, style.r, 0, Math.PI * 2, false); |
| ctx.moveTo(style.x + style.r0, style.y); |
| ctx.arc(style.x, style.y, style.r0, 0, Math.PI * 2, true); |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var lineWidth; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| else { |
| lineWidth = 0; |
| } |
| style.__rect = { |
| x : Math.round(style.x - style.r - lineWidth / 2), |
| y : Math.round(style.y - style.r - lineWidth / 2), |
| width : style.r * 2 + lineWidth, |
| height : style.r * 2 + lineWidth |
| }; |
| |
| return style.__rect; |
| } |
| }; |
| |
| require('../tool/util').inherits(Ring, Base); |
| return Ring; |
| } |
| ); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:扇形 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'sector', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,圆心横坐标 |
| y : {number}, // 必须,圆心纵坐标 |
| r0 : {number}, // 默认为0,内圆半径,指定后将出现内弧,同时扇边长度 = r - r0 |
| r : {number}, // 必须,外圆半径 |
| startAngle : {number}, // 必须,起始角度[0, 360) |
| endAngle : {number}, // 必须,结束角度(0, 360] |
| brushType : {string}, // 默认为fill,绘画方式 |
| // fill(填充) | stroke(描边) | both(填充+描边) |
| color : {color}, // 默认为'#000',填充颜色,支持rgba |
| strokeColor : {color}, // 默认为'#000',描边颜色(轮廓),支持rgba |
| lineWidth : {number}, // 默认为1,线条宽度,描边下有效 |
| |
| opacity : {number}, // 默认为1,透明度设置,如果color为rgba,则最终透明度效果叠加 |
| shadowBlur : {number}, // 默认为0,阴影模糊度,大于0有效 |
| shadowColor : {color}, // 默认为'#000',阴影色彩,支持rgba |
| shadowOffsetX : {number}, // 默认为0,阴影横向偏移,正值往右,负值往左 |
| shadowOffsetY : {number}, // 默认为0,阴影纵向偏移,正值往下,负值往上 |
| |
| text : {string}, // 默认为null,附加文本 |
| textFont : {string}, // 默认为null,附加文本样式,eg:'bold 18px verdana' |
| textPosition : {string}, // 默认为outside,附加文本位置。 |
| // outside | inside |
| textAlign : {string}, // 默认根据textPosition自动设置,附加文本水平对齐。 |
| // start | end | left | right | center |
| textBaseline : {string}, // 默认根据textPosition自动设置,附加文本垂直对齐。 |
| // top | bottom | middle | |
| // alphabetic | hanging | ideographic |
| textColor : {color}, // 默认根据textPosition自动设置,默认策略如下,附加文本颜色 |
| // 'inside' ? '#fff' : color |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'sector', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : 100, |
| r : 50, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function(eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define( |
| 'zrender/shape/Sector',['require','../tool/math','./Base','./Ring','./Polygon','../tool/util'],function (require) { |
| var math = require('../tool/math'); |
| var Base = require('./Base'); |
| |
| function Sector(options) { |
| Base.call(this, options); |
| } |
| |
| Sector.prototype = { |
| type: 'sector', |
| |
| /** |
| * 创建扇形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function(ctx, style) { |
| var x = style.x; // 圆心x |
| var y = style.y; // 圆心y |
| var r0 = typeof style.r0 == 'undefined' // 形内半径[0,r) |
| ? 0 : style.r0; |
| var r = style.r; // 扇形外半径(0,r] |
| var startAngle = style.startAngle; // 起始角度[0,360) |
| var endAngle = style.endAngle; // 结束角度(0,360] |
| |
| if (Math.abs(endAngle - startAngle) >= 360) { |
| // 大于360度的扇形简化为圆环画法 |
| ctx.arc(x, y, r, 0, Math.PI * 2, false); |
| if (r0 !== 0) { |
| ctx.moveTo(x + r0, y); |
| ctx.arc(x, y, r0, 0, Math.PI * 2, true); |
| } |
| return; |
| } |
| |
| startAngle = math.degreeToRadian(startAngle); |
| endAngle = math.degreeToRadian(endAngle); |
| |
| var PI2 = Math.PI * 2; |
| var cosStartAngle = math.cos(startAngle); |
| var sinStartAngle = math.sin(startAngle); |
| ctx.moveTo( |
| cosStartAngle * r0 + x, |
| y - sinStartAngle * r0 |
| ); |
| |
| ctx.lineTo( |
| cosStartAngle * r + x, |
| y - sinStartAngle * r |
| ); |
| |
| ctx.arc(x, y, r, PI2 - startAngle, PI2 - endAngle, true); |
| |
| ctx.lineTo( |
| math.cos(endAngle) * r0 + x, |
| y - math.sin(endAngle) * r0 |
| ); |
| |
| if (r0 !== 0) { |
| ctx.arc(x, y, r0, PI2 - endAngle, PI2 - startAngle, false); |
| } |
| |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var x = style.x; // 圆心x |
| var y = style.y; // 圆心y |
| var r0 = typeof style.r0 == 'undefined' // 形内半径[0,r) |
| ? 0 : style.r0; |
| var r = style.r; // 扇形外半径(0,r] |
| var startAngle = style.startAngle; // 起始角度[0,360) |
| var endAngle = style.endAngle; // 结束角度(0,360] |
| |
| if (Math.abs(endAngle - startAngle) >= 360) { |
| // 大于360度的扇形简化为圆环bbox |
| style.__rect = require('./Ring').prototype.getRect(style); |
| return style.__rect; |
| } |
| |
| startAngle = (720 + startAngle) % 360; |
| endAngle = (720 + endAngle) % 360; |
| if (endAngle <= startAngle) { |
| endAngle += 360; |
| } |
| var pointList = []; |
| if (startAngle <= 90 && endAngle >= 90) { |
| pointList.push([ |
| x, y - r |
| ]); |
| } |
| if (startAngle <= 180 && endAngle >= 180) { |
| pointList.push([ |
| x - r, y |
| ]); |
| } |
| if (startAngle <= 270 && endAngle >= 270) { |
| pointList.push([ |
| x, y + r |
| ]); |
| } |
| if (startAngle <= 360 && endAngle >= 360) { |
| pointList.push([ |
| x + r, y |
| ]); |
| } |
| |
| startAngle = math.degreeToRadian(startAngle); |
| endAngle = math.degreeToRadian(endAngle); |
| |
| |
| pointList.push([ |
| math.cos(startAngle) * r0 + x, |
| y - math.sin(startAngle) * r0 |
| ]); |
| |
| pointList.push([ |
| math.cos(startAngle) * r + x, |
| y - math.sin(startAngle) * r |
| ]); |
| |
| pointList.push([ |
| math.cos(endAngle) * r + x, |
| y - math.sin(endAngle) * r |
| ]); |
| |
| pointList.push([ |
| math.cos(endAngle) * r0 + x, |
| y - math.sin(endAngle) * r0 |
| ]); |
| |
| style.__rect = require('./Polygon').prototype.getRect({ |
| brushType : style.brushType, |
| lineWidth : style.lineWidth, |
| pointList : pointList |
| }); |
| |
| return style.__rect; |
| } |
| }; |
| |
| |
| require('../tool/util').inherits(Sector, Base); |
| return Sector; |
| } |
| ); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:蜡烛 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'candle', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| x : {number}, // 必须,横坐标 |
| y : {Array}, // 必须,纵坐标数组 |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'candle', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| x : 200, |
| y : [100,123,90,125], |
| width : 150, |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function (eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define('echarts/util/shape/Candle',['require','zrender/shape/Base','zrender/tool/util','./normalIsCover'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function Candle(options) { |
| Base.call(this, options); |
| } |
| |
| Candle.prototype = { |
| type: 'candle', |
| _numberOrder : function (a, b) { |
| return b - a; |
| }, |
| |
| /** |
| * 创建矩形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| var yList = zrUtil.clone(style.y).sort(this._numberOrder); |
| |
| ctx.moveTo(style.x, yList[3]); |
| ctx.lineTo(style.x, yList[2]); |
| ctx.moveTo(style.x - style.width / 2, yList[2]); |
| ctx.rect( |
| style.x - style.width / 2, |
| yList[2], |
| style.width, |
| yList[1] - yList[2] |
| ); |
| ctx.moveTo(style.x, yList[1]); |
| ctx.lineTo(style.x, yList[0]); |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function (style) { |
| if (!style.__rect) { |
| var lineWidth = 0; |
| if (style.brushType == 'stroke' || style.brushType == 'fill') { |
| lineWidth = style.lineWidth || 1; |
| } |
| |
| var yList = zrUtil.clone(style.y).sort(this._numberOrder); |
| style.__rect = { |
| x : Math.round(style.x - style.width / 2 - lineWidth / 2), |
| y : Math.round(yList[3] - lineWidth / 2), |
| width : style.width + lineWidth, |
| height : yList[0] - yList[3] + lineWidth |
| }; |
| } |
| |
| return style.__rect; |
| }, |
| |
| |
| isCover : require('./normalIsCover') |
| }; |
| |
| zrUtil.inherits(Candle, Base); |
| |
| return Candle; |
| }); |
| |
| /** |
| * echarts组件:图例 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/legend',['require','./base','zrender/shape/Text','zrender/shape/Rectangle','zrender/shape/Sector','../util/shape/Icon','../util/shape/Candle','../config','zrender/tool/util','zrender/tool/area','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var SectorShape = require('zrender/shape/Sector'); |
| //var BeziercurveShape = require('zrender/shape/Beziercurve'); |
| var IconShape = require('../util/shape/Icon'); |
| var CandleShape = require('../util/shape/Candle'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrArea = require('zrender/tool/area'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表参数 |
| */ |
| function Legend(ecTheme, messageCenter, zr, option, myChart) { |
| if (!this.query(option, 'legend.data')) { |
| console.error('option.legend.data has not been defined.'); |
| return; |
| } |
| |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| var self = this; |
| self._legendSelected = function (param) { |
| self.__legendSelected(param); |
| }; |
| |
| this._colorIndex = 0; |
| this._colorMap = {}; |
| this._selectedMap = {}; |
| |
| this.refresh(option); |
| } |
| |
| Legend.prototype = { |
| type : ecConfig.COMPONENT_TYPE_LEGEND, |
| _buildShape : function () { |
| // 图例元素组的位置参数,通过计算所得x, y, width, height |
| this._itemGroupLocation = this._getItemGroupLocation(); |
| |
| this._buildBackground(); |
| this._buildItem(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| }, |
| |
| /** |
| * 构建所有图例元素 |
| */ |
| _buildItem : function () { |
| var data = this.legendOption.data; |
| var dataLength = data.length; |
| var itemName; |
| var itemType; |
| var itemShape; |
| var textShape; |
| var textStyle = this.legendOption.textStyle; |
| var dataTextStyle; |
| var dataFont; |
| var formattedName; |
| |
| var zrWidth = this.zr.getWidth(); |
| var zrHeight = this.zr.getHeight(); |
| var lastX = this._itemGroupLocation.x; |
| var lastY = this._itemGroupLocation.y; |
| var itemWidth = this.legendOption.itemWidth; |
| var itemHeight = this.legendOption.itemHeight; |
| var itemGap = this.legendOption.itemGap; |
| var color; |
| |
| if (this.legendOption.orient == 'vertical' && this.legendOption.x == 'right') { |
| lastX = this._itemGroupLocation.x |
| + this._itemGroupLocation.width |
| - itemWidth; |
| } |
| |
| for (var i = 0; i < dataLength; i++) { |
| dataTextStyle = zrUtil.merge( |
| data[i].textStyle || {}, |
| textStyle |
| ); |
| dataFont = this.getFont(dataTextStyle); |
| |
| itemName = this._getName(data[i]); |
| formattedName = this._getFormatterName(itemName); |
| if (itemName === '') { |
| if (this.legendOption.orient == 'horizontal') { |
| lastX = this._itemGroupLocation.x; |
| lastY += itemHeight + itemGap; |
| } |
| else { |
| this.legendOption.x == 'right' |
| ? lastX -= this._itemGroupLocation.maxWidth + itemGap |
| : lastX += this._itemGroupLocation.maxWidth + itemGap; |
| lastY = this._itemGroupLocation.y; |
| } |
| continue; |
| } |
| itemType = data[i].icon || this._getSomethingByName(itemName).type; |
| |
| color = this.getColor(itemName); |
| |
| if (this.legendOption.orient == 'horizontal') { |
| if (zrWidth - lastX < 200 // 最后200px做分行预判 |
| && (itemWidth + 5 + zrArea.getTextWidth(formattedName, dataFont) |
| // 分行的最后一个不用算itemGap |
| + (i == dataLength - 1 || data[i+1] === '' ? 0 : itemGap) |
| ) >= zrWidth - lastX |
| ) { |
| lastX = this._itemGroupLocation.x; |
| lastY += itemHeight + itemGap; |
| } |
| } |
| else { |
| if (zrHeight - lastY < 200 // 最后200px做分行预判 |
| && (itemHeight |
| // 分行的最后一个不用算itemGap |
| + (i == dataLength - 1 || data[i+1] === '' ? 0 : itemGap) |
| ) >= zrHeight - lastY |
| ) { |
| this.legendOption.x == 'right' |
| ? lastX -= this._itemGroupLocation.maxWidth + itemGap |
| : lastX += this._itemGroupLocation.maxWidth + itemGap; |
| lastY = this._itemGroupLocation.y; |
| } |
| } |
| |
| // 图形 |
| itemShape = this._getItemShapeByType( |
| lastX, lastY, |
| itemWidth, itemHeight, |
| (this._selectedMap[itemName] ? color : '#ccc'), |
| itemType, |
| color |
| ); |
| itemShape._name = itemName; |
| itemShape = new IconShape(itemShape); |
| |
| // 文字 |
| textShape = { |
| // shape : 'text', |
| zlevel : this._zlevelBase, |
| style : { |
| x : lastX + itemWidth + 5, |
| y : lastY + itemHeight / 2, |
| color : this._selectedMap[itemName] |
| ? (dataTextStyle.color === 'auto' ? color : dataTextStyle.color) |
| : '#ccc', |
| text: formattedName, |
| textFont: dataFont, |
| textBaseline: 'middle' |
| }, |
| highlightStyle : { |
| color : color, |
| brushType: 'fill' |
| }, |
| hoverable : !!this.legendOption.selectedMode, |
| clickable : !!this.legendOption.selectedMode |
| }; |
| |
| if (this.legendOption.orient == 'vertical' |
| && this.legendOption.x == 'right' |
| ) { |
| textShape.style.x -= (itemWidth + 10); |
| textShape.style.textAlign = 'right'; |
| } |
| |
| textShape._name = itemName; |
| textShape = new TextShape(textShape); |
| |
| if (this.legendOption.selectedMode) { |
| itemShape.onclick = textShape.onclick = this._legendSelected; |
| itemShape.onmouseover = textShape.onmouseover = this.hoverConnect; |
| itemShape.hoverConnect = textShape.id; |
| textShape.hoverConnect = itemShape.id; |
| } |
| this.shapeList.push(itemShape); |
| this.shapeList.push(textShape); |
| |
| if (this.legendOption.orient == 'horizontal') { |
| lastX += itemWidth + 5 |
| + zrArea.getTextWidth(formattedName, dataFont) |
| + itemGap; |
| } |
| else { |
| lastY += itemHeight + itemGap; |
| } |
| } |
| |
| if (this.legendOption.orient == 'horizontal' |
| && this.legendOption.x == 'center' |
| && lastY != this._itemGroupLocation.y |
| ) { |
| // 多行橫排居中优化 |
| this._mLineOptimize(); |
| } |
| }, |
| |
| _getName : function(data) { |
| return typeof data.name != 'undefined' ? data.name : data; |
| }, |
| |
| _getFormatterName: function(itemName) { |
| var formatter = this.legendOption.formatter; |
| var formattedName; |
| if (typeof formatter == 'function') { |
| formattedName = formatter.call(this.myChart, itemName); |
| } |
| else if (typeof formatter == 'string') { |
| formattedName = formatter.replace('{name}', itemName); |
| } |
| else { |
| formattedName = itemName; |
| } |
| return formattedName; |
| }, |
| |
| _getFormatterNameFromData: function(data) { |
| var itemName = this._getName(data); |
| return this._getFormatterName(itemName); |
| }, |
| |
| // 多行橫排居中优化 |
| _mLineOptimize : function () { |
| var lineOffsetArray = []; // 每行宽度 |
| var lastX = this._itemGroupLocation.x; |
| for (var i = 2, l = this.shapeList.length; i < l; i++) { |
| if (this.shapeList[i].style.x == lastX) { |
| lineOffsetArray.push( |
| ( |
| this._itemGroupLocation.width |
| - ( |
| this.shapeList[i - 1].style.x |
| + zrArea.getTextWidth( |
| this.shapeList[i - 1].style.text, |
| this.shapeList[i - 1].style.textFont |
| ) |
| - lastX |
| ) |
| ) / 2 |
| ); |
| } |
| else if (i == l - 1) { |
| lineOffsetArray.push( |
| ( |
| this._itemGroupLocation.width |
| - ( |
| this.shapeList[i].style.x |
| + zrArea.getTextWidth( |
| this.shapeList[i].style.text, |
| this.shapeList[i].style.textFont |
| ) |
| - lastX |
| ) |
| ) / 2 |
| ); |
| } |
| } |
| var curLineIndex = -1; |
| for (var i = 1, l = this.shapeList.length; i < l; i++) { |
| if (this.shapeList[i].style.x == lastX) { |
| curLineIndex++; |
| } |
| if (lineOffsetArray[curLineIndex] === 0) { |
| continue; |
| } |
| else { |
| this.shapeList[i].style.x += lineOffsetArray[curLineIndex]; |
| } |
| } |
| }, |
| |
| _buildBackground : function () { |
| var pTop = this.legendOption.padding[0]; |
| var pRight = this.legendOption.padding[1]; |
| var pBottom = this.legendOption.padding[2]; |
| var pLeft = this.legendOption.padding[3]; |
| |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : this._itemGroupLocation.x - pLeft, |
| y : this._itemGroupLocation.y - pTop, |
| width : this._itemGroupLocation.width + pLeft + pRight, |
| height : this._itemGroupLocation.height + pTop + pBottom, |
| brushType : this.legendOption.borderWidth === 0 ? 'fill' : 'both', |
| color : this.legendOption.backgroundColor, |
| strokeColor : this.legendOption.borderColor, |
| lineWidth : this.legendOption.borderWidth |
| } |
| })); |
| }, |
| |
| /** |
| * 根据选项计算图例实体的位置坐标 |
| */ |
| _getItemGroupLocation : function () { |
| var data = this.legendOption.data; |
| var dataLength = data.length; |
| var itemGap = this.legendOption.itemGap; |
| var itemWidth = this.legendOption.itemWidth + 5; // 5px是图形和文字的间隔,不可配 |
| var itemHeight = this.legendOption.itemHeight; |
| var textStyle = this.legendOption.textStyle; |
| var font = this.getFont(textStyle); |
| var totalWidth = 0; |
| var totalHeight = 0; |
| var padding = this.legendOption.padding; |
| var zrWidth = this.zr.getWidth() - padding[1] - padding[3]; |
| var zrHeight = this.zr.getHeight() - padding[0] - padding[2]; |
| |
| var temp = 0; // 宽高计算,用于多行判断 |
| var maxWidth = 0; // 垂直布局有用 |
| if (this.legendOption.orient == 'horizontal') { |
| // 水平布局,计算总宽度 |
| totalHeight = itemHeight; |
| for (var i = 0; i < dataLength; i++) { |
| if (this._getName(data[i]) === '') { |
| temp -= itemGap; |
| if (temp > zrWidth) { |
| totalWidth = zrWidth; |
| totalHeight += itemHeight + itemGap; |
| } |
| else { |
| totalWidth = Math.max(totalWidth, temp); |
| } |
| totalHeight += itemHeight + itemGap; |
| temp = 0; |
| continue; |
| } |
| temp += itemWidth |
| + zrArea.getTextWidth( |
| this._getFormatterNameFromData(data[i]), |
| data[i].textStyle |
| ? this.getFont(zrUtil.merge( |
| data[i].textStyle || {}, |
| textStyle |
| )) |
| : font |
| ) |
| + itemGap; |
| } |
| totalHeight = Math.max(totalHeight, itemHeight); |
| temp -= itemGap; // 减去最后一个的itemGap |
| if (temp > zrWidth) { |
| totalWidth = zrWidth; |
| totalHeight += itemHeight + itemGap; |
| } else { |
| totalWidth = Math.max(totalWidth, temp); |
| } |
| } |
| else { |
| // 垂直布局,计算总高度 |
| for (var i = 0; i < dataLength; i++) { |
| maxWidth = Math.max( |
| maxWidth, |
| zrArea.getTextWidth( |
| this._getFormatterNameFromData(data[i]), |
| data[i].textStyle |
| ? this.getFont(zrUtil.merge( |
| data[i].textStyle || {}, |
| textStyle |
| )) |
| : font |
| ) |
| ); |
| } |
| maxWidth += itemWidth; |
| totalWidth = maxWidth; |
| for (var i = 0; i < dataLength; i++) { |
| if (this._getName(data[i]) === '') { |
| temp -= itemGap; |
| if (temp > zrHeight) { |
| totalHeight = zrHeight; |
| totalWidth += maxWidth + itemGap; |
| } |
| else { |
| totalHeight = Math.max(totalHeight, temp); |
| } |
| totalWidth += maxWidth + itemGap; |
| temp = 0; |
| continue; |
| } |
| temp += itemHeight + itemGap; |
| } |
| totalWidth = Math.max(totalWidth, maxWidth); |
| temp -= itemGap; // 减去最后一个的itemGap |
| if (temp > zrHeight) { |
| totalHeight = zrHeight; |
| totalWidth += maxWidth + itemGap; |
| } else { |
| totalHeight = Math.max(totalHeight, temp); |
| } |
| } |
| |
| zrWidth = this.zr.getWidth(); |
| zrHeight = this.zr.getHeight(); |
| var x; |
| switch (this.legendOption.x) { |
| case 'center' : |
| x = Math.floor((zrWidth - totalWidth) / 2); |
| break; |
| case 'left' : |
| x = this.legendOption.padding[3] + this.legendOption.borderWidth; |
| break; |
| case 'right' : |
| x = zrWidth |
| - totalWidth |
| - this.legendOption.padding[1] |
| - this.legendOption.padding[3] |
| - this.legendOption.borderWidth * 2; |
| break; |
| default : |
| x = this.parsePercent(this.legendOption.x, zrWidth); |
| break; |
| } |
| |
| var y; |
| switch (this.legendOption.y) { |
| case 'top' : |
| y = this.legendOption.padding[0] + this.legendOption.borderWidth; |
| break; |
| case 'bottom' : |
| y = zrHeight |
| - totalHeight |
| - this.legendOption.padding[0] |
| - this.legendOption.padding[2] |
| - this.legendOption.borderWidth * 2; |
| break; |
| case 'center' : |
| y = Math.floor((zrHeight - totalHeight) / 2); |
| break; |
| default : |
| y = this.parsePercent(this.legendOption.y, zrHeight); |
| break; |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : totalWidth, |
| height : totalHeight, |
| maxWidth : maxWidth |
| }; |
| }, |
| |
| /** |
| * 根据名称返回series数据或data |
| */ |
| _getSomethingByName : function (name) { |
| var series = this.option.series; |
| var data; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].name == name) { |
| // 系列名称优先 |
| return { |
| type : series[i].type, |
| series : series[i], |
| seriesIndex : i, |
| data : null, |
| dataIndex : -1 |
| }; |
| } |
| |
| if ( |
| series[i].type == ecConfig.CHART_TYPE_PIE |
| || series[i].type == ecConfig.CHART_TYPE_RADAR |
| || series[i].type == ecConfig.CHART_TYPE_CHORD |
| || series[i].type == ecConfig.CHART_TYPE_FORCE |
| || series[i].type == ecConfig.CHART_TYPE_FUNNEL |
| ) { |
| data = series[i].type != ecConfig.CHART_TYPE_FORCE |
| ? series[i].data // 饼图、雷达图、和弦图得查找里面的数据名字 |
| : series[i].categories; // 力导布局查找categories配置 |
| for (var j = 0, k = data.length; j < k; j++) { |
| if (data[j].name == name) { |
| return { |
| type : series[i].type, |
| series : series[i], |
| seriesIndex : i, |
| data : data[j], |
| dataIndex : j |
| }; |
| } |
| } |
| } |
| } |
| return { |
| type : 'bar', |
| series : null, |
| seriesIndex : -1, |
| data : null, |
| dataIndex : -1 |
| }; |
| }, |
| |
| _getItemShapeByType : function (x, y, width, height, color, itemType, defaultColor) { |
| var highlightColor = color === '#ccc' ? defaultColor : color; |
| var itemShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| iconType : 'legendicon' + itemType, |
| x : x, |
| y : y, |
| width : width, |
| height : height, |
| color : color, |
| strokeColor : color, |
| lineWidth : 2 |
| }, |
| highlightStyle: { |
| color : highlightColor, |
| strokeColor : highlightColor, |
| lineWidth : 1 |
| }, |
| hoverable : this.legendOption.selectedMode, |
| clickable : this.legendOption.selectedMode |
| }; |
| |
| var imageLocation; |
| if (itemType.match('image')) { |
| var imageLocation = itemType.replace( |
| new RegExp('^image:\\/\\/'), '' |
| ); |
| itemType = 'image'; |
| } |
| // 特殊设置 |
| switch (itemType) { |
| case 'line' : |
| itemShape.style.brushType = 'stroke'; |
| itemShape.highlightStyle.lineWidth = 3; |
| break; |
| case 'radar' : |
| case 'scatter' : |
| itemShape.highlightStyle.lineWidth = 3; |
| break; |
| case 'k' : |
| itemShape.style.brushType = 'both'; |
| itemShape.highlightStyle.lineWidth = 3; |
| itemShape.highlightStyle.color = |
| itemShape.style.color = this.query(this.ecTheme, 'k.itemStyle.normal.color') |
| || '#fff'; |
| itemShape.style.strokeColor = color != '#ccc' |
| ? (this.query(this.ecTheme, 'k.itemStyle.normal.lineStyle.color') |
| || '#ff3200') |
| : color; |
| break; |
| case 'image' : |
| itemShape.style.iconType = 'image'; |
| itemShape.style.image = imageLocation; |
| if (color === '#ccc') { |
| itemShape.style.opacity = 0.5; |
| } |
| break; |
| } |
| return itemShape; |
| }, |
| |
| __legendSelected : function (param) { |
| var itemName = param.target._name; |
| if (this.legendOption.selectedMode === 'single') { |
| for (var k in this._selectedMap) { |
| this._selectedMap[k] = false; |
| } |
| } |
| this._selectedMap[itemName] = !this._selectedMap[itemName]; |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.LEGEND_SELECTED, |
| param.event, |
| { |
| selected : this._selectedMap, |
| target : itemName |
| }, |
| this.myChart |
| ); |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption || this.option; |
| this.option.legend = this.reformOption(this.option.legend); |
| // 补全padding属性 |
| this.option.legend.padding = this.reformCssArray( |
| this.option.legend.padding |
| ); |
| this.legendOption = this.option.legend; |
| |
| var data = this.legendOption.data || []; |
| var itemName; |
| var something; |
| var color; |
| var queryTarget; |
| if (this.legendOption.selected) { |
| for (var k in this.legendOption.selected) { |
| this._selectedMap[k] = typeof this._selectedMap[k] != 'undefined' |
| ? this._selectedMap[k] |
| : this.legendOption.selected[k]; |
| } |
| } |
| for (var i = 0, dataLength = data.length; i < dataLength; i++) { |
| itemName = this._getName(data[i]); |
| if (itemName === '') { |
| continue; |
| } |
| something = this._getSomethingByName(itemName); |
| if (!something.series) { |
| this._selectedMap[itemName] = false; |
| } |
| else { |
| if (something.data |
| && (something.type == ecConfig.CHART_TYPE_PIE |
| || something.type == ecConfig.CHART_TYPE_FORCE |
| || something.type == ecConfig.CHART_TYPE_FUNNEL) |
| ) { |
| queryTarget = [something.data, something.series]; |
| } |
| else { |
| queryTarget = [something.series]; |
| } |
| |
| color = this.getItemStyleColor( |
| this.deepQuery(queryTarget, 'itemStyle.normal.color'), |
| something.seriesIndex, |
| something.dataIndex, |
| something.data |
| ); |
| if (color && something.type != ecConfig.CHART_TYPE_K) { |
| this.setColor(itemName, color); |
| } |
| this._selectedMap[itemName] = |
| typeof this._selectedMap[itemName] != 'undefined' |
| ? this._selectedMap[itemName] : true; |
| } |
| } |
| } |
| this.clear(); |
| this._buildShape(); |
| }, |
| |
| getRelatedAmount : function(name) { |
| var amount = 0; |
| var series = this.option.series; |
| var data; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].name == name) { |
| // 系列名称优先 |
| amount++; |
| } |
| |
| if ( |
| series[i].type == ecConfig.CHART_TYPE_PIE |
| || series[i].type == ecConfig.CHART_TYPE_RADAR |
| || series[i].type == ecConfig.CHART_TYPE_CHORD |
| || series[i].type == ecConfig.CHART_TYPE_FORCE |
| || series[i].type == ecConfig.CHART_TYPE_FUNNEL |
| ) { |
| data = series[i].type != ecConfig.CHART_TYPE_FORCE |
| ? series[i].data // 饼图、雷达图、和弦图得查找里面的数据名字 |
| : series[i].categories; // 力导布局查找categories配置 |
| for (var j = 0, k = data.length; j < k; j++) { |
| if (data[j].name == name && data[j].value != '-') { |
| amount++; |
| } |
| } |
| } |
| } |
| return amount; |
| }, |
| |
| setColor : function (legendName, color) { |
| this._colorMap[legendName] = color; |
| }, |
| |
| getColor : function (legendName) { |
| if (!this._colorMap[legendName]) { |
| this._colorMap[legendName] = this.zr.getColor(this._colorIndex++); |
| } |
| return this._colorMap[legendName]; |
| }, |
| |
| hasColor : function (legendName) { |
| return this._colorMap[legendName] ? this._colorMap[legendName] : false; |
| }, |
| |
| add : function (name, color){ |
| var data = this.legendOption.data; |
| for (var i = 0, dataLength = data.length; i < dataLength; i++) { |
| if (this._getName(data[i]) == name) { |
| // 已有就不重复加了 |
| return; |
| } |
| } |
| this.legendOption.data.push(name); |
| this.setColor(name,color); |
| this._selectedMap[name] = true; |
| }, |
| |
| del : function (name){ |
| var data = this.legendOption.data; |
| for (var i = 0, dataLength = data.length; i < dataLength; i++) { |
| if (this._getName(data[i]) == name) { |
| return this.legendOption.data.splice(i, 1); |
| } |
| } |
| }, |
| |
| /** |
| * 特殊图形元素回调设置 |
| * @param {Object} name |
| * @param {Object} itemShape |
| */ |
| getItemShape : function (name) { |
| if (typeof name == 'undefined') { |
| return; |
| } |
| var shape; |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| shape = this.shapeList[i]; |
| if (shape._name == name && shape.type != 'text') { |
| return shape; |
| } |
| } |
| }, |
| |
| /** |
| * 特殊图形元素回调设置 |
| * @param {Object} name |
| * @param {Object} itemShape |
| */ |
| setItemShape : function (name, itemShape) { |
| var shape; |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| shape = this.shapeList[i]; |
| if (shape._name == name && shape.type != 'text') { |
| if (!this._selectedMap[name]) { |
| itemShape.style.color = '#ccc'; |
| itemShape.style.strokeColor = '#ccc'; |
| } |
| this.zr.modShape(shape.id, itemShape); |
| } |
| } |
| }, |
| |
| isSelected : function (itemName) { |
| if (typeof this._selectedMap[itemName] != 'undefined') { |
| return this._selectedMap[itemName]; |
| } |
| else { |
| // 没在legend里定义的都为true啊~ |
| return true; |
| } |
| }, |
| |
| getSelectedMap : function () { |
| return this._selectedMap; |
| }, |
| |
| setSelected : function(itemName, selectStatus) { |
| if (this.legendOption.selectedMode === 'single') { |
| for (var k in this._selectedMap) { |
| this._selectedMap[k] = false; |
| } |
| } |
| this._selectedMap[itemName] = selectStatus; |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.LEGEND_SELECTED, |
| null, |
| { |
| selected : this._selectedMap, |
| target : itemName |
| }, |
| this.myChart |
| ); |
| }, |
| |
| /** |
| * 图例选择 |
| */ |
| onlegendSelected : function (param, status) { |
| var legendSelected = param.selected; |
| for (var itemName in legendSelected) { |
| if (this._selectedMap[itemName] != legendSelected[itemName]) { |
| // 有一项不一致都需要重绘 |
| status.needRefresh = true; |
| } |
| this._selectedMap[itemName] = legendSelected[itemName]; |
| } |
| return; |
| } |
| }; |
| |
| var legendIcon = { |
| line : function (ctx, style) { |
| var dy = style.height / 2; |
| ctx.moveTo(style.x, style.y + dy); |
| ctx.lineTo(style.x + style.width,style.y + dy); |
| }, |
| |
| pie : function (ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| SectorShape.prototype.buildPath(ctx, { |
| x : x + width / 2, |
| y : y + height + 2, |
| r : height + 2, |
| r0 : 6, |
| startAngle : 45, |
| endAngle : 135 |
| }); |
| }, |
| /* |
| chord : function (ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| ctx.moveTo(x, y + height); |
| BeziercurveShape.prototype.buildPath(ctx, { |
| xStart : x, |
| yStart : y + height, |
| cpX1 : x + width, |
| cpY1 : y + height, |
| cpX2 : x, |
| cpY2 : y + 4, |
| xEnd : x + width, |
| yEnd : y + 4 |
| }); |
| ctx.lineTo(x + width, y); |
| BeziercurveShape.prototype.buildPath(ctx, { |
| xStart : x + width, |
| yStart : y, |
| cpX1 : x, |
| cpY1 : y, |
| cpX2 : x + width, |
| cpY2 : y + height - 4, |
| xEnd : x, |
| yEnd : y + height - 4 |
| }); |
| ctx.lineTo(x, y + height); |
| }, |
| */ |
| k : function (ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| CandleShape.prototype.buildPath(ctx, { |
| x : x + width / 2, |
| y : [y + 1, y + 1, y + height - 6, y + height], |
| width : width - 6 |
| }); |
| }, |
| |
| bar : function (ctx, style) { |
| var x = style.x; |
| var y = style.y +1; |
| var width = style.width; |
| var height = style.height - 2; |
| var r = 3; |
| |
| ctx.moveTo(x + r, y); |
| ctx.lineTo(x + width - r, y); |
| ctx.quadraticCurveTo( |
| x + width, y, x + width, y + r |
| ); |
| ctx.lineTo(x + width, y + height - r); |
| ctx.quadraticCurveTo( |
| x + width, y + height, x + width - r, y + height |
| ); |
| ctx.lineTo(x + r, y + height); |
| ctx.quadraticCurveTo( |
| x, y + height, x, y + height - r |
| ); |
| ctx.lineTo(x, y + r); |
| ctx.quadraticCurveTo(x, y, x + r, y); |
| }, |
| |
| force : function (ctx, style) { |
| IconShape.prototype.iconLibrary.circle(ctx, style); |
| }, |
| |
| radar: function (ctx, style) { |
| var n = 6; |
| var x = style.x + style.width / 2; |
| var y = style.y + style.height / 2; |
| var r = style.height / 2; |
| |
| var dStep = 2 * Math.PI / n; |
| var deg = -Math.PI / 2; |
| var xStart = x + r * Math.cos(deg); |
| var yStart = y + r * Math.sin(deg); |
| |
| ctx.moveTo(xStart, yStart); |
| deg += dStep; |
| for (var i = 0, end = n - 1; i < end; i ++) { |
| ctx.lineTo(x + r * Math.cos(deg), y + r * Math.sin(deg)); |
| deg += dStep; |
| } |
| ctx.lineTo(xStart, yStart); |
| } |
| }; |
| legendIcon.chord = legendIcon.pie; |
| legendIcon.map = legendIcon.bar; |
| |
| for (var k in legendIcon) { |
| IconShape.prototype.iconLibrary['legendicon' + k] = legendIcon[k]; |
| } |
| |
| zrUtil.inherits(Legend, Base); |
| |
| require('../component').define('legend', Legend); |
| |
| return Legend; |
| }); |
| |
| |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:时间轴线 |
| */ |
| define('echarts/util/shape/Chain',['require','zrender/shape/Base','./Icon','zrender/shape/util/dashedLineTo','zrender/tool/util','zrender/tool/matrix'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var IconShape = require('./Icon'); |
| |
| var dashedLineTo = require('zrender/shape/util/dashedLineTo'); |
| var zrUtil = require('zrender/tool/util'); |
| var matrix = require('zrender/tool/matrix'); |
| |
| function Chain(options) { |
| Base.call(this, options); |
| } |
| |
| Chain.prototype = { |
| type : 'chain', |
| |
| /** |
| * 画刷 |
| * @param ctx 画布句柄 |
| * @param e 形状实体 |
| * @param isHighlight 是否为高亮状态 |
| * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e) |
| * 让painter更新视图,base.brush没用,需要的话重载brush |
| */ |
| brush : function (ctx, isHighlight) { |
| var style = this.style; |
| |
| if (isHighlight) { |
| // 根据style扩展默认高亮样式 |
| style = this.getHighlightStyle( |
| style, |
| this.highlightStyle || {} |
| ); |
| } |
| |
| ctx.save(); |
| this.setContext(ctx, style); |
| |
| // 设置transform |
| this.setTransform(ctx); |
| |
| ctx.beginPath(); |
| this.buildLinePath(ctx, style); |
| ctx.stroke(); |
| |
| this.brushSymbol(ctx, style); |
| |
| ctx.restore(); |
| return; |
| }, |
| |
| /** |
| * 创建线条路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildLinePath : function (ctx, style) { |
| var x = style.x; |
| var y = style.y + 5; |
| var width = style.width; |
| var height = style.height / 2 - 10; |
| |
| ctx.moveTo(x, y); |
| ctx.lineTo(x, y + height); |
| ctx.moveTo(x + width, y); |
| ctx.lineTo(x + width, y + height); |
| |
| ctx.moveTo(x, y + height / 2); |
| if (!style.lineType || style.lineType == 'solid') { |
| ctx.lineTo(x + width, y + height / 2); |
| } |
| else if (style.lineType == 'dashed' || style.lineType == 'dotted') { |
| var dashLength = (style.lineWidth || 1) |
| * (style.lineType == 'dashed' ? 5 : 1); |
| dashedLineTo(ctx, x, y + height / 2, x + width, y + height / 2, dashLength); |
| } |
| }, |
| |
| /** |
| * 标线始末标注 |
| */ |
| brushSymbol : function (ctx, style) { |
| var y = style.y + style.height / 4; |
| ctx.save(); |
| |
| var chainPoint = style.chainPoint; |
| var curPoint; |
| for (var idx = 0, l = chainPoint.length; idx < l; idx++) { |
| curPoint = chainPoint[idx]; |
| if (curPoint.symbol != 'none') { |
| ctx.beginPath(); |
| var symbolSize = curPoint.symbolSize; |
| IconShape.prototype.buildPath( |
| ctx, |
| { |
| iconType : curPoint.symbol, |
| x : curPoint.x - symbolSize, |
| y : y - symbolSize, |
| width : symbolSize * 2, |
| height : symbolSize * 2, |
| n : curPoint.n |
| } |
| ); |
| ctx.fillStyle = curPoint.isEmpty ? '#fff' : style.strokeColor; |
| ctx.closePath(); |
| ctx.fill(); |
| ctx.stroke(); |
| } |
| |
| if (curPoint.showLabel) { |
| ctx.font = curPoint.textFont; |
| ctx.fillStyle = curPoint.textColor; |
| ctx.textAlign = curPoint.textAlign; |
| ctx.textBaseline = curPoint.textBaseline; |
| if (curPoint.rotation) { |
| ctx.save(); |
| this._updateTextTransform(ctx, curPoint.rotation); |
| ctx.fillText(curPoint.name, curPoint.textX, curPoint.textY); |
| ctx.restore(); |
| } |
| else { |
| ctx.fillText(curPoint.name, curPoint.textX, curPoint.textY); |
| } |
| } |
| } |
| |
| ctx.restore(); |
| }, |
| |
| _updateTextTransform : function (ctx, rotation) { |
| var _transform = matrix.create(); |
| matrix.identity(_transform); |
| |
| if (rotation[0] !== 0) { |
| var originX = rotation[1] || 0; |
| var originY = rotation[2] || 0; |
| if (originX || originY) { |
| matrix.translate( |
| _transform, _transform, [-originX, -originY] |
| ); |
| } |
| matrix.rotate(_transform, _transform, rotation[0]); |
| if (originX || originY) { |
| matrix.translate( |
| _transform, _transform, [originX, originY] |
| ); |
| } |
| } |
| |
| // 保存这个变换矩阵 |
| ctx.transform.apply(ctx, _transform); |
| }, |
| |
| isCover : function (x, y) { |
| var rect = this.style; |
| if (x >= rect.x |
| && x <= (rect.x + rect.width) |
| && y >= rect.y |
| && y <= (rect.y + rect.height) |
| ) { |
| // 矩形内 |
| return true; |
| } |
| else { |
| return false; |
| } |
| } |
| }; |
| |
| zrUtil.inherits(Chain, Base); |
| |
| return Chain; |
| }); |
| |
| /** |
| * echarts组件:时间轴组件 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/timeline',['require','./base','zrender/shape/Rectangle','../util/shape/Icon','../util/shape/Chain','../config','zrender/tool/util','zrender/tool/area','zrender/tool/event','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var IconShape = require('../util/shape/Icon'); |
| var ChainShape = require('../util/shape/Chain'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrArea = require('zrender/tool/area'); |
| var zrEvent = require('zrender/tool/event'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表参数 |
| */ |
| function Timeline(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| var self = this; |
| self._onclick = function(param) { |
| return self.__onclick(param); |
| }; |
| self._ondrift = function (dx, dy) { |
| return self.__ondrift(this, dx, dy); |
| }; |
| self._ondragend = function () { |
| return self.__ondragend(); |
| }; |
| self._setCurrentOption = function() { |
| var timelineOption = self.timelineOption; |
| self.currentIndex %= timelineOption.data.length; |
| // console.log(self.currentIndex); |
| var curOption = self.options[self.currentIndex] || {}; |
| self.myChart.setOption(curOption, timelineOption.notMerge); |
| |
| self.messageCenter.dispatch( |
| ecConfig.EVENT.TIMELINE_CHANGED, |
| null, |
| { |
| currentIndex: self.currentIndex, |
| data : typeof timelineOption.data[self.currentIndex].name != 'undefined' |
| ? timelineOption.data[self.currentIndex].name |
| : timelineOption.data[self.currentIndex] |
| }, |
| self.myChart |
| ); |
| }; |
| self._onFrame = function() { |
| self._setCurrentOption(); |
| self._syncHandleShape(); |
| |
| if (self.timelineOption.autoPlay) { |
| self.playTicket = setTimeout( |
| function() { |
| self.currentIndex += 1; |
| if (!self.timelineOption.loop |
| && self.currentIndex >= self.timelineOption.data.length |
| ) { |
| self.currentIndex = self.timelineOption.data.length - 1; |
| self.stop(); |
| return; |
| } |
| self._onFrame(); |
| }, |
| self.timelineOption.playInterval |
| ); |
| } |
| }; |
| |
| this.setTheme(false); |
| this.options = this.option.options; |
| this.currentIndex = this.timelineOption.currentIndex % this.timelineOption.data.length; |
| |
| if (!this.timelineOption.notMerge && this.currentIndex !== 0) { |
| /* |
| for (var i = 1, l = this.timelineOption.data.length; i < l; i++) { |
| this.options[i] = zrUtil.merge( |
| this.options[i], this.options[i - 1] |
| ); |
| } |
| */ |
| this.options[this.currentIndex] = zrUtil.merge( |
| this.options[this.currentIndex], this.options[0] |
| ); |
| } |
| |
| if (this.timelineOption.show) { |
| this._buildShape(); |
| this._syncHandleShape(); |
| } |
| |
| this._setCurrentOption(); |
| |
| if (this.timelineOption.autoPlay) { |
| var self = this; |
| this.playTicket = setTimeout( |
| function() { |
| self.play(); |
| }, |
| this.ecTheme.animationDuration |
| ); |
| } |
| } |
| |
| Timeline.prototype = { |
| type : ecConfig.COMPONENT_TYPE_TIMELINE, |
| _buildShape : function () { |
| // 位置参数,通过计算所得x, y, width, height |
| this._location = this._getLocation(); |
| this._buildBackground(); |
| this._buildControl(); |
| this._chainPoint = this._getChainPoint(); |
| if (this.timelineOption.label.show) { |
| // 标签显示的挑选间隔 |
| var interval = this._getInterval(); |
| for (var i = 0, len = this._chainPoint.length; i < len; i += interval) { |
| this._chainPoint[i].showLabel = true; |
| } |
| } |
| this._buildChain(); |
| this._buildHandle(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| }, |
| |
| /** |
| * 根据选项计算实体的位置坐标 |
| */ |
| _getLocation : function () { |
| var timelineOption = this.timelineOption; |
| var padding = timelineOption.padding; |
| |
| // 水平布局 |
| var zrWidth = this.zr.getWidth(); |
| var x = this.parsePercent(timelineOption.x, zrWidth); |
| var x2 = this.parsePercent(timelineOption.x2, zrWidth); |
| var width; |
| if (typeof timelineOption.width == 'undefined') { |
| width = zrWidth - x - x2; |
| x2 = zrWidth - x2; |
| } |
| else { |
| width = this.parsePercent(timelineOption.width, zrWidth); |
| x2 = x + width; |
| } |
| |
| var zrHeight = this.zr.getHeight(); |
| var height = this.parsePercent(timelineOption.height, zrHeight); |
| var y; |
| var y2; |
| if (typeof timelineOption.y != 'undefined') { |
| y = this.parsePercent(timelineOption.y, zrHeight); |
| y2 = y + height; |
| } |
| else { |
| y2 = zrHeight - this.parsePercent(timelineOption.y2, zrHeight); |
| y = y2 - height; |
| } |
| |
| return { |
| x : x + padding[3], |
| y : y + padding[0], |
| x2 : x2 - padding[1], |
| y2 : y2 - padding[2], |
| width : width - padding[1] - padding[3], |
| height : height - padding[0] - padding[2] |
| }; |
| }, |
| |
| _getReformedLabel : function (idx) { |
| var timelineOption = this.timelineOption; |
| var data = typeof timelineOption.data[idx].name != 'undefined' |
| ? timelineOption.data[idx].name |
| : timelineOption.data[idx]; |
| var formatter = timelineOption.data[idx].formatter |
| || timelineOption.label.formatter; |
| if (formatter) { |
| if (typeof formatter == 'function') { |
| data = formatter.call(this.myChart, data); |
| } |
| else if (typeof formatter == 'string') { |
| data = formatter.replace('{value}', data); |
| } |
| } |
| return data; |
| }, |
| |
| /** |
| * 计算标签显示挑选间隔 |
| */ |
| _getInterval : function () { |
| var chainPoint = this._chainPoint; |
| var timelineOption = this.timelineOption; |
| var interval = timelineOption.label.interval; |
| if (interval == 'auto') { |
| // 麻烦的自适应计算 |
| var fontSize = timelineOption.label.textStyle.fontSize; |
| var data = timelineOption.data; |
| var dataLength = timelineOption.data.length; |
| |
| // 横向 |
| if (dataLength > 3) { |
| var isEnough = false; |
| var labelSpace; |
| var labelSize; |
| interval = 0; |
| while (!isEnough && interval < dataLength) { |
| interval++; |
| isEnough = true; |
| for (var i = interval; i < dataLength; i += interval) { |
| labelSpace = chainPoint[i].x - chainPoint[i - interval].x; |
| if (timelineOption.label.rotate !== 0) { |
| // 有旋转 |
| labelSize = fontSize; |
| } |
| else if (data[i].textStyle) { |
| labelSize = zrArea.getTextWidth( |
| chainPoint[i].name, |
| chainPoint[i].textFont |
| ); |
| } |
| else { |
| // 不定义data级特殊文本样式,用fontSize优化getTextWidth |
| var label = chainPoint[i].name + ''; |
| var wLen = (label.match(/\w/g) || '').length; |
| var oLen = label.length - wLen; |
| labelSize = wLen * fontSize * 2 / 3 + oLen * fontSize; |
| } |
| |
| if (labelSpace < labelSize) { |
| // 放不下,中断循环让interval++ |
| isEnough = false; |
| break; |
| } |
| } |
| } |
| } |
| else { |
| // 少于3个则全部显示 |
| interval = 1; |
| } |
| } |
| else { |
| // 用户自定义间隔 |
| interval = interval - 0 + 1; |
| } |
| |
| return interval; |
| }, |
| |
| /** |
| * 根据选项计算时间链条上的坐标及symbolList |
| */ |
| _getChainPoint : function() { |
| var timelineOption = this.timelineOption; |
| var symbol = timelineOption.symbol.toLowerCase(); |
| var symbolSize = timelineOption.symbolSize; |
| var rotate = timelineOption.label.rotate; |
| var textStyle = timelineOption.label.textStyle; |
| var textFont = this.getFont(textStyle); |
| var dataTextStyle; |
| var data = timelineOption.data; |
| var x = this._location.x; |
| var y = this._location.y + this._location.height / 4 * 3; |
| var width = this._location.x2 - this._location.x; |
| var len = data.length; |
| |
| function _getName(i) { |
| return typeof data[i].name != 'undefined' ? data[i].name : data[i]; |
| } |
| var xList = []; |
| if (len > 1) { |
| var boundaryGap = width / len; |
| boundaryGap = boundaryGap > 50 ? 50 : (boundaryGap < 20 ? 5 : boundaryGap); |
| width -= boundaryGap * 2; |
| if (timelineOption.type == 'number') { |
| // 平均分布 |
| for (var i = 0; i < len; i++) { |
| xList.push(x + boundaryGap + width / (len - 1) * i); |
| } |
| } |
| else { |
| // 时间比例 |
| xList[0] = new Date(_getName(0).replace(/-/g, '/')); |
| xList[len - 1] = new Date(_getName(len - 1).replace(/-/g, '/')) - xList[0]; |
| for (var i = 1; i < len; i++) { |
| xList[i] = x + boundaryGap |
| + width |
| * (new Date(_getName(i).replace(/-/g, '/')) - xList[0]) |
| / xList[len - 1]; |
| } |
| xList[0] = x + boundaryGap; |
| } |
| } |
| else { |
| xList.push(x + width / 2); |
| } |
| |
| var list = []; |
| var curSymbol; |
| var n; |
| var isEmpty; |
| var textAlign; |
| var rotation; |
| for (var i = 0; i < len; i++) { |
| x = xList[i]; |
| curSymbol = (data[i].symbol && data[i].symbol.toLowerCase()) || symbol; |
| if (curSymbol.match('empty')) { |
| curSymbol = curSymbol.replace('empty', ''); |
| isEmpty = true; |
| } |
| else { |
| isEmpty = false; |
| } |
| if (curSymbol.match('star')) { |
| n = (curSymbol.replace('star','') - 0) || 5; |
| curSymbol = 'star'; |
| } |
| |
| dataTextStyle = data[i].textStyle |
| ? zrUtil.merge(data[i].textStyle || {}, textStyle) |
| : textStyle; |
| |
| textAlign = dataTextStyle.align || 'center'; |
| |
| if (rotate) { |
| textAlign = rotate > 0 ? 'right' : 'left'; |
| rotation = [rotate * Math.PI / 180, x, y - 5]; |
| } |
| else { |
| rotation = false; |
| } |
| |
| list.push({ |
| x : x, |
| n : n, |
| isEmpty : isEmpty, |
| symbol : curSymbol, |
| symbolSize : data[i].symbolSize || symbolSize, |
| color : data[i].color, |
| borderColor : data[i].borderColor, |
| borderWidth : data[i].borderWidth, |
| name : this._getReformedLabel(i), |
| textColor : dataTextStyle.color, |
| textAlign : textAlign, |
| textBaseline : dataTextStyle.baseline || 'middle', |
| textX : x, |
| textY : y - (rotate ? 5 : 0), |
| textFont : data[i].textStyle ? this.getFont(dataTextStyle) : textFont, |
| rotation : rotation, |
| showLabel : false |
| }); |
| } |
| |
| return list; |
| }, |
| |
| _buildBackground : function () { |
| var timelineOption = this.timelineOption; |
| var padding = timelineOption.padding; |
| var width = this._location.width; |
| var height = this._location.height; |
| |
| if (timelineOption.borderWidth !== 0 |
| || timelineOption.backgroundColor.replace(/\s/g,'') != 'rgba(0,0,0,0)' |
| ) { |
| // 背景 |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : this._location.x - padding[3], |
| y : this._location.y - padding[0], |
| width : width + padding[1] + padding[3], |
| height : height + padding[0] + padding[2], |
| brushType : timelineOption.borderWidth === 0 |
| ? 'fill' : 'both', |
| color : timelineOption.backgroundColor, |
| strokeColor : timelineOption.borderColor, |
| lineWidth : timelineOption.borderWidth |
| } |
| })); |
| } |
| }, |
| |
| _buildControl : function() { |
| var self = this; |
| var timelineOption = this.timelineOption; |
| var lineStyle = timelineOption.lineStyle; |
| var controlStyle = timelineOption.controlStyle; |
| if (timelineOption.controlPosition == 'none') { |
| return; |
| } |
| var iconSize = 15; |
| var iconGap = 5; |
| var x; |
| if (timelineOption.controlPosition == 'left') { |
| x = this._location.x; |
| this._location.x += (iconSize + iconGap) * 3; |
| } |
| else { |
| x = this._location.x2 - ((iconSize + iconGap) * 3 - iconGap); |
| this._location.x2 -= (iconSize + iconGap) * 3; |
| } |
| |
| var y = this._location.y; |
| var iconStyle = { |
| zlevel : this._zlevelBase + 1, |
| style : { |
| iconType : 'timelineControl', |
| symbol : 'last', |
| x : x, |
| y : y, |
| width : iconSize, |
| height : iconSize, |
| brushType : 'stroke', |
| color: controlStyle.normal.color, |
| strokeColor : controlStyle.normal.color, |
| lineWidth : lineStyle.width |
| }, |
| highlightStyle : { |
| color : controlStyle.emphasis.color, |
| strokeColor : controlStyle.emphasis.color, |
| lineWidth : lineStyle.width + 1 |
| }, |
| clickable : true |
| }; |
| |
| this._ctrLastShape = new IconShape(iconStyle); |
| this._ctrLastShape.onclick = function() { |
| self.last(); |
| }; |
| this.shapeList.push(this._ctrLastShape); |
| |
| x += iconSize + iconGap; |
| this._ctrPlayShape = new IconShape(zrUtil.clone(iconStyle)); |
| this._ctrPlayShape.style.brushType = 'fill'; |
| this._ctrPlayShape.style.symbol = 'play'; |
| this._ctrPlayShape.style.status = this.timelineOption.autoPlay ? 'playing' : 'stop'; |
| this._ctrPlayShape.style.x = x; |
| this._ctrPlayShape.onclick = function() { |
| if (self._ctrPlayShape.style.status == 'stop') { |
| self.play(); |
| } |
| else { |
| self.stop(); |
| } |
| }; |
| this.shapeList.push(this._ctrPlayShape); |
| |
| x += iconSize + iconGap; |
| this._ctrNextShape = new IconShape(zrUtil.clone(iconStyle)); |
| this._ctrNextShape.style.symbol = 'next'; |
| this._ctrNextShape.style.x = x; |
| this._ctrNextShape.onclick = function() { |
| self.next(); |
| }; |
| this.shapeList.push(this._ctrNextShape); |
| }, |
| |
| /** |
| * 构建时间轴 |
| */ |
| _buildChain : function () { |
| var timelineOption = this.timelineOption; |
| var lineStyle = timelineOption.lineStyle; |
| this._timelineShae = { |
| zlevel : this._zlevelBase, |
| style : { |
| x : this._location.x, |
| y : this.subPixelOptimize(this._location.y, lineStyle.width), |
| width : this._location.x2 - this._location.x, |
| height : this._location.height, |
| chainPoint : this._chainPoint, |
| brushType:'both', |
| strokeColor : lineStyle.color, |
| lineWidth : lineStyle.width, |
| lineType : lineStyle.type |
| }, |
| hoverable : false, |
| clickable : true, |
| onclick : this._onclick |
| }; |
| |
| this._timelineShae = new ChainShape(this._timelineShae); |
| this.shapeList.push(this._timelineShae); |
| }, |
| |
| /** |
| * 构建拖拽手柄 |
| */ |
| _buildHandle : function () { |
| var curPoint = this._chainPoint[this.currentIndex]; |
| var symbolSize = curPoint.symbolSize + 1; |
| symbolSize = symbolSize < 5 ? 5 : symbolSize; |
| |
| this._handleShape = { |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| draggable : true, |
| style : { |
| iconType : 'diamond', |
| n : curPoint.n, |
| x : curPoint.x - symbolSize, |
| y : this._location.y + this._location.height / 4 - symbolSize, |
| width : symbolSize * 2, |
| height : symbolSize * 2, |
| brushType:'both', |
| textPosition : 'specific', |
| textX : curPoint.x, |
| textY : this._location.y - this._location.height / 4, |
| textAlign : 'center', |
| textBaseline : 'middle' |
| }, |
| highlightStyle : {}, |
| ondrift : this._ondrift, |
| ondragend : this._ondragend |
| }; |
| |
| this._handleShape = new IconShape(this._handleShape); |
| this.shapeList.push(this._handleShape); |
| }, |
| |
| /** |
| * 同步拖拽图形样式 |
| */ |
| _syncHandleShape : function() { |
| if (!this.timelineOption.show) { |
| return; |
| } |
| |
| var timelineOption = this.timelineOption; |
| var cpStyle = timelineOption.checkpointStyle; |
| var curPoint = this._chainPoint[this.currentIndex]; |
| |
| this._handleShape.style.text = cpStyle.label.show ? curPoint.name : ''; |
| this._handleShape.style.textFont = curPoint.textFont; |
| |
| this._handleShape.style.n = curPoint.n; |
| if (cpStyle.symbol == 'auto') { |
| this._handleShape.style.iconType = curPoint.symbol != 'none' |
| ? curPoint.symbol : 'diamond'; |
| } |
| else { |
| this._handleShape.style.iconType = cpStyle.symbol; |
| if (cpStyle.symbol.match('star')) { |
| this._handleShape.style.n = (cpStyle.symbol.replace('star','') - 0) || 5; |
| this._handleShape.style.iconType = 'star'; |
| } |
| } |
| |
| var symbolSize; |
| if (cpStyle.symbolSize == 'auto') { |
| symbolSize = curPoint.symbolSize + 2; |
| symbolSize = symbolSize < 5 ? 5 : symbolSize; |
| } |
| else { |
| symbolSize = cpStyle.symbolSize - 0; |
| } |
| |
| this._handleShape.style.color = cpStyle.color == 'auto' |
| ? (curPoint.color |
| ? curPoint.color |
| : timelineOption.controlStyle.emphasis.color |
| ) |
| : cpStyle.color; |
| this._handleShape.style.textColor = cpStyle.label.textStyle.color == 'auto' |
| ? this._handleShape.style.color |
| : cpStyle.label.textStyle.color; |
| this._handleShape.highlightStyle.strokeColor = |
| this._handleShape.style.strokeColor = cpStyle.borderColor == 'auto' |
| ? (curPoint.borderColor ? curPoint.borderColor : '#fff') |
| : cpStyle.borderColor; |
| this._handleShape.style.lineWidth = cpStyle.borderWidth == 'auto' |
| ? (curPoint.borderWidth ? curPoint.borderWidth : 0) |
| : (cpStyle.borderWidth - 0); |
| this._handleShape.highlightStyle.lineWidth = this._handleShape.style.lineWidth + 1; |
| |
| this.zr.animate(this._handleShape.id, 'style') |
| .when( |
| 500, |
| { |
| x : curPoint.x - symbolSize, |
| textX : curPoint.x, |
| y : this._location.y + this._location.height / 4 - symbolSize, |
| width : symbolSize * 2, |
| height : symbolSize * 2 |
| } |
| ) |
| .start('ExponentialOut'); |
| }, |
| |
| _findChainIndex : function(x) { |
| var chainPoint = this._chainPoint; |
| var len = chainPoint.length; |
| if (x <= chainPoint[0].x) { |
| return 0; |
| } |
| else if (x >= chainPoint[len - 1].x) { |
| return len - 1; |
| } |
| for (var i = 0; i < len - 1; i++) { |
| if (x >= chainPoint[i].x && x <= chainPoint[i + 1].x) { |
| // catch you! |
| return (Math.abs(x - chainPoint[i].x) < Math.abs(x - chainPoint[i + 1].x)) |
| ? i : (i + 1); |
| } |
| } |
| }, |
| |
| __onclick : function(param) { |
| var x = zrEvent.getX(param.event); |
| var newIndex = this._findChainIndex(x); |
| if (newIndex == this.currentIndex) { |
| return true; // 啥事都没发生 |
| } |
| |
| this.currentIndex = newIndex; |
| this.timelineOption.autoPlay && this.stop(); // 停止自动播放 |
| clearTimeout(this.playTicket); |
| this._onFrame(); |
| }, |
| |
| /** |
| * 拖拽范围控制 |
| */ |
| __ondrift : function (shape, dx) { |
| this.timelineOption.autoPlay && this.stop(); // 停止自动播放 |
| |
| var chainPoint = this._chainPoint; |
| var len = chainPoint.length; |
| var newIndex; |
| if (shape.style.x + dx <= chainPoint[0].x - chainPoint[0].symbolSize) { |
| shape.style.x = chainPoint[0].x - chainPoint[0].symbolSize; |
| newIndex = 0; |
| } |
| else if (shape.style.x + dx >= chainPoint[len - 1].x - chainPoint[len - 1].symbolSize) { |
| shape.style.x = chainPoint[len - 1].x - chainPoint[len - 1].symbolSize; |
| newIndex = len - 1; |
| } |
| else { |
| shape.style.x += dx; |
| newIndex = this._findChainIndex(shape.style.x); |
| } |
| var curPoint = chainPoint[newIndex]; |
| var symbolSize = curPoint.symbolSize + 2; |
| shape.style.iconType = curPoint.symbol; |
| shape.style.n = curPoint.n; |
| shape.style.textX = shape.style.x + symbolSize / 2; |
| shape.style.y = this._location.y + this._location.height / 4 - symbolSize; |
| shape.style.width = symbolSize * 2; |
| shape.style.height = symbolSize * 2; |
| shape.style.text = curPoint.name; |
| |
| //console.log(newIndex) |
| if (newIndex == this.currentIndex) { |
| return true; // 啥事都没发生 |
| } |
| |
| this.currentIndex = newIndex; |
| if (this.timelineOption.realtime) { |
| clearTimeout(this.playTicket); |
| var self = this; |
| this.playTicket = setTimeout(function() { |
| self._setCurrentOption(); |
| },200); |
| } |
| |
| return true; |
| }, |
| |
| __ondragend : function () { |
| this.isDragend = true; |
| }, |
| |
| /** |
| * 数据项被拖拽出去 |
| */ |
| ondragend : function (param, status) { |
| if (!this.isDragend || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| !this.timelineOption.realtime && this._setCurrentOption(); |
| |
| // 别status = {}赋值啊!! |
| status.dragOut = true; |
| status.dragIn = true; |
| status.needRefresh = false; // 会有消息触发fresh,不用再刷一遍 |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| this._syncHandleShape(); |
| return; |
| }, |
| |
| last : function () { |
| this.timelineOption.autoPlay && this.stop(); // 停止自动播放 |
| |
| this.currentIndex -= 1; |
| if (this.currentIndex < 0) { |
| this.currentIndex = this.timelineOption.data.length - 1; |
| } |
| this._onFrame(); |
| |
| return this.currentIndex; |
| }, |
| |
| next : function () { |
| this.timelineOption.autoPlay && this.stop(); // 停止自动播放 |
| |
| this.currentIndex += 1; |
| if (this.currentIndex >= this.timelineOption.data.length) { |
| this.currentIndex = 0; |
| } |
| this._onFrame(); |
| |
| return this.currentIndex; |
| }, |
| |
| play : function (targetIndex, autoPlay) { |
| if (this._ctrPlayShape && this._ctrPlayShape.style.status != 'playing') { |
| this._ctrPlayShape.style.status = 'playing'; |
| this.zr.modShape(this._ctrPlayShape.id); |
| this.zr.refresh(); |
| } |
| |
| |
| this.timelineOption.autoPlay = typeof autoPlay != 'undefined' |
| ? autoPlay : true; |
| |
| if (!this.timelineOption.autoPlay) { |
| clearTimeout(this.playTicket); |
| } |
| |
| this.currentIndex = typeof targetIndex != 'undefined' |
| ? targetIndex : (this.currentIndex + 1); |
| if (this.currentIndex >= this.timelineOption.data.length) { |
| this.currentIndex = 0; |
| } |
| this._onFrame(); |
| |
| return this.currentIndex; |
| }, |
| |
| stop : function () { |
| if (this._ctrPlayShape && this._ctrPlayShape.style.status != 'stop') { |
| this._ctrPlayShape.style.status = 'stop'; |
| this.zr.modShape(this._ctrPlayShape.id); |
| this.zr.refresh(); |
| } |
| |
| this.timelineOption.autoPlay = false; |
| |
| clearTimeout(this.playTicket); |
| |
| return this.currentIndex; |
| }, |
| |
| /** |
| * 避免dataZoom带来两次refresh,不设refresh接口,resize重复一下buildshape逻辑 |
| */ |
| resize : function () { |
| if (this.timelineOption.show) { |
| this.clear(); |
| this._buildShape(); |
| this._syncHandleShape(); |
| } |
| }, |
| |
| setTheme : function(needRefresh) { |
| this.timelineOption = this.reformOption(zrUtil.clone(this.option.timeline)); |
| // 补全padding属性 |
| this.timelineOption.padding = this.reformCssArray( |
| this.timelineOption.padding |
| ); |
| // 通用字体设置 |
| this.timelineOption.label.textStyle = zrUtil.merge( |
| this.timelineOption.label.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| this.timelineOption.checkpointStyle.label.textStyle = zrUtil.merge( |
| this.timelineOption.checkpointStyle.label.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| |
| if (this.timelineOption.show && needRefresh) { |
| this.clear(); |
| this._buildShape(); |
| this._syncHandleShape(); |
| } |
| }, |
| |
| /** |
| * 释放后实例不可用,重载基类方法 |
| */ |
| dispose : function () { |
| this.clear(); |
| this.shapeList = null; |
| |
| clearTimeout(this.playTicket); |
| } |
| }; |
| |
| function timelineControl(ctx, style) { |
| var lineWidth = 2;//style.lineWidth; |
| var x = style.x + lineWidth; |
| var y = style.y + lineWidth + 2; |
| var width = style.width - lineWidth; |
| var height = style.height - lineWidth; |
| |
| |
| var symbol = style.symbol; |
| if (symbol == 'last') { |
| ctx.moveTo(x + width - 2, y + height / 3); |
| ctx.lineTo(x + width - 2, y); |
| ctx.lineTo(x + 2, y + height / 2); |
| ctx.lineTo(x + width - 2, y + height); |
| ctx.lineTo(x + width - 2, y + height / 3 * 2); |
| ctx.moveTo(x, y); |
| ctx.lineTo(x, y); |
| } |
| else if (symbol == 'next') { |
| ctx.moveTo(x + 2, y + height / 3); |
| ctx.lineTo(x + 2, y); |
| ctx.lineTo(x + width - 2, y + height / 2); |
| ctx.lineTo(x + 2, y + height); |
| ctx.lineTo(x + 2, y + height / 3 * 2); |
| ctx.moveTo(x, y); |
| ctx.lineTo(x, y); |
| } |
| else if (symbol == 'play') { |
| if (style.status == 'stop') { |
| ctx.moveTo(x + 2, y); |
| ctx.lineTo(x + width - 2, y + height / 2); |
| ctx.lineTo(x + 2, y + height); |
| ctx.lineTo(x + 2, y); |
| } |
| else { |
| var delta = style.brushType == 'both' ? 2 : 3; |
| ctx.rect(x + 2, y, delta, height); |
| ctx.rect(x + width - delta - 2, y, delta, height); |
| } |
| } |
| else if (symbol.match('image')) { |
| var imageLocation = ''; |
| imageLocation = symbol.replace( |
| new RegExp('^image:\\/\\/'), '' |
| ); |
| symbol = IconShape.prototype.iconLibrary.image; |
| symbol(ctx, { |
| x : x, |
| y : y, |
| width : width, |
| height : height, |
| image : imageLocation |
| }); |
| } |
| } |
| IconShape.prototype.iconLibrary['timelineControl'] = timelineControl; |
| |
| zrUtil.inherits(Timeline, Base); |
| |
| require('../component').define('timeline', Timeline); |
| |
| return Timeline; |
| }); |
| |
| define( |
| 'zrender/loadingEffect/Bar',['require','./Base','../tool/util','../tool/color','../shape/Rectangle'],function (require) { |
| var Base = require('./Base'); |
| var util = require('../tool/util'); |
| var zrColor = require('../tool/color'); |
| var RectangleShape = require('../shape/Rectangle'); |
| |
| function Bar(options) { |
| Base.call(this, options); |
| } |
| util.inherits(Bar, Base); |
| |
| |
| /** |
| * 进度条 |
| * |
| * @param {Object} addShapeHandle |
| * @param {Object} refreshHandle |
| */ |
| Bar.prototype._start = function (addShapeHandle, refreshHandle) { |
| // 特效默认配置 |
| var options = util.merge( |
| this.options, |
| { |
| textStyle : { |
| color : '#888' |
| }, |
| backgroundColor : 'rgba(250, 250, 250, 0.8)', |
| effectOption : { |
| x : 0, |
| y : this.canvasHeight / 2 - 30, |
| width : this.canvasWidth, |
| height : 5, |
| brushType : 'fill', |
| timeInterval : 100 |
| } |
| } |
| ); |
| |
| var textShape = this.createTextShape(options.textStyle); |
| var background = this.createBackgroundShape(options.backgroundColor); |
| |
| var effectOption = options.effectOption; |
| |
| // 初始化动画元素 |
| var barShape = new RectangleShape({ |
| highlightStyle : util.clone(effectOption) |
| }); |
| |
| barShape.highlightStyle.color = |
| effectOption.color |
| || zrColor.getLinearGradient( |
| effectOption.x, |
| effectOption.y, |
| effectOption.x + effectOption.width, |
| effectOption.y + effectOption.height, |
| [[0, '#ff6400'], [0.5, '#ffe100'], [1, '#b1ff00']] |
| ); |
| |
| if (options.progress != null) { |
| // 指定进度 |
| addShapeHandle(background); |
| |
| barShape.highlightStyle.width = |
| this.adjust(options.progress, [0,1]) |
| * options.effectOption.width; |
| |
| addShapeHandle(barShape); |
| addShapeHandle(textShape); |
| |
| refreshHandle(); |
| return; |
| } |
| else { |
| // 循环显示 |
| barShape.highlightStyle.width = 0; |
| return setInterval( |
| function() { |
| addShapeHandle(background); |
| |
| if (barShape.highlightStyle.width < effectOption.width) { |
| barShape.highlightStyle.width += 8; |
| } |
| else { |
| barShape.highlightStyle.width = 0; |
| } |
| addShapeHandle(barShape); |
| addShapeHandle(textShape); |
| refreshHandle(); |
| }, |
| effectOption.timeInterval |
| ); |
| } |
| }; |
| |
| return Bar; |
| } |
| ); |
| |
| |
| define( |
| 'zrender/loadingEffect/Bubble',['require','./Base','../tool/util','../tool/color','../shape/Circle'],function (require) { |
| var Base = require('./Base'); |
| var util = require('../tool/util'); |
| var zrColor = require('../tool/color'); |
| var CircleShape = require('../shape/Circle'); |
| |
| function Bubble(options) { |
| Base.call(this, options); |
| } |
| util.inherits(Bubble, Base); |
| |
| /** |
| * 泡泡 |
| * |
| * @param {Object} addShapeHandle |
| * @param {Object} refreshHandle |
| */ |
| Bubble.prototype._start = function (addShapeHandle, refreshHandle) { |
| |
| // 特效默认配置 |
| var options = util.merge( |
| this.options, |
| { |
| textStyle : { |
| color : '#888' |
| }, |
| backgroundColor : 'rgba(250, 250, 250, 0.8)', |
| effect : { |
| n : 50, |
| lineWidth : 2, |
| brushType : 'stroke', |
| color : 'random', |
| timeInterval : 100 |
| } |
| } |
| ); |
| |
| var textShape = this.createTextShape(options.textStyle); |
| var background = this.createBackgroundShape(options.backgroundColor); |
| |
| var effectOption = options.effect; |
| var n = effectOption.n; |
| var brushType = effectOption.brushType; |
| var lineWidth = effectOption.lineWidth; |
| |
| var shapeList = []; |
| var canvasWidth = this.canvasWidth; |
| var canvasHeight = this.canvasHeight; |
| |
| // 初始化动画元素 |
| for(var i = 0; i < n; i++) { |
| var color = effectOption.color == 'random' |
| ? zrColor.alpha(zrColor.random(), 0.3) |
| : effectOption.color; |
| |
| shapeList[i] = new CircleShape({ |
| highlightStyle : { |
| x : Math.ceil(Math.random() * canvasWidth), |
| y : Math.ceil(Math.random() * canvasHeight), |
| r : Math.ceil(Math.random() * 40), |
| brushType : brushType, |
| color : color, |
| strokeColor : color, |
| lineWidth : lineWidth |
| }, |
| animationY : Math.ceil(Math.random() * 20) |
| }); |
| } |
| |
| return setInterval( |
| function () { |
| addShapeHandle(background); |
| |
| for(var i = 0; i < n; i++) { |
| var style = shapeList[i].highlightStyle; |
| |
| if (style.y - shapeList[i].animationY + style.r <= 0){ |
| shapeList[i].highlightStyle.y = canvasHeight + style.r; |
| shapeList[i].highlightStyle.x = Math.ceil( |
| Math.random() * canvasWidth |
| ); |
| } |
| shapeList[i].highlightStyle.y -= |
| shapeList[i].animationY; |
| |
| addShapeHandle(shapeList[i]); |
| } |
| |
| addShapeHandle(textShape); |
| refreshHandle(); |
| }, |
| effectOption.timeInterval |
| ); |
| }; |
| |
| return Bubble; |
| } |
| ); |
| |
| |
| define( |
| 'zrender/loadingEffect/DynamicLine',['require','./Base','../tool/util','../tool/color','../shape/Line'],function (require) { |
| var Base = require('./Base'); |
| var util = require('../tool/util'); |
| var zrColor = require('../tool/color'); |
| var LineShape = require('../shape/Line'); |
| |
| function DynamicLine(options) { |
| Base.call(this, options); |
| } |
| util.inherits(DynamicLine, Base); |
| |
| |
| /** |
| * 动态线 |
| * |
| * @param {Object} addShapeHandle |
| * @param {Object} refreshHandle |
| */ |
| DynamicLine.prototype._start = function (addShapeHandle, refreshHandle) { |
| // 特效默认配置 |
| var options = util.merge( |
| this.options, |
| { |
| textStyle : { |
| color : '#fff' |
| }, |
| backgroundColor : 'rgba(0, 0, 0, 0.8)', |
| effectOption : { |
| n : 30, |
| lineWidth : 1, |
| color : 'random', |
| timeInterval : 100 |
| } |
| } |
| ); |
| |
| var textShape = this.createTextShape(options.textStyle); |
| var background = this.createBackgroundShape(options.backgroundColor); |
| |
| var effectOption = options.effectOption; |
| var n = effectOption.n; |
| var lineWidth = effectOption.lineWidth; |
| |
| var shapeList = []; |
| var canvasWidth = this.canvasWidth; |
| var canvasHeight = this.canvasHeight; |
| |
| // 初始化动画元素 |
| for(var i = 0; i < n; i++) { |
| var xStart = -Math.ceil(Math.random() * 1000); |
| var len = Math.ceil(Math.random() * 400); |
| var pos = Math.ceil(Math.random() * canvasHeight); |
| |
| var color = effectOption.color == 'random' |
| ? zrColor.random() |
| : effectOption.color; |
| |
| shapeList[i] = new LineShape({ |
| highlightStyle : { |
| xStart : xStart, |
| yStart : pos, |
| xEnd : xStart + len, |
| yEnd : pos, |
| strokeColor : color, |
| lineWidth : lineWidth |
| }, |
| animationX : Math.ceil(Math.random() * 100), |
| len : len |
| }); |
| } |
| |
| return setInterval( |
| function() { |
| addShapeHandle(background); |
| |
| for(var i = 0; i < n; i++) { |
| var style = shapeList[i].highlightStyle; |
| |
| if (style.xStart >= canvasWidth){ |
| |
| shapeList[i].len = Math.ceil(Math.random() * 400); |
| style.xStart = -400; |
| style.xEnd = -400 + shapeList[i].len; |
| style.yStart = Math.ceil(Math.random() * canvasHeight); |
| style.yEnd = style.yStart; |
| } |
| |
| style.xStart += shapeList[i].animationX; |
| style.xEnd += shapeList[i].animationX; |
| |
| addShapeHandle(shapeList[i]); |
| } |
| |
| addShapeHandle(textShape); |
| refreshHandle(); |
| }, |
| effectOption.timeInterval |
| ); |
| }; |
| |
| return DynamicLine; |
| } |
| ); |
| |
| |
| define( |
| 'zrender/loadingEffect/Ring',['require','./Base','../tool/util','../tool/color','../shape/Ring','../shape/Sector'],function (require) { |
| var Base = require('./Base'); |
| var util = require('../tool/util'); |
| var zrColor = require('../tool/color'); |
| var RingShape = require('../shape/Ring'); |
| var SectorShape = require('../shape/Sector'); |
| |
| function Ring(options) { |
| Base.call(this, options); |
| } |
| util.inherits(Ring, Base); |
| |
| |
| /** |
| * 圆环 |
| * |
| * @param {Object} addShapeHandle |
| * @param {Object} refreshHandle |
| */ |
| Ring.prototype._start = function (addShapeHandle, refreshHandle) { |
| |
| // 特效默认配置 |
| var options = util.merge( |
| this.options, |
| { |
| textStyle : { |
| color : '#07a' |
| }, |
| backgroundColor : 'rgba(250, 250, 250, 0.8)', |
| effect : { |
| x : this.canvasWidth / 2, |
| y : this.canvasHeight / 2, |
| r0 : 60, |
| r : 100, |
| color : '#bbdcff', |
| brushType: 'fill', |
| textPosition : 'inside', |
| textFont : 'normal 30px verdana', |
| textColor : 'rgba(30, 144, 255, 0.6)', |
| timeInterval : 100 |
| } |
| } |
| ); |
| |
| var effectOption = options.effect; |
| |
| var textStyle = options.textStyle; |
| if (textStyle.x == null) { |
| textStyle.x = effectOption.x; |
| } |
| if (textStyle.y == null) { |
| textStyle.y = (effectOption.y + (effectOption.r0 + effectOption.r) / 2 - 5); |
| } |
| |
| var textShape = this.createTextShape(options.textStyle); |
| var background = this.createBackgroundShape(options.backgroundColor); |
| |
| var x = effectOption.x; |
| var y = effectOption.y; |
| var r0 = effectOption.r0 + 6; |
| var r = effectOption.r - 6; |
| var color = effectOption.color; |
| var darkColor = zrColor.lift(color, 0.1); |
| |
| var shapeRing = new RingShape({ |
| highlightStyle : util.clone(effectOption) |
| }); |
| |
| // 初始化动画元素 |
| var shapeList = []; |
| var clolrList = zrColor.getGradientColors( |
| ['#ff6400', '#ffe100', '#97ff00'], 25 |
| ); |
| var preAngle = 15; |
| var endAngle = 240; |
| |
| for(var i = 0; i < 16; i++) { |
| shapeList.push(new SectorShape({ |
| highlightStyle : { |
| x : x, |
| y : y, |
| r0 : r0, |
| r : r, |
| startAngle : endAngle - preAngle, |
| endAngle : endAngle, |
| brushType: 'fill', |
| color : darkColor |
| }, |
| _color : zrColor.getLinearGradient( |
| x + r0 * Math.cos(endAngle, true), |
| y - r0 * Math.sin(endAngle, true), |
| x + r0 * Math.cos(endAngle - preAngle, true), |
| y - r0 * Math.sin(endAngle - preAngle, true), |
| [ |
| [0, clolrList[i * 2]], |
| [1, clolrList[i * 2 + 1]] |
| ] |
| ) |
| })); |
| endAngle -= preAngle; |
| } |
| endAngle = 360; |
| for(var i = 0; i < 4; i++) { |
| shapeList.push(new SectorShape({ |
| highlightStyle : { |
| x : x, |
| y : y, |
| r0 : r0, |
| r : r, |
| startAngle : endAngle - preAngle, |
| endAngle : endAngle, |
| brushType: 'fill', |
| color : darkColor |
| }, |
| _color : zrColor.getLinearGradient( |
| x + r0 * Math.cos(endAngle, true), |
| y - r0 * Math.sin(endAngle, true), |
| x + r0 * Math.cos(endAngle - preAngle, true), |
| y - r0 * Math.sin(endAngle - preAngle, true), |
| [ |
| [0, clolrList[i * 2 + 32]], |
| [1, clolrList[i * 2 + 33]] |
| ] |
| ) |
| })); |
| endAngle -= preAngle; |
| } |
| |
| var n = 0; |
| if (options.progress != null) { |
| // 指定进度 |
| addShapeHandle(background); |
| |
| n = this.adjust(options.progress, [0,1]).toFixed(2) * 100 / 5; |
| shapeRing.highlightStyle.text = n * 5 + '%'; |
| addShapeHandle(shapeRing); |
| |
| for(var i = 0; i < 20; i++) { |
| shapeList[i].highlightStyle.color = i < n |
| ? shapeList[i]._color : darkColor; |
| addShapeHandle(shapeList[i]); |
| } |
| |
| addShapeHandle(textShape); |
| refreshHandle(); |
| return; |
| } |
| |
| // 循环显示 |
| return setInterval( |
| function() { |
| addShapeHandle(background); |
| |
| n += n >= 20 ? -20 : 1; |
| |
| //shapeRing.highlightStyle.text = n * 5 + '%'; |
| addShapeHandle(shapeRing); |
| |
| for(var i = 0; i < 20; i++) { |
| shapeList[i].highlightStyle.color = i < n |
| ? shapeList[i]._color : darkColor; |
| addShapeHandle(shapeList[i]); |
| } |
| |
| addShapeHandle(textShape); |
| refreshHandle(); |
| }, |
| effectOption.timeInterval |
| ); |
| }; |
| |
| return Ring; |
| } |
| ); |
| |
| |
| define( |
| 'zrender/loadingEffect/Spin',['require','./Base','../tool/util','../tool/color','../shape/Sector'],function (require) { |
| var Base = require('./Base'); |
| var util = require('../tool/util'); |
| var zrColor = require('../tool/color'); |
| var SectorShape = require('../shape/Sector'); |
| |
| function Spin(options) { |
| Base.call(this, options); |
| } |
| util.inherits(Spin, Base); |
| |
| /** |
| * 旋转 |
| * |
| * @param {Object} addShapeHandle |
| * @param {Object} refreshHandle |
| */ |
| Spin.prototype._start = function (addShapeHandle, refreshHandle) { |
| // 特效默认配置 |
| var effectOption = util.merge( |
| this.options.effect || {}, |
| { |
| x : this.canvasWidth / 2 - 80, |
| y : this.canvasHeight / 2, |
| r0 : 9, |
| r : 15, |
| n : 18, |
| color : '#fff', |
| timeInterval : 100 |
| } |
| ); |
| |
| var options = util.merge( |
| this.options, |
| { |
| textStyle : { |
| color : '#fff', |
| x : effectOption.x + effectOption.r + 10, |
| y : effectOption.y, |
| textAlign : 'start' |
| }, |
| backgroundColor : 'rgba(0, 0, 0, 0.8)' |
| } |
| ); |
| |
| var textShape = this.createTextShape(options.textStyle); |
| var background = this.createBackgroundShape(options.backgroundColor); |
| |
| var n = effectOption.n; |
| var x = effectOption.x; |
| var y = effectOption.y; |
| var r0 = effectOption.r0; |
| var r = effectOption.r; |
| var color = effectOption.color; |
| |
| // 初始化动画元素 |
| var shapeList = []; |
| var preAngle = Math.round(180 / n); |
| for(var i = 0; i < n; i++) { |
| shapeList[i] = new SectorShape({ |
| highlightStyle : { |
| x : x, |
| y : y, |
| r0 : r0, |
| r : r, |
| startAngle : preAngle * i * 2, |
| endAngle : preAngle * i * 2 + preAngle, |
| color : zrColor.alpha(color, (i + 1) / n), |
| brushType: 'fill' |
| } |
| }); |
| } |
| |
| var pos = [0, x, y]; |
| |
| return setInterval( |
| function() { |
| addShapeHandle(background); |
| pos[0] -= 0.3; |
| for(var i = 0; i < n; i++) { |
| shapeList[i].rotation = pos; |
| addShapeHandle(shapeList[i]); |
| } |
| |
| addShapeHandle(textShape); |
| refreshHandle(); |
| }, |
| effectOption.timeInterval |
| ); |
| }; |
| |
| return Spin; |
| } |
| ); |
| |
| |
| define( |
| 'zrender/loadingEffect/Whirling',['require','./Base','../tool/util','../shape/Ring','../shape/Droplet','../shape/Circle'],function (require) { |
| var Base = require('./Base'); |
| var util = require('../tool/util'); |
| var RingShape = require('../shape/Ring'); |
| var DropletShape = require('../shape/Droplet'); |
| var CircleShape = require('../shape/Circle'); |
| |
| function Whirling(options) { |
| Base.call(this, options); |
| } |
| util.inherits(Whirling, Base); |
| |
| /** |
| * 旋转水滴 |
| * |
| * @param {Object} addShapeHandle |
| * @param {Object} refreshHandle |
| */ |
| Whirling.prototype._start = function (addShapeHandle, refreshHandle) { |
| // 特效默认配置 |
| var effectOption = util.merge( |
| this.options.effect || {}, |
| { |
| x : this.canvasWidth / 2 - 80, |
| y : this.canvasHeight / 2, |
| r : 18, |
| colorIn : '#fff', |
| colorOut : '#555', |
| colorWhirl : '#6cf', |
| timeInterval : 50 |
| } |
| ); |
| |
| var options = util.merge( |
| this.options, |
| { |
| textStyle : { |
| color : '#888', |
| x : effectOption.x + effectOption.r + 10, |
| y : effectOption.y, |
| textAlign : 'start' |
| }, |
| backgroundColor : 'rgba(250, 250, 250, 0.8)' |
| } |
| ); |
| |
| var textShape = this.createTextShape(options.textStyle); |
| var background = this.createBackgroundShape(options.backgroundColor); |
| |
| // 初始化动画元素 |
| var droplet = new DropletShape({ |
| highlightStyle : { |
| a : Math.round(effectOption.r / 2), |
| b : Math.round(effectOption.r - effectOption.r / 6), |
| brushType : 'fill', |
| color : effectOption.colorWhirl |
| } |
| }); |
| var circleIn = new CircleShape({ |
| highlightStyle : { |
| r : Math.round(effectOption.r / 6), |
| brushType : 'fill', |
| color : effectOption.colorIn |
| } |
| }); |
| var circleOut = new RingShape({ |
| highlightStyle : { |
| r0 : Math.round(effectOption.r - effectOption.r / 3), |
| r : effectOption.r, |
| brushType : 'fill', |
| color : effectOption.colorOut |
| } |
| }); |
| |
| var pos = [0, effectOption.x, effectOption.y]; |
| |
| droplet.highlightStyle.x |
| = circleIn.highlightStyle.x |
| = circleOut.highlightStyle.x |
| = pos[1]; |
| droplet.highlightStyle.y |
| = circleIn.highlightStyle.y |
| = circleOut.highlightStyle.y |
| = pos[2]; |
| |
| return setInterval( |
| function() { |
| addShapeHandle(background); |
| addShapeHandle(circleOut); |
| pos[0] -= 0.3; |
| droplet.rotation = pos; |
| addShapeHandle(droplet); |
| addShapeHandle(circleIn); |
| addShapeHandle(textShape); |
| refreshHandle(); |
| }, |
| effectOption.timeInterval |
| ); |
| }; |
| |
| return Whirling; |
| } |
| ); |
| |
| /** |
| * echarts默认主题,开发中 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/theme/default',[],function() { |
| var config = { |
| }; |
| |
| return config; |
| }); |
| /*! |
| * ECharts, a javascript interactive chart library. |
| * |
| * Copyright (c) 2014, Baidu Inc. |
| * All rights reserved. |
| * |
| * LICENSE |
| * https://github.com/ecomfe/echarts/blob/master/LICENSE.txt |
| */ |
| |
| /** |
| * echarts |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/echarts',['require','./config','zrender/tool/util','zrender/tool/event','zrender/tool/env','zrender','zrender/config','zrender','./chart/island','./component/toolbox','./component','./component/title','./component/tooltip','./component/legend','./util/ecData','./chart','./component','zrender/tool/color','./component/timeline','zrender','zrender/shape/Image','zrender/loadingEffect/Bar','zrender/loadingEffect/Bubble','zrender/loadingEffect/DynamicLine','zrender/loadingEffect/Ring','zrender/loadingEffect/Spin','zrender/loadingEffect/Whirling','./theme/default'],function (require) { |
| var ecConfig = require('./config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrEvent = require('zrender/tool/event'); |
| |
| var self = {}; |
| |
| var _canvasSupported = require('zrender/tool/env').canvasSupported; |
| var _idBase = new Date() - 0; |
| var _instances = {}; // ECharts实例map索引 |
| var DOM_ATTRIBUTE_KEY = '_echarts_instance_'; |
| |
| self.version = '2.0.1'; |
| self.dependencies = { |
| zrender : '2.0.1' |
| }; |
| /** |
| * 入口方法 |
| */ |
| self.init = function (dom, theme) { |
| var zrender = require('zrender'); |
| if (((zrender.version || '1.0.3').replace('.', '') - 0) |
| < (self.dependencies.zrender.replace('.', '') - 0) |
| ) { |
| console.error( |
| 'ZRender ' + (zrender.version || '1.0.3-') |
| + ' is too old for ECharts ' + self.version |
| + '. Current version need ZRender ' |
| + self.dependencies.zrender + '+' |
| ); |
| } |
| |
| dom = dom instanceof Array ? dom[0] : dom; |
| |
| // dom与echarts实例映射索引 |
| var key = dom.getAttribute(DOM_ATTRIBUTE_KEY); |
| if (!key) { |
| key = _idBase++; |
| dom.setAttribute(DOM_ATTRIBUTE_KEY, key); |
| } |
| |
| if (_instances[key]) { |
| // 同一个dom上多次init,自动释放已有实例 |
| _instances[key].dispose(); |
| } |
| _instances[key] = new Echarts(dom); |
| _instances[key].id = key; |
| _instances[key].setTheme(theme); |
| |
| return _instances[key]; |
| }; |
| |
| /** |
| * 通过id获得ECharts实例,id可在实例化后读取 |
| */ |
| self.getInstanceById = function (key) { |
| return _instances[key]; |
| }; |
| |
| /** |
| * 消息中心 |
| */ |
| function MessageCenter() { |
| zrEvent.Dispatcher.call(this); |
| } |
| zrUtil.merge(MessageCenter.prototype, zrEvent.Dispatcher.prototype, true); |
| |
| /** |
| * 基于zrender实现Echarts接口层 |
| * @param {HtmlElement} dom 必要 |
| */ |
| function Echarts(dom) { |
| this._themeConfig = zrUtil.clone(ecConfig); |
| |
| this.dom = dom; |
| // this._zr; |
| // this._option; // curOption clone |
| // this._optionRestore; // for restore; |
| // this._island; |
| // this._toolbox; |
| // this._timeline; |
| // this._refreshInside; // 内部刷新标志位 |
| |
| this._connected = false; |
| this._status = { // 用于图表间通信 |
| dragIn : false, |
| dragOut : false, |
| needRefresh : false |
| }; |
| this._curEventType = false; // 破循环信号灯 |
| this._chartList = []; // 图表实例 |
| |
| this._messageCenter = new MessageCenter(); |
| |
| this._messageCenterOutSide = new MessageCenter(); // Echarts层的外部消息中心,做Echarts层的消息转发 |
| |
| // resize方法经常被绑定到window.resize上,闭包一个this |
| this.resize = this.resize(); |
| |
| // 初始化::构造函数 |
| this._init(); |
| } |
| |
| /** |
| * ZRender EVENT |
| * |
| * @inner |
| * @const |
| * @type {Object} |
| */ |
| var ZR_EVENT = require('zrender/config').EVENT; |
| |
| /** |
| * 要绑定监听的zrender事件列表 |
| * |
| * @const |
| * @inner |
| * @type {Array} |
| */ |
| var ZR_EVENT_LISTENS = [ |
| 'CLICK', 'MOUSEOVER', |
| 'DRAGSTART', 'DRAGEND', 'DRAGENTER', 'DRAGOVER', 'DRAGLEAVE', 'DROP' |
| ]; |
| |
| /** |
| * 对echarts的实例中的chartList属性成员,逐个进行方法调用,遍历顺序为逆序 |
| * 由于在事件触发的默认行为处理中,多次用到相同逻辑,所以抽象了该方法 |
| * 由于所有的调用场景里,最多只有两个参数,基于性能和体积考虑,这里就不使用call或者apply了 |
| * |
| * @inner |
| * @param {ECharts} ecInstance ECharts实例 |
| * @param {string} methodName 要调用的方法名 |
| * @param {*} arg0 调用参数1 |
| * @param {*} arg1 调用参数2 |
| * @param {*} arg2 调用参数3 |
| */ |
| function callChartListMethodReverse(ecInstance, methodName, arg0, arg1, arg2) { |
| var chartList = ecInstance._chartList; |
| var len = chartList.length; |
| |
| while (len--) { |
| var chart = chartList[len]; |
| if (typeof chart[methodName] === 'function') { |
| chart[methodName](arg0, arg1, arg2); |
| } |
| } |
| } |
| |
| Echarts.prototype = { |
| /** |
| * 初始化::构造函数 |
| */ |
| _init : function () { |
| var self = this; |
| var _zr = require('zrender').init(this.dom); |
| this._zr = _zr; |
| |
| // wrap: n,e,d,t for name event data this |
| this._messageCenter.dispatch = function(type, event, eventPackage, that) { |
| eventPackage = eventPackage || {}; |
| eventPackage.type = type; |
| eventPackage.event = event; |
| |
| self._messageCenter.dispatchWithContext(type, eventPackage, that); |
| if (type != 'HOVER') { |
| setTimeout(function(){ |
| self._messageCenterOutSide.dispatchWithContext( |
| type, eventPackage, that |
| ); |
| },50); |
| } |
| else { |
| self._messageCenterOutSide.dispatchWithContext( |
| type, eventPackage, that |
| ); |
| } |
| }; |
| |
| this._onevent = function(param){ |
| return self.__onevent(param); |
| }; |
| for (var e in ecConfig.EVENT) { |
| if (e != 'CLICK' && e != 'HOVER' && e != 'MAP_ROAM') { |
| this._messageCenter.bind(ecConfig.EVENT[e], this._onevent, this); |
| } |
| } |
| |
| |
| var eventBehaviors = {}; |
| this._onzrevent = function (param) { |
| return self[eventBehaviors[ param.type ]](param); |
| }; |
| |
| // 挂载关心的事件 |
| for (var i = 0, len = ZR_EVENT_LISTENS.length; i < len; i++) { |
| var eventName = ZR_EVENT_LISTENS[i]; |
| var eventValue = ZR_EVENT[eventName]; |
| eventBehaviors[eventValue] = '_on' + eventName.toLowerCase(); |
| _zr.on(eventValue, this._onzrevent); |
| } |
| |
| this.chart = {}; // 图表索引 |
| this.component = {}; // 组件索引 |
| |
| // 内置图表 |
| // 孤岛 |
| var Island = require('./chart/island'); |
| this._island = new Island(this._themeConfig, this._messageCenter, _zr, {}, this); |
| this.chart.island = this._island; |
| |
| // 内置通用组件 |
| // 工具箱 |
| var Toolbox = require('./component/toolbox'); |
| this._toolbox = new Toolbox(this._themeConfig, this._messageCenter, _zr, {}, this); |
| this.component.toolbox = this._toolbox; |
| |
| var componentLibrary = require('./component'); |
| componentLibrary.define('title', require('./component/title')); |
| componentLibrary.define('tooltip', require('./component/tooltip')); |
| componentLibrary.define('legend', require('./component/legend')); |
| }, |
| |
| /** |
| * ECharts事件处理中心 |
| */ |
| __onevent : function (param){ |
| param.__echartsId = param.__echartsId || this.id; |
| |
| // 来自其他联动图表的事件 |
| var fromMyself = (param.__echartsId == this.id); |
| |
| if (!this._curEventType) { |
| this._curEventType = param.type; |
| } |
| |
| switch (param.type) { |
| case ecConfig.EVENT.LEGEND_SELECTED : |
| this._onlegendSelected(param); |
| break; |
| case ecConfig.EVENT.DATA_ZOOM : |
| if (!fromMyself) { |
| var dz = this.component.dataZoom; |
| if (dz) { |
| dz.silence(true); |
| dz.absoluteZoom(param.zoom); |
| dz.silence(false); |
| } |
| } |
| this._ondataZoom(param); |
| break; |
| case ecConfig.EVENT.DATA_RANGE : |
| fromMyself && this._ondataRange(param); |
| break; |
| case ecConfig.EVENT.MAGIC_TYPE_CHANGED : |
| if (!fromMyself) { |
| var tb = this.component.toolbox; |
| if (tb) { |
| tb.silence(true); |
| tb.setMagicType(param.magicType); |
| tb.silence(false); |
| } |
| } |
| this._onmagicTypeChanged(param); |
| break; |
| case ecConfig.EVENT.DATA_VIEW_CHANGED : |
| fromMyself && this._ondataViewChanged(param); |
| break; |
| case ecConfig.EVENT.TOOLTIP_HOVER : |
| fromMyself && this._tooltipHover(param); |
| break; |
| case ecConfig.EVENT.RESTORE : |
| this._onrestore(); |
| break; |
| case ecConfig.EVENT.REFRESH : |
| fromMyself && this._onrefresh(param); |
| break; |
| // 鼠标同步 |
| case ecConfig.EVENT.TOOLTIP_IN_GRID : |
| case ecConfig.EVENT.TOOLTIP_OUT_GRID : |
| if (!fromMyself) { |
| // 只处理来自外部的鼠标同步 |
| var grid = this.component.grid; |
| if (grid) { |
| this._zr.trigger( |
| 'mousemove', |
| { |
| connectTrigger : true, |
| zrenderX : grid.getX() + param.x * grid.getWidth(), |
| zrenderY : grid.getY() + param.y * grid.getHeight() |
| } |
| ); |
| } |
| } |
| else if (this._connected) { |
| // 来自自己,并且存在多图联动,空间坐标映射修改参数分发 |
| var grid = this.component.grid; |
| if (grid) { |
| param.x = (param.event.zrenderX - grid.getX()) / grid.getWidth(); |
| param.y = (param.event.zrenderY - grid.getY()) / grid.getHeight(); |
| } |
| } |
| break; |
| /* |
| case ecConfig.EVENT.RESIZE : |
| case ecConfig.EVENT.DATA_CHANGED : |
| case ecConfig.EVENT.PIE_SELECTED : |
| case ecConfig.EVENT.MAP_SELECTED : |
| break; |
| */ |
| } |
| |
| // 多图联动,只做自己的一级事件分发,避免级联事件循环 |
| if (this._connected && fromMyself && this._curEventType == param.type) { |
| for (var c in this._connected) { |
| this._connected[c].connectedEventHandler(param); |
| } |
| // 分发完毕后复位 |
| this._curEventType = null; |
| } |
| |
| if (!fromMyself || (!this._connected && fromMyself)) { // 处理了完联动事件复位 |
| this._curEventType = null; |
| } |
| }, |
| |
| /** |
| * 点击事件,响应zrender事件,包装后分发到Echarts层 |
| */ |
| _onclick : function (param) { |
| callChartListMethodReverse(this, 'onclick', param); |
| |
| if (param.target) { |
| var ecData = this._eventPackage(param.target); |
| if (ecData && ecData.seriesIndex != null) { |
| this._messageCenter.dispatch( |
| ecConfig.EVENT.CLICK, |
| param.event, |
| ecData, |
| this |
| ); |
| } |
| } |
| }, |
| |
| /** |
| * 鼠标移入事件,响应zrender事件,包装后分发到Echarts层 |
| */ |
| _onmouseover : function (param) { |
| if (param.target) { |
| var ecData = this._eventPackage(param.target); |
| if (ecData && ecData.seriesIndex != null) { |
| this._messageCenter.dispatch( |
| ecConfig.EVENT.HOVER, |
| param.event, |
| ecData, |
| this |
| ); |
| } |
| } |
| }, |
| |
| /** |
| * dragstart回调,可计算特性实现 |
| */ |
| _ondragstart : function (param) { |
| // 复位用于图表间通信拖拽标识 |
| this._status = { |
| dragIn : false, |
| dragOut : false, |
| needRefresh : false |
| }; |
| |
| callChartListMethodReverse(this, 'ondragstart', param); |
| }, |
| |
| /** |
| * dragging回调,可计算特性实现 |
| */ |
| _ondragenter : function (param) { |
| callChartListMethodReverse(this, 'ondragenter', param); |
| }, |
| |
| /** |
| * dragstart回调,可计算特性实现 |
| */ |
| _ondragover : function (param) { |
| callChartListMethodReverse(this, 'ondragover', param); |
| }, |
| |
| /** |
| * dragstart回调,可计算特性实现 |
| */ |
| _ondragleave : function (param) { |
| callChartListMethodReverse(this, 'ondragleave', param); |
| }, |
| |
| /** |
| * dragstart回调,可计算特性实现 |
| */ |
| _ondrop : function (param) { |
| callChartListMethodReverse(this, 'ondrop', param, this._status); |
| this._island.ondrop(param, this._status); |
| }, |
| |
| /** |
| * dragdone回调 ,可计算特性实现 |
| */ |
| _ondragend : function (param) { |
| callChartListMethodReverse(this, 'ondragend', param, this._status); |
| |
| this._timeline && this._timeline.ondragend(param, this._status); |
| this._island.ondragend(param, this._status); |
| |
| // 发生过重计算 |
| if (this._status.needRefresh) { |
| this._syncBackupData(this._option); |
| |
| var messageCenter = this._messageCenter; |
| messageCenter.dispatch( |
| ecConfig.EVENT.DATA_CHANGED, |
| param.event, |
| this._eventPackage(param.target), |
| this |
| ); |
| messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); |
| } |
| }, |
| |
| /** |
| * 图例选择响应 |
| */ |
| _onlegendSelected : function (param) { |
| // 用于图表间通信 |
| this._status.needRefresh = false; |
| callChartListMethodReverse(this, 'onlegendSelected', param, this._status); |
| |
| if (this._status.needRefresh) { |
| this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); |
| } |
| }, |
| |
| /** |
| * 数据区域缩放响应 |
| */ |
| _ondataZoom : function (param) { |
| // 用于图表间通信 |
| this._status.needRefresh = false; |
| callChartListMethodReverse(this, 'ondataZoom', param, this._status); |
| |
| if (this._status.needRefresh) { |
| this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); |
| } |
| }, |
| |
| /** |
| * 值域漫游响应 |
| */ |
| _ondataRange : function (param) { |
| this._clearEffect(); |
| // 用于图表间通信 |
| this._status.needRefresh = false; |
| callChartListMethodReverse(this, 'ondataRange', param, this._status); |
| |
| // 没有相互影响,直接刷新即可 |
| if (this._status.needRefresh) { |
| this._zr.refresh(); |
| } |
| }, |
| |
| /** |
| * 动态类型切换响应 |
| */ |
| _onmagicTypeChanged : function () { |
| this._clearEffect(); |
| this._render(this._toolbox.getMagicOption()); |
| }, |
| |
| /** |
| * 数据视图修改响应 |
| */ |
| _ondataViewChanged : function (param) { |
| this._syncBackupData(param.option); |
| this._messageCenter.dispatch( |
| ecConfig.EVENT.DATA_CHANGED, |
| null, |
| param, |
| this |
| ); |
| this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this); |
| }, |
| |
| /** |
| * tooltip与图表间通信 |
| */ |
| _tooltipHover : function (param) { |
| var tipShape = []; |
| callChartListMethodReverse(this, 'ontooltipHover', param, tipShape); |
| }, |
| |
| /** |
| * 还原 |
| */ |
| _onrestore : function () { |
| this.restore(); |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| _onrefresh : function (param) { |
| this._refreshInside = true; |
| this.refresh(param); |
| this._refreshInside = false; |
| }, |
| |
| /** |
| * 数据修改后的反向同步dataZoom持有的备份数据 |
| */ |
| _syncBackupData : function (curOption) { |
| this.component.dataZoom && this.component.dataZoom.syncBackupData(curOption); |
| }, |
| |
| /** |
| * 打包Echarts层的事件附件 |
| */ |
| _eventPackage : function (target) { |
| if (target) { |
| var ecData = require('./util/ecData'); |
| |
| var seriesIndex = ecData.get(target, 'seriesIndex'); |
| var dataIndex = ecData.get(target, 'dataIndex'); |
| |
| dataIndex = seriesIndex != -1 && this.component.dataZoom |
| ? this.component.dataZoom.getRealDataIndex( |
| seriesIndex, |
| dataIndex |
| ) |
| : dataIndex; |
| return { |
| seriesIndex : seriesIndex, |
| dataIndex : dataIndex, |
| data : ecData.get(target, 'data'), |
| name : ecData.get(target, 'name'), |
| value : ecData.get(target, 'value'), |
| special : ecData.get(target, 'special') |
| }; |
| } |
| return; |
| }, |
| |
| /** |
| * 图表渲染 |
| */ |
| _render : function (magicOption) { |
| this._mergeGlobalConifg(magicOption); |
| |
| var bgColor = magicOption.backgroundColor; |
| if (bgColor) { |
| if (!_canvasSupported |
| && bgColor.indexOf('rgba') != -1 |
| ) { |
| // IE6~8对RGBA的处理,filter会带来其他颜色的影响 |
| var cList = bgColor.split(','); |
| this.dom.style.filter = 'alpha(opacity=' + |
| cList[3].substring(0, cList[3].lastIndexOf(')')) * 100 |
| + ')'; |
| cList.length = 3; |
| cList[0] = cList[0].replace('a', ''); |
| this.dom.style.backgroundColor = cList.join(',') + ')'; |
| } |
| else { |
| this.dom.style.backgroundColor = bgColor; |
| } |
| } |
| |
| this._zr.clearAnimation(); |
| this._chartList = []; |
| |
| var chartLibrary = require('./chart'); |
| var componentLibrary = require('./component'); |
| |
| if (magicOption.xAxis || magicOption.yAxis) { |
| magicOption.grid = magicOption.grid || {}; |
| magicOption.dataZoom = magicOption.dataZoom || {}; |
| } |
| |
| var componentList = [ |
| 'title', 'legend', 'tooltip', 'dataRange', |
| 'grid', 'dataZoom', 'xAxis', 'yAxis', 'polar' |
| ]; |
| |
| var ComponentClass; |
| var componentType; |
| var component; |
| for (var i = 0, l = componentList.length; i < l; i++) { |
| componentType = componentList[i]; |
| component = this.component[componentType]; |
| |
| if (magicOption[componentType]) { |
| if (component) { |
| component.refresh && component.refresh(magicOption); |
| } |
| else { |
| ComponentClass = componentLibrary.get( |
| /^[xy]Axis$/.test(componentType) ? 'axis' : componentType |
| ); |
| component = new ComponentClass( |
| this._themeConfig, this._messageCenter, this._zr, |
| magicOption, this, componentType |
| ); |
| this.component[componentType] = component; |
| } |
| this._chartList.push(component); |
| } |
| else if (component) { |
| component.dispose(); |
| this.component[componentType] = null; |
| delete this.component[componentType]; |
| } |
| } |
| |
| var ChartClass; |
| var chartType; |
| var chart; |
| var chartMap = {}; // 记录已经初始化的图表 |
| for (var i = 0, l = magicOption.series.length; i < l; i++) { |
| chartType = magicOption.series[i].type; |
| if (!chartType) { |
| console.error('series[' + i + '] chart type has not been defined.'); |
| continue; |
| } |
| |
| if (!chartMap[chartType]) { |
| chartMap[chartType] = true; |
| ChartClass = chartLibrary.get(chartType); |
| if (ChartClass) { |
| if (this.chart[chartType]) { |
| chart = this.chart[chartType]; |
| chart.refresh(magicOption); |
| } |
| else { |
| chart = new ChartClass( |
| this._themeConfig, this._messageCenter, this._zr, |
| magicOption, this |
| ); |
| } |
| this._chartList.push(chart); |
| this.chart[chartType] = chart; |
| } |
| else { |
| console.error(chartType + ' has not been required.'); |
| } |
| } |
| } |
| |
| // 已有实例但新option不带这类图表的实例释放 |
| for (chartType in this.chart) { |
| if (chartType != ecConfig.CHART_TYPE_ISLAND && !chartMap[chartType]) { |
| this.chart[chartType].dispose(); |
| this.chart[chartType] = null; |
| delete this.chart[chartType]; |
| } |
| } |
| |
| this.component.grid && this.component.grid.refixAxisShape(this.component); |
| |
| this._island.refresh(magicOption); |
| this._toolbox.refresh(magicOption); |
| |
| magicOption.animation && !magicOption.renderAsImage |
| ? this._zr.refresh() |
| : this._zr.render(); |
| |
| var imgId = 'IMG' + this.id; |
| var img = document.getElementById(imgId); |
| if (magicOption.renderAsImage && _canvasSupported) { |
| // IE8- 不支持图片渲染形式 |
| if (img) { |
| // 已经渲染过则更新显示 |
| img.src = this.getDataURL(magicOption.renderAsImage); |
| } |
| else { |
| // 没有渲染过插入img dom |
| img = this.getImage(magicOption.renderAsImage); |
| img.id = imgId; |
| img.style.position = 'absolute'; |
| img.style.left = 0; |
| img.style.top = 0; |
| this.dom.firstChild.appendChild(img); |
| } |
| this.un(); |
| this._zr.un(); |
| this._disposeChartList(); |
| this._zr.clear(); |
| } |
| else if (img) { |
| // 删除可能存在的img |
| img.parentNode.removeChild(img); |
| } |
| img = null; |
| |
| this._option = magicOption; |
| }, |
| |
| /** |
| * 还原 |
| */ |
| restore : function () { |
| this._clearEffect(); |
| this._option = zrUtil.clone(this._optionRestore); |
| this._disposeChartList(); |
| this._island.clear(); |
| this._toolbox.reset(this._option, true); |
| this._render(this._option); |
| }, |
| |
| /** |
| * 刷新 |
| * @param {Object=} param,可选参数,用于附带option,内部同步用,外部不建议带入数据修改,无法同步 |
| */ |
| refresh : function (param) { |
| this._clearEffect(); |
| param = param || {}; |
| var magicOption = param.option; |
| |
| // 外部调用的refresh且有option带入 |
| if (!this._refreshInside && magicOption) { |
| // 做简单的差异合并去同步内部持有的数据克隆,不建议带入数据 |
| // 开启数据区域缩放、拖拽重计算、数据视图可编辑模式情况下,当用户产生了数据变化后无法同步 |
| // 如有带入option存在数据变化,请重新setOption |
| magicOption = this.getOption(); |
| zrUtil.merge(magicOption, param.option, true); |
| zrUtil.merge(this._optionRestore, param.option, true); |
| this._toolbox.reset(magicOption); |
| } |
| |
| this._island.refresh(magicOption); |
| this._toolbox.refresh(magicOption); |
| |
| // 停止动画 |
| this._zr.clearAnimation(); |
| // 先来后到,安顺序刷新各种图表,图表内部refresh优化检查magicOption,无需更新则不更新~ |
| for (var i = 0, l = this._chartList.length; i < l; i++) { |
| this._chartList[i].refresh && this._chartList[i].refresh(magicOption); |
| } |
| this.component.grid && this.component.grid.refixAxisShape(this.component); |
| this._zr.refresh(); |
| }, |
| |
| /** |
| * 释放图表实例 |
| */ |
| _disposeChartList : function () { |
| this._clearEffect(); |
| |
| // 停止动画 |
| this._zr.clearAnimation(); |
| |
| var len = this._chartList.length; |
| while (len--) { |
| var chart = this._chartList[len]; |
| |
| if (chart) { |
| var chartType = chart.type; |
| this.chart[chartType] && delete this.chart[chartType]; |
| this.component[chartType] && delete this.component[chartType]; |
| chart.dispose && chart.dispose(); |
| } |
| } |
| |
| this._chartList = []; |
| }, |
| |
| /** |
| * 非图表全局属性merge~~ |
| */ |
| _mergeGlobalConifg : function (magicOption) { |
| var mergeList = [ |
| // 背景颜色 |
| 'backgroundColor', |
| |
| // 拖拽重计算相关 |
| 'calculable', 'calculableColor', 'calculableHolderColor', |
| |
| // 孤岛显示连接符 |
| 'nameConnector', 'valueConnector', |
| |
| // 动画相关 |
| 'animation', 'animationThreshold', 'animationDuration', |
| 'animationEasing', 'addDataAnimation', |
| |
| // 默认标志图形类型列表 |
| 'symbolList', |
| |
| // 降低图表内元素拖拽敏感度,单位ms,不建议外部干预 |
| 'DRAG_ENABLE_TIME' |
| ]; |
| |
| var len = mergeList.length; |
| while (len--) { |
| var mergeItem = mergeList[len]; |
| if (magicOption[mergeItem] == null) { |
| magicOption[mergeItem] = this._themeConfig[mergeItem]; |
| } |
| } |
| |
| // 数值系列的颜色列表,不传则采用内置颜色,可配数组,借用zrender实例注入,会有冲突风险,先这样 |
| var themeColor = magicOption.color; |
| if (!(themeColor && themeColor.length)) { |
| themeColor = this._themeConfig.color; |
| } |
| |
| this._zr.getColor = function (idx) { |
| var zrColor = require('zrender/tool/color'); |
| return zrColor.getColor(idx, themeColor); |
| }; |
| }, |
| |
| /** |
| * 万能接口,配置图表实例任何可配置选项,多次调用时option选项做merge处理 |
| * @param {Object} option |
| * @param {boolean=} notMerge 多次调用时option选项是默认是合并(merge)的, |
| * 如果不需求,可以通过notMerger参数为true阻止与上次option的合并 |
| */ |
| setOption : function (option, notMerge) { |
| if (!option.timeline) { |
| return this._setOption(option, notMerge); |
| } |
| else { |
| return this._setTimelineOption(option); |
| } |
| }, |
| |
| /** |
| * 万能接口,配置图表实例任何可配置选项,多次调用时option选项做merge处理 |
| * @param {Object} option |
| * @param {boolean=} notMerge 多次调用时option选项是默认是合并(merge)的, |
| * 如果不需求,可以通过notMerger参数为true阻止与上次option的合并 |
| */ |
| _setOption : function (option, notMerge) { |
| if (!notMerge && this._option) { |
| this._option = zrUtil.merge( |
| this.getOption(), |
| zrUtil.clone(option), |
| true |
| ); |
| } |
| else { |
| this._option = zrUtil.clone(option); |
| } |
| |
| this._optionRestore = zrUtil.clone(this._option); |
| |
| if (!this._option.series || this._option.series.length === 0) { |
| this._zr.clear(); |
| return; |
| } |
| |
| if (this.component.dataZoom // 存在dataZoom控件 |
| && (this._option.dataZoom // 并且新option也存在 |
| || (this._option.toolbox |
| && this._option.toolbox.feature |
| && this._option.toolbox.feature.dataZoom |
| && this._option.toolbox.feature.dataZoom.show |
| ) |
| ) |
| ) { |
| // dataZoom同步数据 |
| this.component.dataZoom.syncOption(this._option); |
| } |
| this._toolbox.reset(this._option); |
| this._render(this._option); |
| return this; |
| }, |
| |
| /** |
| * 返回内部持有的当前显示option克隆 |
| */ |
| getOption : function () { |
| var magicOption = zrUtil.clone(this._option); |
| |
| var self = this; |
| function restoreOption(prop) { |
| var restoreSource = self._optionRestore[prop]; |
| |
| if (restoreSource) { |
| if (restoreSource instanceof Array) { |
| var len = restoreSource.length; |
| while (len--) { |
| magicOption[prop][len].data = zrUtil.clone( |
| restoreSource[len].data |
| ); |
| } |
| } |
| else { |
| magicOption[prop].data = zrUtil.clone(restoreSource.data); |
| } |
| } |
| } |
| |
| // 横轴数据还原 |
| restoreOption('xAxis'); |
| |
| // 纵轴数据还原 |
| restoreOption('yAxis'); |
| |
| // 系列数据还原 |
| restoreOption('series'); |
| |
| return magicOption; |
| }, |
| |
| /** |
| * 数据设置快捷接口 |
| * @param {Array} series |
| * @param {boolean=} notMerge 多次调用时option选项是默认是合并(merge)的, |
| * 如果不需求,可以通过notMerger参数为true阻止与上次option的合并。 |
| */ |
| setSeries : function (series, notMerge) { |
| if (!notMerge) { |
| this.setOption({series: series}); |
| } |
| else { |
| this._option.series = series; |
| this.setOption(this._option, notMerge); |
| } |
| return this; |
| }, |
| |
| /** |
| * 返回内部持有的当前显示series克隆 |
| */ |
| getSeries : function () { |
| return this.getOption().series; |
| }, |
| |
| /** |
| * timelineOption接口,配置图表实例任何可配置选项 |
| * @param {Object} option |
| */ |
| _setTimelineOption : function(option) { |
| this._timeline && this._timeline.dispose(); |
| var Timeline = require('./component/timeline'); |
| var timeline = new Timeline( |
| this._themeConfig, this._messageCenter, this._zr, option, this |
| ); |
| this._timeline = timeline; |
| this.component.timeline = this._timeline; |
| |
| return this; |
| }, |
| |
| /** |
| * 动态数据添加 |
| * 形参为单组数据参数,多组时为数据,内容同[seriesIdx, data, isShift, additionData] |
| * @param {number} seriesIdx 系列索引 |
| * @param {number | Object} data 增加数据 |
| * @param {boolean=} isHead 是否队头加入,默认,不指定或false时为队尾插入 |
| * @param {boolean=} dataGrow 是否增长数据队列长度,默认,不指定或false时移出目标数组对位数据 |
| * @param {string=} additionData 是否增加类目轴(饼图为图例)数据,附加操作同isHead和dataGrow |
| */ |
| addData : function (seriesIdx, data, isHead, dataGrow, additionData) { |
| var params = seriesIdx instanceof Array |
| ? seriesIdx |
| : [[seriesIdx, data, isHead, dataGrow, additionData]]; |
| |
| //this._optionRestore 和 magicOption 都要同步 |
| var magicOption = this.getOption(); |
| var optionRestore = this._optionRestore; |
| for (var i = 0, l = params.length; i < l; i++) { |
| seriesIdx = params[i][0]; |
| data = params[i][1]; |
| isHead = params[i][2]; |
| dataGrow = params[i][3]; |
| additionData = params[i][4]; |
| |
| |
| var seriesItem = optionRestore.series[seriesIdx]; |
| var inMethod = isHead ? 'unshift' : 'push'; |
| var outMethod = isHead ? 'pop' : 'shift'; |
| if (seriesItem) { |
| var seriesItemData = seriesItem.data; |
| var mSeriesItemData = magicOption.series[seriesIdx].data; |
| |
| seriesItemData[inMethod](data); |
| mSeriesItemData[inMethod](data); |
| if (!dataGrow) { |
| seriesItemData[outMethod](); |
| data = mSeriesItemData[outMethod](); |
| } |
| |
| |
| if (additionData != null) { |
| var legend; |
| var legendData; |
| |
| if (seriesItem.type == ecConfig.CHART_TYPE_PIE |
| && (legend = optionRestore.legend) |
| && (legendData = legend.data) |
| ) { |
| var mLegendData = magicOption.legend.data; |
| legendData[inMethod](additionData); |
| mLegendData[inMethod](additionData); |
| |
| if (!dataGrow) { |
| var legendDataIdx = zrUtil.indexOf(legendData, data.name); |
| legendDataIdx != -1 && legendData.splice(legendDataIdx, 1); |
| |
| legendDataIdx = zrUtil.indexOf(mLegendData, data.name); |
| legendDataIdx != -1 && mLegendData.splice(legendDataIdx, 1); |
| } |
| } |
| else if (optionRestore.xAxis != null && optionRestore.yAxis != null) { |
| // x轴类目 |
| var axisData; |
| var mAxisData; |
| var axisIdx = seriesItem.xAxisIndex || 0; |
| |
| if (typeof optionRestore.xAxis[axisIdx].type == 'undefined' |
| || optionRestore.xAxis[axisIdx].type == 'category' |
| ) { |
| axisData = optionRestore.xAxis[axisIdx].data; |
| mAxisData = magicOption.xAxis[axisIdx].data; |
| |
| axisData[inMethod](additionData); |
| mAxisData[inMethod](additionData); |
| if (!dataGrow) { |
| axisData[outMethod](); |
| mAxisData[outMethod](); |
| } |
| } |
| |
| // y轴类目 |
| axisIdx = seriesItem.yAxisIndex || 0; |
| if (optionRestore.yAxis[axisIdx].type == 'category') { |
| axisData = optionRestore.yAxis[axisIdx].data; |
| mAxisData = magicOption.yAxis[axisIdx].data; |
| |
| axisData[inMethod](additionData); |
| mAxisData[inMethod](additionData); |
| if (!dataGrow) { |
| axisData[outMethod](); |
| mAxisData[outMethod](); |
| } |
| } |
| } |
| } |
| |
| // 同步图表内状态,动画需要 |
| this._option.series[seriesIdx].data = magicOption.series[seriesIdx].data; |
| } |
| } |
| |
| this._zr.clearAnimation(); |
| var chartList = this._chartList; |
| for (var i = 0, l = chartList.length; i < l; i++) { |
| if (magicOption.addDataAnimation && chartList[i].addDataAnimation) { |
| chartList[i].addDataAnimation(params); |
| } |
| } |
| |
| // dataZoom同步数据 |
| this.component.dataZoom && this.component.dataZoom.syncOption(magicOption); |
| |
| this._option = magicOption; |
| var self = this; |
| setTimeout(function (){ |
| if (!self._zr) { |
| return; // 已经被释放 |
| } |
| self._zr.clearAnimation(); |
| for (var i = 0, l = chartList.length; i < l; i++) { |
| // 有addData动画就去掉过渡动画 |
| chartList[i].motionlessOnce = |
| magicOption.addDataAnimation && chartList[i].addDataAnimation; |
| } |
| self._messageCenter.dispatch( |
| ecConfig.EVENT.REFRESH, |
| null, |
| {option: magicOption}, |
| self |
| ); |
| }, magicOption.addDataAnimation ? 500 : 0); |
| return this; |
| }, |
| |
| /** |
| * 动态[标注 | 标线]添加 |
| * @param {number} seriesIdx 系列索引 |
| * @param {Object} markData [标注 | 标线]对象,支持多个 |
| */ |
| addMarkPoint : function (seriesIdx, markData) { |
| return this._addMark(seriesIdx, markData, 'markPoint'); |
| }, |
| |
| addMarkLine : function (seriesIdx, markData) { |
| return this._addMark(seriesIdx, markData, 'markLine'); |
| }, |
| |
| _addMark : function (seriesIdx, markData, markType) { |
| var series = this._option.series; |
| var seriesItem; |
| |
| if (series && (seriesItem = series[seriesIdx])) { |
| var seriesR = this._optionRestore.series; |
| var seriesRItem = seriesR[seriesIdx]; |
| var markOpt = seriesItem[markType]; |
| var markOptR = seriesRItem[markType]; |
| |
| markOpt = seriesItem[markType] = markOpt || {data: []}; |
| markOptR = seriesRItem[markType] = markOptR || {data: []}; |
| |
| for (var key in markData) { |
| if (key == 'data') { |
| // 数据concat |
| markOpt.data = markOpt.data.concat(markData.data); |
| markOptR.data = markOptR.data.concat(markData.data); |
| } |
| else if (typeof markData[key] != 'object' |
| || typeof markOpt[key] == 'undefined' |
| ) { |
| // 简单类型或新值直接赋值 |
| markOpt[key] = markOptR[key] = markData[key]; |
| } |
| else { |
| // 非数据的复杂对象merge |
| zrUtil.merge(markOpt[key], markData[key], true); |
| zrUtil.merge(markOptR[key], markData[key], true); |
| } |
| } |
| |
| var chart = this.chart[seriesItem.type]; |
| chart && chart.addMark(seriesIdx, markData, markType); |
| } |
| |
| return this; |
| }, |
| |
| /** |
| * 动态[标注 | 标线]删除 |
| * @param {number} seriesIdx 系列索引 |
| * @param {string} markName [标注 | 标线]名称 |
| */ |
| delMarkPoint : function (seriesIdx, markName) { |
| return this._delMark(seriesIdx, markName, 'markPoint'); |
| }, |
| |
| delMarkLine : function (seriesIdx, markName) { |
| return this._delMark(seriesIdx, markName, 'markLine'); |
| }, |
| |
| _delMark : function (seriesIdx, markName, markType) { |
| var series = this._option.series; |
| var seriesItem; |
| var mark; |
| var dataArray; |
| |
| if (!( |
| series |
| && (seriesItem = series[seriesIdx]) |
| && (mark = seriesItem[markType]) |
| && (dataArray = mark.data) |
| ) |
| ) { |
| return this; |
| } |
| |
| markName = markName.split(' > '); |
| var targetIndex = -1; |
| |
| for (var i = 0, l = dataArray.length; i < l; i++) { |
| var dataItem = dataArray[i]; |
| if (dataItem instanceof Array) { |
| if (dataItem[0].name == markName[0] |
| && dataItem[1].name == markName[1] |
| ) { |
| targetIndex = i; |
| break; |
| } |
| } |
| else if (dataItem.name == markName[0]) { |
| targetIndex = i; |
| break; |
| } |
| } |
| |
| if (targetIndex > -1) { |
| dataArray.splice(targetIndex, 1); |
| this._optionRestore.series[seriesIdx][markType].data.splice(targetIndex, 1); |
| |
| var chart = this.chart[seriesItem.type]; |
| chart && chart.delMark(seriesIdx, markName.join(' > '), markType); |
| } |
| |
| return this; |
| }, |
| |
| /** |
| * 获取当前dom |
| */ |
| getDom : function () { |
| return this.dom; |
| }, |
| |
| /** |
| * 获取当前zrender实例,可用于添加额为的shape和深度控制 |
| */ |
| getZrender : function () { |
| return this._zr; |
| }, |
| |
| /** |
| * 获取Base64图片dataURL |
| * @param {string} imgType 图片类型,支持png|jpeg,默认为png |
| * @return imgDataURL |
| */ |
| getDataURL : function (imgType) { |
| if (!_canvasSupported) { |
| return ''; |
| } |
| |
| if (this._chartList.length === 0) { |
| // 渲染为图片 |
| var imgId = 'IMG' + this.id; |
| var img = document.getElementById(imgId); |
| if (img) { |
| return img.src; |
| } |
| } |
| |
| // 清除可能存在的tooltip元素 |
| var tooltip = this.component.tooltip; |
| tooltip && tooltip.hideTip(); |
| |
| switch (imgType) { |
| case 'jpeg': |
| break; |
| default: |
| imgType = 'png'; |
| } |
| |
| var bgColor = this._option.backgroundColor; |
| if (bgColor && bgColor.replace(' ','') == 'rgba(0,0,0,0)') { |
| bgColor = '#fff'; |
| } |
| |
| return this._zr.toDataURL('image/' + imgType, bgColor); |
| }, |
| |
| /** |
| * 获取img |
| * @param {string} imgType 图片类型,支持png|jpeg,默认为png |
| * @return img dom |
| */ |
| getImage : function (imgType) { |
| var title = this._optionRestore.title; |
| var imgDom = document.createElement('img'); |
| imgDom.src = this.getDataURL(imgType); |
| imgDom.title = (title && title.text) || 'ECharts'; |
| return imgDom; |
| }, |
| |
| /** |
| * 获取多图联动的Base64图片dataURL |
| * @param {string} imgType 图片类型,支持png|jpeg,默认为png |
| * @return imgDataURL |
| */ |
| getConnectedDataURL : function (imgType) { |
| if (!this.isConnected()) { |
| return this.getDataURL(imgType); |
| } |
| |
| var tempDom = this.dom; |
| var imgList = { |
| 'self' : { |
| img : this.getDataURL(imgType), |
| left : tempDom.offsetLeft, |
| top : tempDom.offsetTop, |
| right : tempDom.offsetLeft + tempDom.offsetWidth, |
| bottom : tempDom.offsetTop + tempDom.offsetHeight |
| } |
| }; |
| |
| var minLeft = imgList.self.left; |
| var minTop = imgList.self.top; |
| var maxRight = imgList.self.right; |
| var maxBottom = imgList.self.bottom; |
| |
| for (var c in this._connected) { |
| tempDom = this._connected[c].getDom(); |
| imgList[c] = { |
| img : this._connected[c].getDataURL(imgType), |
| left : tempDom.offsetLeft, |
| top : tempDom.offsetTop, |
| right : tempDom.offsetLeft + tempDom.offsetWidth, |
| bottom : tempDom.offsetTop + tempDom.offsetHeight |
| }; |
| |
| minLeft = Math.min(minLeft, imgList[c].left); |
| minTop = Math.min(minTop, imgList[c].top); |
| maxRight = Math.max(maxRight, imgList[c].right); |
| maxBottom = Math.max(maxBottom, imgList[c].bottom); |
| } |
| |
| var zrDom = document.createElement('div'); |
| zrDom.style.position = 'absolute'; |
| zrDom.style.left = '-4000px'; |
| zrDom.style.width = (maxRight - minLeft) + 'px'; |
| zrDom.style.height = (maxBottom - minTop) + 'px'; |
| document.body.appendChild(zrDom); |
| |
| var zrImg = require('zrender').init(zrDom); |
| |
| var ImageShape = require('zrender/shape/Image'); |
| for (var c in imgList) { |
| zrImg.addShape(new ImageShape({ |
| style : { |
| x : imgList[c].left - minLeft, |
| y : imgList[c].top - minTop, |
| image : imgList[c].img |
| } |
| })); |
| } |
| |
| zrImg.render(); |
| var bgColor = this._option.backgroundColor; |
| if (bgColor && bgColor.replace(/ /g, '') == 'rgba(0,0,0,0)') { |
| bgColor = '#fff'; |
| } |
| |
| var image = zrImg.toDataURL('image/png', bgColor); |
| |
| setTimeout(function () { |
| zrImg.dispose(); |
| zrDom.parentNode.removeChild(zrDom); |
| zrDom = null; |
| }, 100); |
| |
| return image; |
| }, |
| |
| /** |
| * 获取多图联动的img |
| * @param {string} imgType 图片类型,支持png|jpeg,默认为png |
| * @return img dom |
| */ |
| getConnectedImage : function (imgType) { |
| var title = this._optionRestore.title; |
| var imgDom = document.createElement('img'); |
| imgDom.src = this.getConnectedDataURL(imgType); |
| imgDom.title = (title && title.text) || 'ECharts'; |
| return imgDom; |
| }, |
| |
| /** |
| * 外部接口绑定事件 |
| * @param {Object} eventName 事件名称 |
| * @param {Object} eventListener 事件响应函数 |
| */ |
| on : function (eventName, eventListener) { |
| this._messageCenterOutSide.bind(eventName, eventListener, this); |
| return this; |
| }, |
| |
| /** |
| * 外部接口解除事件绑定 |
| * @param {Object} eventName 事件名称 |
| * @param {Object} eventListener 事件响应函数 |
| */ |
| un : function (eventName, eventListener) { |
| this._messageCenterOutSide.unbind(eventName, eventListener); |
| return this; |
| }, |
| |
| /** |
| * 多图联动 |
| * @param connectTarget{ECharts | Array <ECharts>} connectTarget 联动目标 |
| */ |
| connect : function (connectTarget) { |
| if (!connectTarget) { |
| return this; |
| } |
| |
| if (!this._connected) { |
| this._connected = {}; |
| } |
| |
| if (connectTarget instanceof Array) { |
| for (var i = 0, l = connectTarget.length; i < l; i++) { |
| this._connected[connectTarget[i].id] = connectTarget[i]; |
| } |
| } |
| else { |
| this._connected[connectTarget.id] = connectTarget; |
| } |
| |
| return this; |
| }, |
| |
| /** |
| * 解除多图联动 |
| * @param connectTarget{ECharts | Array <ECharts>} connectTarget 解除联动目标 |
| */ |
| disConnect : function (connectTarget) { |
| if (!connectTarget || !this._connected) { |
| return this; |
| } |
| |
| if (connectTarget instanceof Array) { |
| for (var i = 0, l = connectTarget.length; i < l; i++) { |
| delete this._connected[connectTarget[i].id]; |
| } |
| } |
| else { |
| delete this._connected[connectTarget.id]; |
| } |
| |
| for (var k in this._connected) { |
| return k, this; // 非空 |
| } |
| |
| // 空,转为标志位 |
| this._connected = false; |
| return this; |
| }, |
| |
| /** |
| * 联动事件响应 |
| */ |
| connectedEventHandler : function (param) { |
| if (param.__echartsId != this.id) { |
| // 来自其他联动图表的事件 |
| this._onevent(param); |
| } |
| }, |
| |
| /** |
| * 是否存在多图联动 |
| */ |
| isConnected : function () { |
| return !!this._connected; |
| }, |
| |
| /** |
| * 显示loading过渡 |
| * @param {Object} loadingOption |
| */ |
| showLoading : function (loadingOption) { |
| var effectList = { |
| bar : require('zrender/loadingEffect/Bar'), |
| bubble : require('zrender/loadingEffect/Bubble'), |
| dynamicLine : require('zrender/loadingEffect/DynamicLine'), |
| ring : require('zrender/loadingEffect/Ring'), |
| spin : require('zrender/loadingEffect/Spin'), |
| whirling : require('zrender/loadingEffect/Whirling') |
| }; |
| this._toolbox.hideDataView(); |
| |
| loadingOption = loadingOption || {}; |
| |
| var textStyle = loadingOption.textStyle || {}; |
| loadingOption.textStyle = textStyle; |
| |
| var finalTextStyle = zrUtil.merge( |
| zrUtil.clone(textStyle), |
| this._themeConfig.textStyle |
| ); |
| textStyle.textFont = finalTextStyle.fontStyle + ' ' |
| + finalTextStyle.fontWeight + ' ' |
| + finalTextStyle.fontSize + 'px ' |
| + finalTextStyle.fontFamily; |
| |
| textStyle.text = loadingOption.text || this._themeConfig.loadingText; |
| |
| if (loadingOption.x != null) { |
| textStyle.x = loadingOption.x; |
| } |
| if (loadingOption.y != null) { |
| textStyle.y = loadingOption.y; |
| } |
| |
| loadingOption.effectOption = loadingOption.effectOption || {}; |
| loadingOption.effectOption.textStyle = textStyle; |
| |
| var Effect = loadingOption.effect; |
| if (typeof Effect == 'string' || Effect == null) { |
| Effect = effectList[loadingOption.effect || 'spin']; |
| } |
| this._zr.showLoading(new Effect(loadingOption.effectOption)); |
| return this; |
| }, |
| |
| /** |
| * 隐藏loading过渡 |
| */ |
| hideLoading : function () { |
| this._zr.hideLoading(); |
| return this; |
| }, |
| |
| /** |
| * 主题设置 |
| */ |
| setTheme : function (theme) { |
| if (theme) { |
| if (typeof theme === 'string') { |
| // 默认主题 |
| switch (theme) { |
| // case 'themename': |
| // theme = require('./theme/themename'); |
| default: |
| theme = require('./theme/default'); |
| } |
| } |
| else { |
| theme = theme || {}; |
| } |
| |
| // 复位默认配置 |
| // this._themeConfig会被别的对象引用持有 |
| // 所以不能改成this._themeConfig = {}; |
| for (var key in this._themeConfig) { |
| delete this._themeConfig[key]; |
| } |
| for (var key in ecConfig) { |
| this._themeConfig[key] = zrUtil.clone(ecConfig[key]); |
| } |
| |
| // 颜色数组随theme,不merge |
| theme.color && (this._themeConfig.color = []); |
| |
| // 默认标志图形类型列表,不merge |
| theme.symbolList && (this._themeConfig.symbolList = []); |
| |
| // 应用新主题 |
| zrUtil.merge(this._themeConfig, zrUtil.clone(theme), true); |
| } |
| |
| if (!_canvasSupported) { // IE8- |
| this._themeConfig.textStyle.fontFamily = this._themeConfig.textStyle.fontFamily2; |
| } |
| |
| this._timeline && this._timeline.setTheme(true); |
| this._optionRestore && this.restore(); |
| }, |
| |
| /** |
| * 视图区域大小变化更新,不默认绑定,供使用方按需调用 |
| */ |
| resize : function () { |
| var self = this; |
| return function(){ |
| self._clearEffect(); |
| self._zr.resize(); |
| if (self._option && self._option.renderAsImage && _canvasSupported) { |
| // 渲染为图片重走render模式 |
| self._render(self._option); |
| return self; |
| } |
| // 停止动画 |
| self._zr.clearAnimation(); |
| self._island.resize(); |
| self._toolbox.resize(); |
| self._timeline && self._timeline.resize(); |
| // 先来后到,不能仅刷新自己,也不能在上一个循环中刷新,如坐标系数据改变会影响其他图表的大小 |
| // 所以安顺序刷新各种图表,图表内部refresh优化无需更新则不更新~ |
| for (var i = 0, l = self._chartList.length; i < l; i++) { |
| self._chartList[i].resize && self._chartList[i].resize(); |
| } |
| self.component.grid && self.component.grid.refixAxisShape(self.component); |
| self._zr.refresh(); |
| self._messageCenter.dispatch(ecConfig.EVENT.RESIZE, null, null, self); |
| return self; |
| }; |
| }, |
| |
| _clearEffect : function() { |
| this._zr.modLayer(ecConfig.EFFECT_ZLEVEL, {motionBlur : false}); |
| this._zr.painter.clearLayer(ecConfig.EFFECT_ZLEVEL); |
| }, |
| |
| /** |
| * 清除已渲染内容 ,clear后echarts实例可用 |
| */ |
| clear : function () { |
| this._disposeChartList(); |
| this._zr.clear(); |
| this._option = {}; |
| this._optionRestore = {}; |
| return this; |
| }, |
| |
| /** |
| * 释放,dispose后echarts实例不可用 |
| */ |
| dispose : function () { |
| var key = this.dom.getAttribute(DOM_ATTRIBUTE_KEY); |
| key && delete _instances[key]; |
| |
| this._island.dispose(); |
| this._toolbox.dispose(); |
| this._timeline && this._timeline.dispose(); |
| this._messageCenter.unbind(); |
| this.clear(); |
| this._zr.dispose(); |
| this._zr = null; |
| } |
| }; |
| |
| return self; |
| }); |
| define('echarts', ['echarts/echarts'], function (main) { return main; }); |
| |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:仪表盘指针 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'gauge-pointer', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| xStart : {number}, // 必须,起点横坐标 |
| yStart : {number}, // 必须,起点纵坐标 |
| xEnd : {number}, // 必须,终点横坐标 |
| yEnd : {number}, // 必须,终点纵坐标 |
| strokeColor : {color}, // 默认为'#000',线条颜色(轮廓),支持rgba |
| lineWidth : {number}, // 线条宽度 |
| }, |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| */ |
| define('echarts/util/shape/GaugePointer',['require','zrender/shape/Base','zrender/tool/util','./normalIsCover'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function GaugePointer(options) { |
| Base.call(this, options); |
| } |
| |
| GaugePointer.prototype = { |
| type: 'gauge-pointer', |
| /** |
| * 创建矩形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| var r = style.r; |
| var width = style.width; |
| var angle = style.angle; |
| var x = style.x - Math.cos(angle) * width * (width >= r / 3 ? 1 : 2); |
| var y = style.y + Math.sin(angle) * width * (width >= r / 3 ? 1 : 2); |
| |
| angle = style.angle - Math.PI / 2; |
| ctx.moveTo(x, y); |
| ctx.lineTo( |
| style.x + Math.cos(angle) * width, |
| style.y - Math.sin(angle) * width |
| ); |
| ctx.lineTo( |
| style.x + Math.cos(style.angle) * r, |
| style.y - Math.sin(style.angle) * r |
| ); |
| ctx.lineTo( |
| style.x - Math.cos(angle) * width, |
| style.y + Math.sin(angle) * width |
| ); |
| ctx.lineTo(x, y); |
| return; |
| }, |
| |
| /** |
| * 返回矩形区域,用于局部刷新和文字定位 |
| * @param {Object} style |
| */ |
| getRect : function(style) { |
| if (style.__rect) { |
| return style.__rect; |
| } |
| |
| var width = style.width * 2; |
| var xStart = style.x; |
| var yStart = style.y; |
| var xEnd = xStart + Math.cos(style.angle) * style.r; |
| var yEnd = yStart - Math.sin(style.angle) * style.r; |
| |
| style.__rect = { |
| x : Math.min(xStart, xEnd) - width, |
| y : Math.min(yStart, yEnd) - width, |
| width : Math.abs(xStart - xEnd) + width, |
| height : Math.abs(yStart - yEnd) + width |
| }; |
| return style.__rect; |
| }, |
| |
| isCover : require('./normalIsCover') |
| }; |
| |
| zrUtil.inherits(GaugePointer, Base); |
| |
| return GaugePointer; |
| }); |
| |
| /** |
| * echarts图表类:仪表盘 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/gauge',['require','../component/base','./base','../util/shape/GaugePointer','zrender/shape/Text','zrender/shape/Line','zrender/shape/Rectangle','zrender/shape/Circle','zrender/shape/Sector','../config','../util/ecData','zrender/tool/util','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var GaugePointerShape = require('../util/shape/GaugePointer'); |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var CircleShape = require('zrender/shape/Circle'); |
| var SectorShape = require('zrender/shape/Sector'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Gauge(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| this.refresh(option); |
| } |
| |
| Gauge.prototype = { |
| type : ecConfig.CHART_TYPE_GAUGE, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| // 复用参数索引 |
| this._paramsMap = {}; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type == ecConfig.CHART_TYPE_GAUGE) { |
| series[i] = this.reformOption(series[i]); |
| this._buildSingleGauge(i); |
| this.buildMark(i); |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个仪表盘 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSingleGauge : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| |
| this._paramsMap[seriesIndex] = { |
| center : this.parseCenter(this.zr, serie.center), |
| radius : this.parseRadius(this.zr, serie.radius), |
| startAngle : serie.startAngle.toFixed(2) - 0, |
| endAngle : serie.endAngle.toFixed(2) - 0 |
| }; |
| this._paramsMap[seriesIndex].totalAngle = this._paramsMap[seriesIndex].startAngle |
| - this._paramsMap[seriesIndex].endAngle; |
| |
| this._colorMap(seriesIndex); |
| |
| this._buildAxisLine(seriesIndex); |
| |
| this._buildSplitLine(seriesIndex); |
| |
| this._buildAxisTick(seriesIndex); |
| |
| this._buildAxisLabel(seriesIndex); |
| |
| this._buildPointer(seriesIndex); |
| |
| this._buildTitle(seriesIndex); |
| |
| this._buildDetail(seriesIndex); |
| }, |
| |
| // 轴线 |
| _buildAxisLine : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.axisLine.show) { |
| return; |
| } |
| var min = serie.min; |
| var total = serie.max - min; |
| var params = this._paramsMap[seriesIndex]; |
| var center = params.center; |
| var startAngle = params.startAngle; |
| var totalAngle = params.totalAngle; |
| var colorArray = params.colorArray; |
| var lineStyle = serie.axisLine.lineStyle; |
| var lineWidth = this.parsePercent(lineStyle.width, params.radius[1]); |
| var r = params.radius[1]; |
| var r0 = r - lineWidth; |
| |
| var sectorShape; |
| var lastAngle = startAngle; |
| var newAngle; |
| for (var i = 0, l = colorArray.length; i < l; i++) { |
| newAngle = startAngle - totalAngle * (colorArray[i][0] - min) / total; |
| sectorShape = this._getSector( |
| center, r0, r, |
| newAngle, // startAngle |
| lastAngle, // endAngle |
| colorArray[i][1], // color |
| lineStyle |
| ); |
| lastAngle = newAngle; |
| sectorShape._animationAdd = 'r'; |
| ecData.set(sectorShape, 'seriesIndex', seriesIndex); |
| ecData.set(sectorShape, 'dataIndex', i); |
| this.shapeList.push(sectorShape); |
| } |
| }, |
| |
| // 坐标轴分割线 |
| _buildSplitLine : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.splitLine.show) { |
| return; |
| } |
| |
| var params = this._paramsMap[seriesIndex]; |
| var splitNumber = serie.splitNumber; |
| var min = serie.min; |
| var total = serie.max - min; |
| var splitLine = serie.splitLine; |
| var length = this.parsePercent( |
| splitLine.length, params.radius[1] |
| ); |
| var lineStyle = splitLine.lineStyle; |
| var color = lineStyle.color; |
| var center = params.center; |
| var startAngle = params.startAngle * Math.PI / 180; |
| var totalAngle = params.totalAngle * Math.PI / 180; |
| var r = params.radius[1]; |
| var r0 = r - length; |
| |
| var angle; |
| var sinAngle; |
| var cosAngle; |
| for (var i = 0; i <= splitNumber; i++) { |
| angle = startAngle - totalAngle / splitNumber * i; |
| sinAngle = Math.sin(angle); |
| cosAngle = Math.cos(angle); |
| this.shapeList.push(new LineShape({ |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| style : { |
| xStart : center[0] + cosAngle * r, |
| yStart : center[1] - sinAngle * r, |
| xEnd : center[0] + cosAngle * r0, |
| yEnd : center[1] - sinAngle * r0, |
| strokeColor : color == 'auto' |
| ? this._getColor(seriesIndex, min + total / splitNumber * i) |
| : color, |
| lineType : lineStyle.type, |
| lineWidth : lineStyle.width, |
| shadowColor : lineStyle.shadowColor, |
| shadowBlur: lineStyle.shadowBlur, |
| shadowOffsetX: lineStyle.shadowOffsetX, |
| shadowOffsetY: lineStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| // 小标记 |
| _buildAxisTick : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.axisTick.show) { |
| return; |
| } |
| |
| var params = this._paramsMap[seriesIndex]; |
| var splitNumber = serie.splitNumber; |
| var min = serie.min; |
| var total = serie.max - min; |
| var axisTick = serie.axisTick; |
| var tickSplit = axisTick.splitNumber; |
| var length = this.parsePercent( |
| axisTick.length, params.radius[1] |
| ); |
| var lineStyle = axisTick.lineStyle; |
| var color = lineStyle.color; |
| |
| var center = params.center; |
| var startAngle = params.startAngle * Math.PI / 180; |
| var totalAngle = params.totalAngle * Math.PI / 180; |
| var r = params.radius[1]; |
| var r0 = r - length; |
| |
| var angle; |
| var sinAngle; |
| var cosAngle; |
| for (var i = 0, l = splitNumber * tickSplit; i <= l; i++) { |
| if (i % tickSplit === 0) { // 同splitLine |
| continue; |
| } |
| angle = startAngle - totalAngle / l * i; |
| sinAngle = Math.sin(angle); |
| cosAngle = Math.cos(angle); |
| this.shapeList.push(new LineShape({ |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| style : { |
| xStart : center[0] + cosAngle * r, |
| yStart : center[1] - sinAngle * r, |
| xEnd : center[0] + cosAngle * r0, |
| yEnd : center[1] - sinAngle * r0, |
| strokeColor : color == 'auto' |
| ? this._getColor(seriesIndex, min + total / l * i) |
| : color, |
| lineType : lineStyle.type, |
| lineWidth : lineStyle.width, |
| shadowColor : lineStyle.shadowColor, |
| shadowBlur: lineStyle.shadowBlur, |
| shadowOffsetX: lineStyle.shadowOffsetX, |
| shadowOffsetY: lineStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| // 坐标轴文本 |
| _buildAxisLabel : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.axisLabel.show) { |
| return; |
| } |
| |
| var splitNumber = serie.splitNumber; |
| var min = serie.min; |
| var total = serie.max - min; |
| var textStyle = serie.axisLabel.textStyle; |
| var textFont = this.getFont(textStyle); |
| var color = textStyle.color; |
| |
| var params = this._paramsMap[seriesIndex]; |
| var center = params.center; |
| var startAngle = params.startAngle; |
| var totalAngle = params.totalAngle; |
| var r0 = params.radius[1] |
| - this.parsePercent( |
| serie.splitLine.length, params.radius[1] |
| ) - 10; |
| |
| var angle; |
| var sinAngle; |
| var cosAngle; |
| var value; |
| for (var i = 0; i <= splitNumber; i++) { |
| value = min + total / splitNumber * i; |
| angle = startAngle - totalAngle / splitNumber * i; |
| sinAngle = Math.sin(angle * Math.PI / 180); |
| cosAngle = Math.cos(angle * Math.PI / 180); |
| angle = (angle + 360) % 360; |
| this.shapeList.push(new TextShape({ |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| style : { |
| x : center[0] + cosAngle * r0, |
| y : center[1] - sinAngle * r0, |
| color : color == 'auto' ? this._getColor(seriesIndex, value) : color, |
| text : this._getLabelText(serie.axisLabel.formatter, value), |
| textAlign : (angle >= 110 && angle <= 250) |
| ? 'left' |
| : (angle <= 70 || angle >= 290) |
| ? 'right' |
| : 'center', |
| textBaseline : (angle >= 10 && angle <= 170) |
| ? 'top' |
| : (angle >= 190 && angle <= 350) |
| ? 'bottom' |
| : 'middle', |
| textFont : textFont, |
| shadowColor : textStyle.shadowColor, |
| shadowBlur: textStyle.shadowBlur, |
| shadowOffsetX: textStyle.shadowOffsetX, |
| shadowOffsetY: textStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| _buildPointer : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.pointer.show) { |
| return; |
| } |
| var total = serie.max - serie.min; |
| var pointer = serie.pointer; |
| |
| var params = this._paramsMap[seriesIndex]; |
| var length = this.parsePercent(pointer.length, params.radius[1]); |
| var width = this.parsePercent(pointer.width, params.radius[1]); |
| var center = params.center; |
| var value = this._getValue(seriesIndex); |
| value = value < serie.max ? value : serie.max; |
| |
| var angle = (params.startAngle - params.totalAngle / total * (value - serie.min)) * Math.PI / 180; |
| var color = pointer.color == 'auto' |
| ? this._getColor(seriesIndex, value) : pointer.color; |
| |
| var pointShape = new GaugePointerShape({ |
| zlevel : this._zlevelBase + 1, |
| style : { |
| x : center[0], |
| y : center[1], |
| r : length, |
| startAngle : params.startAngle * Math.PI / 180, |
| angle : angle, |
| color : color, |
| width : width, |
| shadowColor : pointer.shadowColor, |
| shadowBlur: pointer.shadowBlur, |
| shadowOffsetX: pointer.shadowOffsetX, |
| shadowOffsetY: pointer.shadowOffsetY |
| }, |
| highlightStyle : { |
| brushType : 'fill', |
| width : width > 2 ? 2 : (width / 2), |
| color : '#fff' |
| } |
| }); |
| ecData.pack( |
| pointShape, |
| this.series[seriesIndex], seriesIndex, |
| this.series[seriesIndex].data[0], 0, |
| this.series[seriesIndex].data[0].name, |
| value |
| ); |
| this.shapeList.push(pointShape); |
| |
| this.shapeList.push(new CircleShape({ |
| zlevel : this._zlevelBase + 2, |
| hoverable : false, |
| style : { |
| x : center[0], |
| y : center[1], |
| r : pointer.width / 2.5, |
| color : '#fff' |
| } |
| })); |
| }, |
| |
| _buildTitle : function(seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.title.show) { |
| return; |
| } |
| |
| var data = serie.data[0]; |
| var name = typeof data.name != 'undefined' ? data.name : ''; |
| if (name !== '') { |
| var title = serie.title; |
| var offsetCenter = title.offsetCenter; |
| var textStyle = title.textStyle; |
| var textColor = textStyle.color; |
| var params = this._paramsMap[seriesIndex]; |
| var x = params.center[0] + this.parsePercent(offsetCenter[0], params.radius[1]); |
| var y = params.center[1] + this.parsePercent(offsetCenter[1], params.radius[1]); |
| this.shapeList.push(new TextShape({ |
| zlevel : this._zlevelBase |
| + (Math.abs(x - params.center[0]) + Math.abs(y - params.center[1])) |
| < textStyle.fontSize * 2 ? 2 : 1, |
| hoverable : false, |
| style : { |
| x : x, |
| y : y, |
| color: textColor == 'auto' ? this._getColor(seriesIndex) : textColor, |
| text: name, |
| textAlign: 'center', |
| textFont : this.getFont(textStyle), |
| shadowColor : textStyle.shadowColor, |
| shadowBlur: textStyle.shadowBlur, |
| shadowOffsetX: textStyle.shadowOffsetX, |
| shadowOffsetY: textStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| _buildDetail : function(seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.detail.show) { |
| return; |
| } |
| |
| var detail = serie.detail; |
| var offsetCenter = detail.offsetCenter; |
| var color = detail.backgroundColor; |
| var textStyle = detail.textStyle; |
| var textColor = textStyle.color; |
| |
| var params = this._paramsMap[seriesIndex]; |
| var value = this._getValue(seriesIndex); |
| var x = params.center[0] - detail.width / 2 |
| + this.parsePercent(offsetCenter[0], params.radius[1]); |
| var y = params.center[1] |
| + this.parsePercent(offsetCenter[1], params.radius[1]); |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase |
| + (Math.abs(x+detail.width/2 - params.center[0]) |
| + Math.abs(y+detail.height/2 - params.center[1])) |
| < textStyle.fontSize ? 2 : 1, |
| hoverable : false, |
| style : { |
| x : x, |
| y : y, |
| width : detail.width, |
| height : detail.height, |
| brushType: 'both', |
| color: color == 'auto' ? this._getColor(seriesIndex, value) : color, |
| lineWidth : detail.borderWidth, |
| strokeColor : detail.borderColor, |
| |
| shadowColor : detail.shadowColor, |
| shadowBlur: detail.shadowBlur, |
| shadowOffsetX: detail.shadowOffsetX, |
| shadowOffsetY: detail.shadowOffsetY, |
| |
| text: this._getLabelText(detail.formatter, value), |
| textFont: this.getFont(textStyle), |
| textPosition: 'inside', |
| textColor : textColor == 'auto' ? this._getColor(seriesIndex, value) : textColor |
| } |
| })); |
| }, |
| |
| _getValue : function(seriesIndex) { |
| var data = this.series[seriesIndex].data[0]; |
| return typeof data.value != 'undefined' ? data.value : data; |
| }, |
| |
| /** |
| * 颜色索引 |
| */ |
| _colorMap : function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| var min = serie.min; |
| var total = serie.max - min; |
| var color = serie.axisLine.lineStyle.color; |
| if (!(color instanceof Array)) { |
| color = [[1, color]]; |
| } |
| var colorArray = []; |
| for (var i = 0, l = color.length; i < l; i++) { |
| colorArray.push([color[i][0] * total + min, color[i][1]]); |
| } |
| this._paramsMap[seriesIndex].colorArray = colorArray; |
| }, |
| |
| /** |
| * 自动颜色 |
| */ |
| _getColor : function (seriesIndex, value) { |
| if (typeof value == 'undefined') { |
| value = this._getValue(seriesIndex); |
| } |
| |
| var colorArray = this._paramsMap[seriesIndex].colorArray; |
| for (var i = 0, l = colorArray.length; i < l; i++) { |
| if (colorArray[i][0] >= value) { |
| return colorArray[i][1]; |
| } |
| } |
| return colorArray[colorArray.length - 1][1]; |
| }, |
| |
| /** |
| * 构建扇形 |
| */ |
| _getSector : function (center, r0, r, startAngle, endAngle, color, lineStyle) { |
| return new SectorShape ({ |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : center[0], // 圆心横坐标 |
| y : center[1], // 圆心纵坐标 |
| r0 : r0, // 圆环内半径 |
| r : r, // 圆环外半径 |
| startAngle : startAngle, |
| endAngle : endAngle, |
| brushType : 'fill', |
| color : color, |
| shadowColor : lineStyle.shadowColor, |
| shadowBlur: lineStyle.shadowBlur, |
| shadowOffsetX: lineStyle.shadowOffsetX, |
| shadowOffsetY: lineStyle.shadowOffsetY |
| } |
| }); |
| }, |
| |
| /** |
| * 根据lable.format计算label text |
| */ |
| _getLabelText : function (formatter, value) { |
| if (formatter) { |
| if (typeof formatter == 'function') { |
| return formatter.call(this.myChart, value); |
| } |
| else if (typeof formatter == 'string') { |
| return formatter.replace('{value}', value); |
| } |
| } |
| return value; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| } |
| }; |
| |
| zrUtil.inherits(Gauge, ChartBase); |
| zrUtil.inherits(Gauge, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('gauge', Gauge); |
| |
| return Gauge; |
| }); |
| /** |
| * echarts图表类:漏斗图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/funnel',['require','../component/base','./base','zrender/shape/Text','zrender/shape/Line','zrender/shape/Polygon','../config','../util/ecData','../util/number','zrender/tool/util','zrender/tool/color','zrender/tool/area','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var PolygonShape = require('zrender/shape/Polygon'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var number = require('../util/number'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| var zrArea = require('zrender/tool/area'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Funnel(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| this.refresh(option); |
| } |
| |
| Funnel.prototype = { |
| type : ecConfig.CHART_TYPE_FUNNEL, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| var legend = this.component.legend; |
| // 复用参数索引 |
| this._paramsMap = {}; |
| this._selected = {}; |
| this.selectedMap = {}; |
| |
| var serieName; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type == ecConfig.CHART_TYPE_FUNNEL) { |
| series[i] = this.reformOption(series[i]); |
| serieName = series[i].name || ''; |
| // 系列图例开关 |
| this.selectedMap[serieName] = |
| legend ? legend.isSelected(serieName) : true; |
| if (!this.selectedMap[serieName]) { |
| continue; |
| } |
| this._buildSingleFunnel(i); |
| this.buildMark(i); |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个仪表盘 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSingleFunnel : function (seriesIndex) { |
| var legend = this.component.legend; |
| var serie = this.series[seriesIndex]; |
| var data = this._mapData(seriesIndex); |
| var location = this._getLocation(seriesIndex); |
| this._paramsMap[seriesIndex] = { |
| location : location, |
| data : data |
| }; |
| |
| var itemName; |
| var total = 0; |
| var selectedData = []; |
| // 计算需要显示的个数和总值 |
| for (var i = 0, l = data.length; i < l; i++) { |
| itemName = data[i].name; |
| if (legend){ |
| this.selectedMap[itemName] = legend.isSelected(itemName); |
| } else { |
| this.selectedMap[itemName] = true; |
| } |
| if (this.selectedMap[itemName] && !isNaN(data[i].value)) { |
| selectedData.push(data[i]); |
| total++; |
| } |
| } |
| if (total === 0) { |
| return; |
| } |
| // 可计算箱子 |
| var funnelCase = this._buildFunnelCase(seriesIndex); |
| var gap = serie.gap; |
| var height = total > 1 |
| ? (location.height - (total - 1) * gap) / total : location.height; |
| var width; |
| var lastY = location.y; |
| var lastWidth = serie.sort == 'descending' |
| ? this._getItemWidth(seriesIndex, selectedData[0].value) |
| : number.parsePercent(serie.minSize, location.width); |
| var next = serie.sort == 'descending' ? 1 : 0; |
| var centerX = location.centerX; |
| var pointList = [ |
| [ |
| centerX - lastWidth / 2 - (lastWidth === 0 ? 0 : 10), |
| lastY - (lastWidth === 0 ? 10 : 5) |
| ], |
| [ |
| centerX + lastWidth / 2 + (lastWidth === 0 ? 0 : 10), |
| lastY - (lastWidth === 0 ? 10 : 5) |
| ] |
| ]; |
| for (var i = 0, l = selectedData.length; i < l; i++) { |
| itemName = selectedData[i].name; |
| if (this.selectedMap[itemName] && !isNaN(selectedData[i].value)) { |
| width = i <= l - 2 |
| ? this._getItemWidth(seriesIndex, selectedData[i + next].value) |
| : serie.sort == 'descending' |
| ? number.parsePercent(serie.minSize, location.width) |
| : number.parsePercent(serie.maxSize, location.width); |
| this._buildItem( |
| seriesIndex, selectedData[i]._index, |
| legend |
| ? legend.getColor(itemName) : this.zr.getColor(selectedData[i]._index), |
| centerX - lastWidth / 2, lastY, |
| lastWidth, width, height |
| ); |
| lastY += height + gap; |
| lastWidth = width; |
| pointList.unshift([centerX - lastWidth / 2 - 10, lastY]); |
| pointList.push([centerX + lastWidth / 2 + 10, lastY]); |
| } |
| } |
| if (funnelCase) { |
| if (lastWidth === 0) { |
| pointList.pop(); |
| pointList[0][0] +=10; |
| pointList[0][1] +=10; |
| } |
| else { |
| pointList[pointList.length - 1][1] +=5; |
| pointList[0][1] +=5; |
| } |
| funnelCase.style.pointList = pointList; |
| } |
| }, |
| |
| _buildFunnelCase : function(seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (this.deepQuery([serie, this.option], 'calculable')) { |
| var location = this._paramsMap[seriesIndex].location; |
| var gap = 10; |
| var funnelCase = { |
| hoverable : false, |
| style : { |
| pointListd : [ |
| [location.x - gap, location.y - gap], |
| [location.x + location.width + gap, location.y - gap], |
| [location.x + location.width + gap, location.y + location.height + gap], |
| [location.x - gap, location.y + location.height + gap] |
| ], |
| brushType : 'stroke', |
| lineWidth : 1, |
| strokeColor : serie.calculableHolderColor |
| || this.ecTheme.calculableHolderColor |
| } |
| }; |
| ecData.pack(funnelCase, serie, seriesIndex, undefined, -1); |
| this.setCalculable(funnelCase); |
| funnelCase = new PolygonShape(funnelCase); |
| this.shapeList.push(funnelCase); |
| return funnelCase; |
| } |
| }, |
| |
| _getLocation: function (seriesIndex) { |
| var gridOption = this.series[seriesIndex]; |
| var zrWidth = this.zr.getWidth(); |
| var zrHeight = this.zr.getHeight(); |
| var x = this.parsePercent(gridOption.x, zrWidth); |
| var y = this.parsePercent(gridOption.y, zrHeight); |
| |
| var width; |
| if (typeof gridOption.width == 'undefined') { |
| width = zrWidth - x - this.parsePercent(gridOption.x2, zrWidth); |
| } |
| else { |
| width = this.parsePercent(gridOption.width, zrWidth); |
| } |
| |
| var height; |
| if (typeof gridOption.height == 'undefined') { |
| height = zrHeight - y - this.parsePercent(gridOption.y2, zrHeight); |
| } |
| else { |
| height = this.parsePercent(gridOption.height, zrHeight); |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : width, |
| height : height, |
| centerX : x + width / 2 |
| }; |
| }, |
| |
| _mapData : function(seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| var funnelData = zrUtil.clone(serie.data); |
| for (var i = 0, l = funnelData.length; i < l; i++) { |
| funnelData[i]._index = i; |
| } |
| function numDescending (a, b) { |
| if (a.value == '-') { |
| return 1; |
| } |
| else if (b.value == '-') { |
| return -1; |
| } |
| return b.value - a.value; |
| } |
| function numAscending (a, b) { |
| return -numDescending(a, b); |
| } |
| if (serie.sort != 'none') { |
| funnelData.sort(serie.sort == 'descending' ? numDescending : numAscending); |
| } |
| |
| return funnelData; |
| }, |
| |
| /** |
| * 构建单个扇形及指标 |
| */ |
| _buildItem : function ( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| // 漏斗 |
| var polygon = this.getPolygon( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ); |
| ecData.pack( |
| polygon, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| series[seriesIndex].data[dataIndex].name |
| ); |
| this.shapeList.push(polygon); |
| |
| // 文本标签 |
| var label = this.getLabel( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ); |
| ecData.pack( |
| label, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| series[seriesIndex].data[dataIndex].name |
| ); |
| this.shapeList.push(label); |
| // 特定状态下是否需要显示文本标签 |
| if (!this._needLabel(serie, data,false)) { |
| label.invisible = true; |
| } |
| |
| // 文本标签视觉引导线 |
| var labelLine = this.getLabelLine( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ); |
| this.shapeList.push(labelLine); |
| // 特定状态下是否需要显示文本标签引导线 |
| if (!this._needLabelLine(serie, data,false)) { |
| labelLine.invisible = true; |
| } |
| |
| var polygonHoverConnect = []; |
| var labelHoverConnect = []; |
| if (this._needLabelLine(serie, data, true)) { |
| polygonHoverConnect.push(labelLine.id); |
| labelHoverConnect.push(labelLine.id); |
| } |
| if (this._needLabel(serie, data, true)) { |
| polygonHoverConnect.push(label.id); |
| labelHoverConnect.push(polygon.id); |
| } |
| polygon.hoverConnect = polygonHoverConnect; |
| label.hoverConnect = labelHoverConnect; |
| polygon.onmouseover = label.onmouseover = this.hoverConnect; |
| }, |
| |
| /** |
| * 根据值计算宽度 |
| */ |
| _getItemWidth : function (seriesIndex, value) { |
| var serie = this.series[seriesIndex]; |
| var location = this._paramsMap[seriesIndex].location; |
| var min = serie.min; |
| var max = serie.max; |
| var minSize = number.parsePercent(serie.minSize, location.width); |
| var maxSize = number.parsePercent(serie.maxSize, location.width); |
| return value * (maxSize - minSize) / (max - min); |
| }, |
| |
| /** |
| * 构建扇形 |
| */ |
| getPolygon : function ( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ) { |
| var serie = this.series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var queryTarget = [data, serie]; |
| |
| // 多级控制 |
| var normal = this.deepMerge( |
| queryTarget, |
| 'itemStyle.normal' |
| ) || {}; |
| var emphasis = this.deepMerge( |
| queryTarget, |
| 'itemStyle.emphasis' |
| ) || {}; |
| var normalColor = this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data) |
| || defaultColor; |
| |
| var emphasisColor = this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data) |
| || (typeof normalColor == 'string' |
| ? zrColor.lift(normalColor, -0.2) |
| : normalColor |
| ); |
| |
| var polygon = { |
| zlevel : this._zlevelBase, |
| clickable : true, |
| style : { |
| pointList : [ |
| [x, y], |
| [x + topWidth, y], |
| [x + topWidth - (topWidth - bottomWidth) / 2, y + height], |
| [x + (topWidth - bottomWidth) / 2, y + height] |
| ], |
| brushType : 'both', |
| color : normalColor, |
| lineWidth : normal.borderWidth, |
| strokeColor : normal.borderColor |
| }, |
| highlightStyle : { |
| color : emphasisColor, |
| lineWidth : emphasis.borderWidth, |
| strokeColor : emphasis.borderColor |
| } |
| }; |
| |
| if (this.deepQuery([data, serie, this.option], 'calculable')) { |
| this.setCalculable(polygon); |
| polygon.draggable = true; |
| } |
| |
| return new PolygonShape(polygon); |
| }, |
| |
| /** |
| * 需要显示则会有返回构建好的shape,否则返回undefined |
| */ |
| getLabel : function ( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ) { |
| var serie = this.series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var location = this._paramsMap[seriesIndex].location; |
| // serie里有默认配置,放心大胆的用! |
| var itemStyle = zrUtil.merge( |
| zrUtil.clone(data.itemStyle) || {}, |
| serie.itemStyle |
| ); |
| var status = 'normal'; |
| // label配置 |
| var labelControl = itemStyle[status].label; |
| var textStyle = labelControl.textStyle || {}; |
| var lineLength = itemStyle[status].labelLine.length; |
| |
| var text = this.getLabelText(seriesIndex, dataIndex, status); |
| var textFont = this.getFont(textStyle); |
| var textAlign; |
| var textX; |
| var textColor = defaultColor; |
| labelControl.position = labelControl.position |
| || itemStyle.normal.label.position; |
| if (labelControl.position == 'inner' || labelControl.position == 'inside') { |
| // 内部 |
| textAlign = 'center'; |
| textX = x + topWidth / 2; |
| if (Math.max(topWidth, bottomWidth) / 2 > zrArea.getTextWidth(text, textFont)) { |
| textColor = '#fff'; |
| } |
| else { |
| textColor = zrColor.reverse(defaultColor); |
| } |
| } |
| else if (labelControl.position == 'left'){ |
| // 左侧显示 |
| textAlign = 'right'; |
| textX = lineLength == 'auto' |
| ? (location.x - 10) |
| : (location.centerX - Math.max(topWidth, bottomWidth) / 2 - lineLength); |
| } |
| else { |
| // 右侧显示,默认 labelControl.position == 'outer' || 'right) |
| textAlign = 'left'; |
| textX = lineLength == 'auto' |
| ? (location.x + location.width + 10) |
| : (location.centerX + Math.max(topWidth, bottomWidth) / 2 + lineLength); |
| } |
| |
| var textShape = { |
| zlevel : this._zlevelBase + 1, |
| style : { |
| x : textX, |
| y : y + height / 2, |
| color : textStyle.color || textColor, |
| text : text, |
| textAlign : textStyle.align || textAlign, |
| textBaseline : textStyle.baseline || 'middle', |
| textFont : textFont |
| } |
| }; |
| |
| //----------高亮 |
| status = 'emphasis'; |
| // label配置 |
| labelControl = itemStyle[status].label || labelControl; |
| textStyle = labelControl.textStyle || textStyle; |
| lineLength = itemStyle[status].labelLine.length || lineLength; |
| labelControl.position = labelControl.position || itemStyle.normal.label.position; |
| text = this.getLabelText(seriesIndex, dataIndex, status); |
| textFont = this.getFont(textStyle); |
| textColor = defaultColor; |
| if (labelControl.position == 'inner' || labelControl.position == 'inside') { |
| // 内部 |
| textAlign = 'center'; |
| textX = x + topWidth / 2; |
| if (Math.max(topWidth, bottomWidth) / 2 > zrArea.getTextWidth(text, textFont)) { |
| textColor = '#fff'; |
| } |
| else { |
| textColor = zrColor.reverse(defaultColor); |
| } |
| } |
| else if (labelControl.position == 'left'){ |
| // 左侧显示 |
| textAlign = 'right'; |
| textX = lineLength == 'auto' |
| ? (location.x - 10) |
| : (location.centerX - Math.max(topWidth, bottomWidth) / 2 - lineLength); |
| } |
| else { |
| // 右侧显示,默认 labelControl.position == 'outer' || 'right) |
| textAlign = 'left'; |
| textX = lineLength == 'auto' |
| ? (location.x + location.width + 10) |
| : (location.centerX + Math.max(topWidth, bottomWidth) / 2 + lineLength); |
| } |
| textShape.highlightStyle = { |
| x : textX, |
| color : textStyle.color || textColor, |
| text : text, |
| textAlign : textStyle.align || textAlign, |
| textFont : textFont, |
| brushType : 'fill' |
| }; |
| |
| return new TextShape(textShape); |
| }, |
| |
| /** |
| * 根据lable.format计算label text |
| */ |
| getLabelText : function (seriesIndex, dataIndex, status) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var formatter = this.deepQuery( |
| [data, serie], |
| 'itemStyle.' + status + '.label.formatter' |
| ); |
| |
| if (formatter) { |
| if (typeof formatter == 'function') { |
| return formatter.call( |
| this.myChart, |
| serie.name, |
| data.name, |
| data.value |
| ); |
| } |
| else if (typeof formatter == 'string') { |
| formatter = formatter.replace('{a}','{a0}') |
| .replace('{b}','{b0}') |
| .replace('{c}','{c0}'); |
| formatter = formatter.replace('{a0}', serie.name) |
| .replace('{b0}', data.name) |
| .replace('{c0}', data.value); |
| |
| return formatter; |
| } |
| } |
| else { |
| return data.name; |
| } |
| }, |
| |
| /** |
| * 需要显示则会有返回构建好的shape,否则返回undefined |
| */ |
| getLabelLine : function ( |
| seriesIndex, dataIndex, defaultColor, |
| x, y, topWidth, bottomWidth, height |
| ) { |
| var serie = this.series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var location = this._paramsMap[seriesIndex].location; |
| |
| // serie里有默认配置,放心大胆的用! |
| var itemStyle = zrUtil.merge( |
| zrUtil.clone(data.itemStyle) || {}, |
| serie.itemStyle |
| ); |
| var status = 'normal'; |
| // labelLine配置 |
| var labelLineControl = itemStyle[status].labelLine; |
| var lineLength = itemStyle[status].labelLine.length; |
| var lineStyle = labelLineControl.lineStyle || {}; |
| |
| var labelControl = itemStyle[status].label; |
| labelControl.position = labelControl.position |
| || itemStyle.normal.label.position; |
| var xEnd; |
| if (labelControl.position == 'inner' || labelControl.position == 'inside') { |
| // 内部 |
| xEnd = x + topWidth / 2; |
| } |
| else if (labelControl.position == 'left'){ |
| // 左侧显示 |
| xEnd = lineLength == 'auto' |
| ? (location.x - 10) |
| : (location.centerX - Math.max(topWidth, bottomWidth) / 2 - lineLength); |
| } |
| else { |
| // 右侧显示,默认 labelControl.position == 'outer' || 'right) |
| xEnd = lineLength == 'auto' |
| ? (location.x + location.width + 10) |
| : (location.centerX + Math.max(topWidth, bottomWidth) / 2 + lineLength); |
| } |
| var lineShape = { |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| style : { |
| xStart : location.centerX, |
| yStart : y + height / 2, |
| xEnd : xEnd, |
| yEnd : y + height / 2, |
| strokeColor : lineStyle.color || defaultColor, |
| lineType : lineStyle.type, |
| lineWidth : lineStyle.width |
| } |
| }; |
| |
| status = 'emphasis'; |
| // labelLine配置 |
| labelLineControl = itemStyle[status].labelLine || labelLineControl; |
| lineLength = itemStyle[status].labelLine.length || lineLength; |
| lineStyle = labelLineControl.lineStyle || lineStyle; |
| |
| labelControl = itemStyle[status].label || labelControl; |
| labelControl.position = labelControl.position; |
| if (labelControl.position == 'inner' || labelControl.position == 'inside') { |
| // 内部 |
| xEnd = x + topWidth / 2; |
| } |
| else if (labelControl.position == 'left'){ |
| // 左侧显示 |
| xEnd = lineLength == 'auto' |
| ? (location.x - 10) |
| : (location.centerX - Math.max(topWidth, bottomWidth) / 2 - lineLength); |
| } |
| else { |
| // 右侧显示,默认 labelControl.position == 'outer' || 'right) |
| xEnd = lineLength == 'auto' |
| ? (location.x + location.width + 10) |
| : (location.centerX + Math.max(topWidth, bottomWidth) / 2 + lineLength); |
| } |
| lineShape.highlightStyle = { |
| xEnd : xEnd, |
| strokeColor : lineStyle.color || defaultColor, |
| lineType : lineStyle.type, |
| lineWidth : lineStyle.width |
| }; |
| |
| return new LineShape(lineShape); |
| }, |
| |
| /** |
| * 返回特定状态(normal or emphasis)下是否需要显示label标签文本 |
| * @param {Object} serie |
| * @param {Object} data |
| * @param {boolean} isEmphasis true is 'emphasis' and false is 'normal' |
| */ |
| _needLabel : function (serie, data, isEmphasis) { |
| return this.deepQuery( |
| [data, serie], |
| 'itemStyle.' |
| + (isEmphasis ? 'emphasis' : 'normal') |
| + '.label.show' |
| ); |
| }, |
| |
| /** |
| * 返回特定状态(normal or emphasis)下是否需要显示labelLine标签视觉引导线 |
| * @param {Object} serie |
| * @param {Object} data |
| * @param {boolean} isEmphasis true is 'emphasis' and false is 'normal' |
| */ |
| _needLabelLine : function (serie, data, isEmphasis) { |
| return this.deepQuery( |
| [data, serie], |
| 'itemStyle.' |
| + (isEmphasis ? 'emphasis' : 'normal') |
| +'.labelLine.show' |
| ); |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| } |
| }; |
| |
| zrUtil.inherits(Funnel, ChartBase); |
| zrUtil.inherits(Funnel, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('funnel', Funnel); |
| |
| return Funnel; |
| }); |
| /** |
| * echarts组件: 类目轴 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/categoryAxis',['require','./base','zrender/shape/Text','zrender/shape/Line','zrender/shape/Rectangle','../config','zrender/tool/util','zrender/tool/area','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrArea = require('zrender/tool/area'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 类目轴参数 |
| * @param {Grid} component 组件 |
| */ |
| function CategoryAxis(ecTheme, messageCenter, zr, option, myChart, axisBase) { |
| if (option.data.length < 1) { |
| console.error('option.data.length < 1.'); |
| return; |
| } |
| |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.grid = this.component.grid; |
| |
| for (var method in axisBase) { |
| this[method] = axisBase[method]; |
| } |
| |
| this.refresh(option); |
| } |
| |
| CategoryAxis.prototype = { |
| type : ecConfig.COMPONENT_TYPE_AXIS_CATEGORY, |
| _getReformedLabel : function (idx) { |
| var data = typeof this.option.data[idx].value != 'undefined' |
| ? this.option.data[idx].value |
| : this.option.data[idx]; |
| var formatter = this.option.data[idx].formatter |
| || this.option.axisLabel.formatter; |
| if (formatter) { |
| if (typeof formatter == 'function') { |
| data = formatter.call(this.myChart, data); |
| } |
| else if (typeof formatter == 'string') { |
| data = formatter.replace('{value}', data); |
| } |
| } |
| return data; |
| }, |
| |
| /** |
| * 计算标签显示挑选间隔 |
| */ |
| _getInterval : function () { |
| var interval = this.option.axisLabel.interval; |
| if (interval == 'auto') { |
| // 麻烦的自适应计算 |
| var fontSize = this.option.axisLabel.textStyle.fontSize; |
| var data = this.option.data; |
| var dataLength = this.option.data.length; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| if (dataLength > 3) { |
| var gap = this.getGap(); |
| var isEnough = false; |
| var labelSpace; |
| var labelSize; |
| var step = Math.floor(0.5 / gap); |
| step = step < 1 ? 1 : step; |
| interval = Math.floor(15 / gap); |
| while (!isEnough && interval < dataLength) { |
| interval += step; |
| isEnough = true; |
| labelSpace = Math.floor(gap * interval); // 标签左右至少间隔为3px |
| for (var i = Math.floor((dataLength - 1)/ interval) * interval; |
| i >= 0; i -= interval |
| ) { |
| if (this.option.axisLabel.rotate !== 0) { |
| // 有旋转 |
| labelSize = fontSize; |
| } |
| else if (data[i].textStyle) { |
| labelSize = zrArea.getTextWidth( |
| this._getReformedLabel(i), |
| this.getFont( |
| zrUtil.merge( |
| data[i].textStyle, |
| this.option.axisLabel.textStyle |
| ) |
| ) |
| ); |
| } |
| else { |
| /* |
| labelSize = zrArea.getTextWidth( |
| this._getReformedLabel(i), |
| font |
| ); |
| */ |
| // 不定义data级特殊文本样式,用fontSize优化getTextWidth |
| var label = this._getReformedLabel(i) + ''; |
| var wLen = (label.match(/\w/g) || '').length; |
| var oLen = label.length - wLen; |
| labelSize = wLen * fontSize * 2 / 3 + oLen * fontSize; |
| } |
| |
| if (labelSpace < labelSize) { |
| // 放不下,中断循环让interval++ |
| isEnough = false; |
| break; |
| } |
| } |
| } |
| } |
| else { |
| // 少于3个则全部显示 |
| interval = 1; |
| } |
| } |
| else { |
| // 纵向 |
| if (dataLength > 3) { |
| var gap = this.getGap(); |
| interval = Math.floor(11 / gap); |
| // 标签上下至少间隔为3px |
| while ((gap * interval - 6) < fontSize |
| && interval < dataLength |
| ) { |
| interval++; |
| } |
| } |
| else { |
| // 少于3个则全部显示 |
| interval = 1; |
| } |
| } |
| } |
| else { |
| // 用户自定义间隔 |
| interval = interval - 0 + 1; |
| } |
| |
| return interval; |
| }, |
| |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| // 标签显示的挑选间隔 |
| this._interval = this._getInterval(); |
| |
| this.option.splitArea.show && this._buildSplitArea(); |
| this.option.splitLine.show && this._buildSplitLine(); |
| this.option.axisLine.show && this._buildAxisLine(); |
| this.option.axisTick.show && this._buildAxisTick(); |
| this.option.axisLabel.show && this._buildAxisLabel(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| }, |
| |
| // 小标记 |
| _buildAxisTick : function () { |
| var axShape; |
| //var data = this.option.data; |
| var dataLength = this.option.data.length; |
| var tickOption = this.option.axisTick; |
| var length = tickOption.length; |
| var color = tickOption.lineStyle.color; |
| var lineWidth = tickOption.lineStyle.width; |
| var interval = tickOption.interval == 'auto' |
| ? this._interval : (tickOption.interval - 0 + 1); |
| var onGap = tickOption.onGap; |
| var optGap = onGap |
| ? (this.getGap() / 2) |
| : typeof onGap == 'undefined' |
| ? (this.option.boundaryGap ? (this.getGap() / 2) : 0) |
| : 0; |
| var startIndex = optGap > 0 ? -interval : 0; |
| if (this.isHorizontal()) { |
| // 横向 |
| var yPosition = this.option.position == 'bottom' |
| ? (tickOption.inside |
| ? (this.grid.getYend() - length - 1) : (this.grid.getYend() + 1)) |
| : (tickOption.inside |
| ? (this.grid.getY() + 1) : (this.grid.getY() - length - 1)); |
| var x; |
| for (var i = startIndex; i < dataLength; i += interval) { |
| // 亚像素优化 |
| x = this.subPixelOptimize( |
| this.getCoordByIndex(i) + (i >= 0 ? optGap : 0), lineWidth |
| ); |
| axShape = { |
| _axisShape : 'axisTick', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : x, |
| yStart : yPosition, |
| xEnd : x, |
| yEnd : yPosition + length, |
| strokeColor : color, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| } |
| else { |
| // 纵向 |
| var xPosition = this.option.position == 'left' |
| ? (tickOption.inside |
| ? (this.grid.getX() + 1) : (this.grid.getX() - length - 1)) |
| : (tickOption.inside |
| ? (this.grid.getXend() - length - 1) : (this.grid.getXend() + 1)); |
| |
| var y; |
| for (var i = startIndex; i < dataLength; i += interval) { |
| // 亚像素优化 |
| y = this.subPixelOptimize( |
| this.getCoordByIndex(i) - (i >= 0 ? optGap : 0), lineWidth |
| ); |
| axShape = { |
| _axisShape : 'axisTick', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : xPosition, |
| yStart : y, |
| xEnd : xPosition + length, |
| yEnd : y, |
| strokeColor : color, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| } |
| }, |
| |
| // 坐标轴文本 |
| _buildAxisLabel : function () { |
| var axShape; |
| var data = this.option.data; |
| var dataLength = this.option.data.length; |
| var rotate = this.option.axisLabel.rotate; |
| var margin = this.option.axisLabel.margin; |
| var clickable = this.option.axisLabel.clickable; |
| var textStyle = this.option.axisLabel.textStyle; |
| var dataTextStyle; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| var yPosition; |
| var baseLine; |
| if (this.option.position == 'bottom') { |
| yPosition = this.grid.getYend() + margin; |
| baseLine = 'top'; |
| } |
| else { |
| yPosition = this.grid.getY() - margin; |
| baseLine = 'bottom'; |
| } |
| |
| for (var i = 0; i < dataLength; i += this._interval) { |
| if (this._getReformedLabel(i) === '') { |
| // 空文本优化 |
| continue; |
| } |
| dataTextStyle = zrUtil.merge( |
| data[i].textStyle || {}, |
| textStyle |
| ); |
| axShape = { |
| // shape : 'text', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : this.getCoordByIndex(i), |
| y : yPosition, |
| color : dataTextStyle.color, |
| text : this._getReformedLabel(i), |
| textFont : this.getFont(dataTextStyle), |
| textAlign : dataTextStyle.align || 'center', |
| textBaseline : dataTextStyle.baseline || baseLine |
| } |
| }; |
| if (rotate) { |
| axShape.style.textAlign = rotate > 0 |
| ? (this.option.position == 'bottom' |
| ? 'right' : 'left') |
| : (this.option.position == 'bottom' |
| ? 'left' : 'right'); |
| axShape.rotation = [ |
| rotate * Math.PI / 180, |
| axShape.style.x, |
| axShape.style.y |
| ]; |
| } |
| this.shapeList.push(new TextShape( |
| this._axisLabelClickable(clickable, axShape) |
| )); |
| } |
| } |
| else { |
| // 纵向 |
| var xPosition; |
| var align; |
| if (this.option.position == 'left') { |
| xPosition = this.grid.getX() - margin; |
| align = 'right'; |
| } |
| else { |
| xPosition = this.grid.getXend() + margin; |
| align = 'left'; |
| } |
| |
| for (var i = 0; i < dataLength; i += this._interval) { |
| if (this._getReformedLabel(i) === '') { |
| // 空文本优化 |
| continue; |
| } |
| dataTextStyle = zrUtil.merge( |
| data[i].textStyle || {}, |
| textStyle |
| ); |
| axShape = { |
| // shape : 'text', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : xPosition, |
| y : this.getCoordByIndex(i), |
| color : dataTextStyle.color, |
| text : this._getReformedLabel(i), |
| textFont : this.getFont(dataTextStyle), |
| textAlign : dataTextStyle.align || align, |
| textBaseline : dataTextStyle.baseline |
| || (i === 0 && this.option.name !== '') |
| ? 'bottom' |
| : (i == (dataLength - 1) |
| && this.option.name !== '') |
| ? 'top' |
| : 'middle' |
| } |
| }; |
| |
| if (rotate) { |
| axShape.rotation = [ |
| rotate * Math.PI / 180, |
| axShape.style.x, |
| axShape.style.y |
| ]; |
| } |
| this.shapeList.push(new TextShape( |
| this._axisLabelClickable(clickable, axShape) |
| )); |
| } |
| } |
| }, |
| |
| _buildSplitLine : function () { |
| var axShape; |
| //var data = this.option.data; |
| var dataLength = this.option.data.length; |
| var sLineOption = this.option.splitLine; |
| var lineType = sLineOption.lineStyle.type; |
| var lineWidth = sLineOption.lineStyle.width; |
| var color = sLineOption.lineStyle.color; |
| color = color instanceof Array ? color : [color]; |
| var colorLength = color.length; |
| |
| var onGap = sLineOption.onGap; |
| var optGap = onGap |
| ? (this.getGap() / 2) |
| : typeof onGap == 'undefined' |
| ? (this.option.boundaryGap ? (this.getGap() / 2) : 0) |
| : 0; |
| dataLength -= (onGap || (typeof onGap == 'undefined' && this.option.boundaryGap)) |
| ? 1 : 0; |
| if (this.isHorizontal()) { |
| // 横向 |
| var sy = this.grid.getY(); |
| var ey = this.grid.getYend(); |
| var x; |
| |
| for (var i = 0; i < dataLength; i += this._interval) { |
| // 亚像素优化 |
| x = this.subPixelOptimize( |
| this.getCoordByIndex(i) + optGap, lineWidth |
| ); |
| axShape = { |
| // shape : 'line', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : x, |
| yStart : sy, |
| xEnd : x, |
| yEnd : ey, |
| strokeColor : color[(i / this._interval) % colorLength], |
| lineType : lineType, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| |
| } |
| else { |
| // 纵向 |
| var sx = this.grid.getX(); |
| var ex = this.grid.getXend(); |
| var y; |
| |
| for (var i = 0; i < dataLength; i += this._interval) { |
| // 亚像素优化 |
| y = this.subPixelOptimize( |
| this.getCoordByIndex(i) - optGap, lineWidth |
| ); |
| axShape = { |
| // shape : 'line', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : sx, |
| yStart : y, |
| xEnd : ex, |
| yEnd : y, |
| strokeColor : color[(i / this._interval) % colorLength], |
| linetype : lineType, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| } |
| }, |
| |
| _buildSplitArea : function () { |
| var axShape; |
| var sAreaOption = this.option.splitArea; |
| var color = sAreaOption.areaStyle.color; |
| if (!(color instanceof Array)) { |
| // 非数组一律认为是单一颜色的字符串,单一颜色则用一个背景,颜色错误不负责啊!!! |
| axShape = { |
| // shape : 'rectangle', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : this.grid.getX(), |
| y : this.grid.getY(), |
| width : this.grid.getWidth(), |
| height : this.grid.getHeight(), |
| color : color |
| // type : this.option.splitArea.areaStyle.type, |
| } |
| }; |
| this.shapeList.push(new RectangleShape(axShape)); |
| } |
| else { |
| // 多颜色 |
| var colorLength = color.length; |
| var dataLength = this.option.data.length; |
| |
| var onGap = sAreaOption.onGap; |
| var optGap = onGap |
| ? (this.getGap() / 2) |
| : typeof onGap == 'undefined' |
| ? (this.option.boundaryGap ? (this.getGap() / 2) : 0) |
| : 0; |
| if (this.isHorizontal()) { |
| // 横向 |
| var y = this.grid.getY(); |
| var height = this.grid.getHeight(); |
| var lastX = this.grid.getX(); |
| var curX; |
| |
| for (var i = 0; i <= dataLength; i += this._interval) { |
| curX = i < dataLength |
| ? (this.getCoordByIndex(i) + optGap) |
| : this.grid.getXend(); |
| axShape = { |
| // shape : 'rectangle', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : lastX, |
| y : y, |
| width : curX - lastX, |
| height : height, |
| color : color[(i / this._interval) % colorLength] |
| // type : this.option.splitArea.areaStyle.type, |
| } |
| }; |
| this.shapeList.push(new RectangleShape(axShape)); |
| lastX = curX; |
| } |
| } |
| else { |
| // 纵向 |
| var x = this.grid.getX(); |
| var width = this.grid.getWidth(); |
| var lastYend = this.grid.getYend(); |
| var curY; |
| |
| for (var i = 0; i <= dataLength; i += this._interval) { |
| curY = i < dataLength |
| ? (this.getCoordByIndex(i) - optGap) |
| : this.grid.getY(); |
| axShape = { |
| // shape : 'rectangle', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : x, |
| y : curY, |
| width : width, |
| height : lastYend - curY, |
| color : color[(i / this._interval) % colorLength] |
| // type : this.option.splitArea.areaStyle.type |
| } |
| }; |
| this.shapeList.push(new RectangleShape(axShape)); |
| lastYend = curY; |
| } |
| } |
| } |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = this.reformOption(newOption); |
| // 通用字体设置 |
| this.option.axisLabel.textStyle = zrUtil.merge( |
| this.option.axisLabel.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| } |
| this.clear(); |
| this._buildShape(); |
| }, |
| |
| /** |
| * 返回间隔 |
| */ |
| getGap : function () { |
| var dataLength = this.option.data.length; |
| var total = this.isHorizontal() |
| ? this.grid.getWidth() |
| : this.grid.getHeight(); |
| if (this.option.boundaryGap) { // 留空 |
| return total / dataLength; |
| } |
| else { // 顶头 |
| return total / (dataLength > 1 ? (dataLength - 1) : 1); |
| } |
| }, |
| |
| // 根据值换算位置 |
| getCoord : function (value) { |
| var data = this.option.data; |
| var dataLength = data.length; |
| var gap = this.getGap(); |
| var position = this.option.boundaryGap ? (gap / 2) : 0; |
| |
| for (var i = 0; i < dataLength; i++) { |
| if (data[i] == value |
| || (typeof data[i].value != 'undefined' |
| && data[i].value == value) |
| ) { |
| if (this.isHorizontal()) { |
| // 横向 |
| position = this.grid.getX() + position; |
| } |
| else { |
| // 纵向 |
| position = this.grid.getYend() - position; |
| } |
| |
| return position; |
| // Math.floor可能引起一些偏差,但性能会更好 |
| /* 准确更重要 |
| return (i === 0 || i == dataLength - 1) |
| ? position |
| : Math.floor(position); |
| */ |
| } |
| position += gap; |
| } |
| }, |
| |
| // 根据类目轴数据索引换算位置 |
| getCoordByIndex : function (dataIndex) { |
| if (dataIndex < 0) { |
| if (this.isHorizontal()) { |
| return this.grid.getX(); |
| } |
| else { |
| return this.grid.getYend(); |
| } |
| } |
| else if (dataIndex > this.option.data.length - 1) { |
| if (this.isHorizontal()) { |
| return this.grid.getXend(); |
| } |
| else { |
| return this.grid.getY(); |
| } |
| } |
| else { |
| var gap = this.getGap(); |
| var position = this.option.boundaryGap ? (gap / 2) : 0; |
| position += dataIndex * gap; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| position = this.grid.getX() + position; |
| } |
| else { |
| // 纵向 |
| position = this.grid.getYend() - position; |
| } |
| |
| return position; |
| /* 准确更重要 |
| return (dataIndex === 0 || dataIndex == this.option.data.length - 1) |
| ? position |
| : Math.floor(position); |
| */ |
| } |
| }, |
| |
| // 根据类目轴数据索引换算类目轴名称 |
| getNameByIndex : function (dataIndex) { |
| var data = this.option.data[dataIndex]; |
| if (typeof data != 'undefined' && typeof data.value != 'undefined') |
| { |
| return data.value; |
| } |
| else { |
| return data; |
| } |
| }, |
| |
| // 根据类目轴名称换算类目轴数据索引 |
| getIndexByName : function (name) { |
| var data = this.option.data; |
| var dataLength = data.length; |
| |
| for (var i = 0; i < dataLength; i++) { |
| if (data[i] == name |
| || (typeof data[i].value != 'undefined' |
| && data[i].value == name) |
| ) { |
| return i; |
| } |
| } |
| |
| return -1; |
| }, |
| |
| // 根据位置换算值 |
| getValueFromCoord : function() { |
| return ''; |
| }, |
| |
| /** |
| * 根据类目轴数据索引返回是否为主轴线 |
| * @param {number} dataIndex 类目轴数据索引 |
| * @return {boolean} 是否为主轴 |
| */ |
| isMainAxis : function (dataIndex) { |
| return dataIndex % this._interval === 0; |
| } |
| }; |
| |
| zrUtil.inherits(CategoryAxis, Base); |
| |
| require('../component').define('categoryAxis', CategoryAxis); |
| |
| return CategoryAxis; |
| }); |
| /** |
| * echarts组件: 数值轴 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/valueAxis',['require','./base','zrender/shape/Text','zrender/shape/Line','zrender/shape/Rectangle','../config','zrender/tool/util','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 类目轴参数 |
| * @param {Object} component 组件 |
| * @param {Array} series 数据对象 |
| */ |
| function ValueAxis(ecTheme, messageCenter, zr, option, myChart, axisBase, series) { |
| if (!series || series.length === 0) { |
| console.err('option.series.length == 0.'); |
| return; |
| } |
| |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.series = series; |
| this.grid = this.component.grid; |
| |
| for (var method in axisBase) { |
| this[method] = axisBase[method]; |
| } |
| |
| this.refresh(option, series); |
| } |
| |
| ValueAxis.prototype = { |
| type : ecConfig.COMPONENT_TYPE_AXIS_VALUE, |
| _buildShape : function () { |
| this._hasData = false; |
| this._calculateValue(); |
| if (!this._hasData) { |
| return; |
| } |
| this.option.splitArea.show && this._buildSplitArea(); |
| this.option.splitLine.show && this._buildSplitLine(); |
| this.option.axisLine.show && this._buildAxisLine(); |
| this.option.axisTick.show && this._buildAxisTick(); |
| this.option.axisLabel.show && this._buildAxisLabel(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| }, |
| |
| // 小标记 |
| _buildAxisTick : function () { |
| var axShape; |
| var data = this._valueList; |
| var dataLength = this._valueList.length; |
| var tickOption = this.option.axisTick; |
| var length = tickOption.length; |
| var color = tickOption.lineStyle.color; |
| var lineWidth = tickOption.lineStyle.width; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| var yPosition = this.option.position == 'bottom' |
| ? (tickOption.inside |
| ? (this.grid.getYend() - length - 1) : (this.grid.getYend()) + 1) |
| : (tickOption.inside |
| ? (this.grid.getY() + 1) : (this.grid.getY() - length - 1)); |
| var x; |
| for (var i = 0; i < dataLength; i++) { |
| // 亚像素优化 |
| x = this.subPixelOptimize(this.getCoord(data[i]), lineWidth); |
| axShape = { |
| _axisShape : 'axisTick', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : x, |
| yStart : yPosition, |
| xEnd : x, |
| yEnd : yPosition + length, |
| strokeColor : color, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| } |
| else { |
| // 纵向 |
| var xPosition = this.option.position == 'left' |
| ? (tickOption.inside |
| ? (this.grid.getX() + 1) : (this.grid.getX() - length - 1)) |
| : (tickOption.inside |
| ? (this.grid.getXend() - length - 1) : (this.grid.getXend() + 1)); |
| |
| var y; |
| for (var i = 0; i < dataLength; i++) { |
| // 亚像素优化 |
| y = this.subPixelOptimize(this.getCoord(data[i]), lineWidth); |
| axShape = { |
| _axisShape : 'axisTick', |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : xPosition, |
| yStart : y, |
| xEnd : xPosition + length, |
| yEnd : y, |
| strokeColor : color, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| } |
| }, |
| |
| // 坐标轴文本 |
| _buildAxisLabel : function () { |
| var axShape; |
| var data = this._valueList; |
| var dataLength = this._valueList.length; |
| var rotate = this.option.axisLabel.rotate; |
| var margin = this.option.axisLabel.margin; |
| var clickable = this.option.axisLabel.clickable; |
| var textStyle = this.option.axisLabel.textStyle; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| var yPosition; |
| var baseLine; |
| if (this.option.position == 'bottom') { |
| yPosition = this.grid.getYend() + margin; |
| baseLine = 'top'; |
| } |
| else { |
| yPosition = this.grid.getY() - margin; |
| baseLine = 'bottom'; |
| } |
| |
| for (var i = 0; i < dataLength; i++) { |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : this.getCoord(data[i]), |
| y : yPosition, |
| color : typeof textStyle.color == 'function' |
| ? textStyle.color(data[i]) : textStyle.color, |
| text : this._valueLabel[i], |
| textFont : this.getFont(textStyle), |
| textAlign : textStyle.align || 'center', |
| textBaseline : textStyle.baseline || baseLine |
| } |
| }; |
| if (rotate) { |
| axShape.style.textAlign = rotate > 0 |
| ? (this.option.position == 'bottom' |
| ? 'right' : 'left') |
| : (this.option.position == 'bottom' |
| ? 'left' : 'right'); |
| axShape.rotation = [ |
| rotate * Math.PI / 180, |
| axShape.style.x, |
| axShape.style.y |
| ]; |
| } |
| this.shapeList.push(new TextShape( |
| this._axisLabelClickable(clickable, axShape) |
| )); |
| } |
| } |
| else { |
| // 纵向 |
| var xPosition; |
| var align; |
| if (this.option.position == 'left') { |
| xPosition = this.grid.getX() - margin; |
| align = 'right'; |
| } |
| else { |
| xPosition = this.grid.getXend() + margin; |
| align = 'left'; |
| } |
| |
| for (var i = 0; i < dataLength; i++) { |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : xPosition, |
| y : this.getCoord(data[i]), |
| color : typeof textStyle.color == 'function' |
| ? textStyle.color(data[i]) : textStyle.color, |
| text : this._valueLabel[i], |
| textFont : this.getFont(textStyle), |
| textAlign : textStyle.align || align, |
| textBaseline : textStyle.baseline |
| || (i === 0 && this.option.name !== '') |
| ? 'bottom' |
| : (i == (dataLength - 1) |
| && this.option.name !== '') |
| ? 'top' |
| : 'middle' |
| } |
| }; |
| |
| if (rotate) { |
| axShape.rotation = [ |
| rotate * Math.PI / 180, |
| axShape.style.x, |
| axShape.style.y |
| ]; |
| } |
| this.shapeList.push(new TextShape( |
| this._axisLabelClickable(clickable, axShape) |
| )); |
| } |
| } |
| }, |
| |
| _buildSplitLine : function () { |
| var axShape; |
| var data = this._valueList; |
| var dataLength = this._valueList.length; |
| var sLineOption = this.option.splitLine; |
| var lineType = sLineOption.lineStyle.type; |
| var lineWidth = sLineOption.lineStyle.width; |
| var color = sLineOption.lineStyle.color; |
| color = color instanceof Array ? color : [color]; |
| var colorLength = color.length; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| var sy = this.grid.getY(); |
| var ey = this.grid.getYend(); |
| var x; |
| |
| for (var i = 0; i < dataLength; i++) { |
| // 亚像素优化 |
| x = this.subPixelOptimize(this.getCoord(data[i]), lineWidth); |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : x, |
| yStart : sy, |
| xEnd : x, |
| yEnd : ey, |
| strokeColor : color[i % colorLength], |
| lineType : lineType, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| |
| } |
| else { |
| // 纵向 |
| var sx = this.grid.getX(); |
| var ex = this.grid.getXend(); |
| var y; |
| |
| for (var i = 0; i < dataLength; i++) { |
| // 亚像素优化 |
| y = this.subPixelOptimize(this.getCoord(data[i]), lineWidth); |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| xStart : sx, |
| yStart : y, |
| xEnd : ex, |
| yEnd : y, |
| strokeColor : color[i % colorLength], |
| lineType : lineType, |
| lineWidth : lineWidth |
| } |
| }; |
| this.shapeList.push(new LineShape(axShape)); |
| } |
| } |
| }, |
| |
| _buildSplitArea : function () { |
| var axShape; |
| var color = this.option.splitArea.areaStyle.color; |
| |
| if (!(color instanceof Array)) { |
| // 非数组一律认为是单一颜色的字符串,单一颜色则用一个背景,颜色错误不负责啊!!! |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : this.grid.getX(), |
| y : this.grid.getY(), |
| width : this.grid.getWidth(), |
| height : this.grid.getHeight(), |
| color : color |
| // type : this.option.splitArea.areaStyle.type, |
| } |
| }; |
| this.shapeList.push(new RectangleShape(axShape)); |
| } |
| else { |
| // 多颜色 |
| var colorLength = color.length; |
| var data = this._valueList; |
| var dataLength = this._valueList.length; |
| |
| if (this.isHorizontal()) { |
| // 横向 |
| var y = this.grid.getY(); |
| var height = this.grid.getHeight(); |
| var lastX = this.grid.getX(); |
| var curX; |
| |
| for (var i = 0; i <= dataLength; i++) { |
| curX = i < dataLength |
| ? this.getCoord(data[i]) |
| : this.grid.getXend(); |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : lastX, |
| y : y, |
| width : curX - lastX, |
| height : height, |
| color : color[i % colorLength] |
| // type : this.option.splitArea.areaStyle.type, |
| } |
| }; |
| this.shapeList.push(new RectangleShape(axShape)); |
| lastX = curX; |
| } |
| } |
| else { |
| // 纵向 |
| var x = this.grid.getX(); |
| var width = this.grid.getWidth(); |
| var lastYend = this.grid.getYend(); |
| var curY; |
| |
| for (var i = 0; i <= dataLength; i++) { |
| curY = i < dataLength |
| ? this.getCoord(data[i]) |
| : this.grid.getY(); |
| axShape = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : x, |
| y : curY, |
| width : width, |
| height : lastYend - curY, |
| color : color[i % colorLength] |
| // type : this.option.splitArea.areaStyle.type |
| } |
| }; |
| this.shapeList.push(new RectangleShape(axShape)); |
| lastYend = curY; |
| } |
| } |
| } |
| }, |
| |
| /** |
| * 极值计算 |
| */ |
| _calculateValue : function () { |
| if (isNaN(this.option.min - 0) || isNaN(this.option.max - 0)) { |
| // 有一个没指定都得算 |
| // 数据整形 |
| var oriData; // 原始数据 |
| var data = {}; // 整形后数据抽取 |
| var value; |
| var xIdx; |
| var yIdx; |
| var legend = this.component.legend; |
| for (var i = 0, l = this.series.length; i < l; i++) { |
| if (this.series[i].type != ecConfig.CHART_TYPE_LINE |
| && this.series[i].type != ecConfig.CHART_TYPE_BAR |
| && this.series[i].type != ecConfig.CHART_TYPE_SCATTER |
| && this.series[i].type != ecConfig.CHART_TYPE_K |
| ) { |
| // 非坐标轴支持的不算极值 |
| continue; |
| } |
| // 请允许我写开,跟上面一个不是一样东西 |
| if (legend && !legend.isSelected(this.series[i].name)){ |
| continue; |
| } |
| |
| // 不指定默认为第一轴线 |
| xIdx = this.series[i].xAxisIndex || 0; |
| yIdx = this.series[i].yAxisIndex || 0; |
| if ((this.option.xAxisIndex != xIdx) |
| && (this.option.yAxisIndex != yIdx) |
| ) { |
| // 不是自己的数据不计算极值 |
| continue; |
| } |
| |
| var key = this.series[i].name || 'kener'; |
| if (!this.series[i].stack) { |
| data[key] = data[key] || []; |
| oriData = this.series[i].data; |
| for (var j = 0, k = oriData.length; j < k; j++) { |
| value = typeof oriData[j].value != 'undefined' |
| ? oriData[j].value |
| : oriData[j]; |
| if (this.series[i].type == ecConfig.CHART_TYPE_SCATTER) { |
| if (this.option.xAxisIndex != -1) { |
| data[key].push(value[0]); |
| } |
| if (this.option.yAxisIndex != -1) { |
| data[key].push(value[1]); |
| } |
| } |
| else if (this.series[i].type == ecConfig.CHART_TYPE_K) { |
| data[key].push(value[0]); |
| data[key].push(value[1]); |
| data[key].push(value[2]); |
| data[key].push(value[3]); |
| } |
| else { |
| data[key].push(value); |
| } |
| } |
| } |
| else { |
| // 堆积数据,需要区分正负向堆积 |
| var keyP = '__Magic_Key_Positive__' + this.series[i].stack; |
| var keyN = '__Magic_Key_Negative__' + this.series[i].stack; |
| data[keyP] = data[keyP] || []; |
| data[keyN] = data[keyN] || []; |
| data[key] = data[key] || []; // scale下还需要记录每一个量 |
| oriData = this.series[i].data; |
| for (var j = 0, k = oriData.length; j < k; j++) { |
| value = typeof oriData[j].value != 'undefined' |
| ? oriData[j].value |
| : oriData[j]; |
| if (value == '-') { |
| continue; |
| } |
| value = value - 0; |
| if (value >= 0) { |
| if (typeof data[keyP][j] != 'undefined') { |
| data[keyP][j] += value; |
| } |
| else { |
| data[keyP][j] = value; |
| } |
| } |
| else { |
| if (typeof data[keyN][j] != 'undefined') { |
| data[keyN][j] += value; |
| } |
| else { |
| data[keyN][j] = value; |
| } |
| } |
| if (this.option.scale) { |
| data[key].push(value); |
| } |
| } |
| } |
| } |
| // 找极值 |
| for (var i in data){ |
| oriData = data[i]; |
| for (var j = 0, k = oriData.length; j < k; j++) { |
| if (!isNaN(oriData[j])){ |
| this._hasData = true; |
| this._min = oriData[j]; |
| this._max = oriData[j]; |
| break; |
| } |
| } |
| if (this._hasData) { |
| break; |
| } |
| } |
| for (var i in data){ |
| oriData = data[i]; |
| for (var j = 0, k = oriData.length; j < k; j++) { |
| if (!isNaN(oriData[j])){ |
| this._min = Math.min(this._min, oriData[j]); |
| this._max = Math.max(this._max, oriData[j]); |
| } |
| } |
| } |
| |
| //console.log(this._min,this._max,'vvvvv111111') |
| this._min = isNaN(this.option.min - 0) |
| ? (this._min - Math.abs(this._min * this.option.boundaryGap[0])) |
| : (this.option.min - 0); // 指定min忽略boundaryGay[0] |
| |
| this._max = isNaN(this.option.max - 0) |
| ? (this._max + Math.abs(this._max * this.option.boundaryGap[1])) |
| : (this.option.max - 0); // 指定max忽略boundaryGay[1] |
| if (this._min == this._max) { |
| if (this._max === 0) { |
| // 修复全0数据 |
| this._max = this.option.power > 0 ? this.option.power : 1; |
| } |
| // 修复最大值==最小值时数据整形 |
| else if (this._max > 0) { |
| this._min = this._max / this.option.splitNumber; |
| } |
| else { // this._max < 0 |
| this._max = this._max / this.option.splitNumber; |
| } |
| } |
| this._reformValue(this.option.scale); |
| } |
| else { |
| this._hasData = true; |
| // 用户指定min max就不多管闲事了 |
| this._min = this.option.min - 0; // 指定min忽略boundaryGay[0] |
| this._max = this.option.max - 0; // 指定max忽略boundaryGay[1] |
| this._customerValue(); |
| } |
| }, |
| |
| /** |
| * 找到原始数据的极值后根据选项整形最终 this._min / this._max / this._valueList |
| * 如果你不知道这个“整形”的用义,请不要试图去理解和修改这个方法!找我也没用,我相信我已经记不起来! |
| * 如果你有更简洁的数学推导欢迎重写,后果自负~ |
| * 一旦你不得不遇到了需要修改或重写的厄运,希望下面的脚手架能帮助你 |
| * ps:其实我是想说别搞砸了!升级后至少得保证这些case通过!! |
| * |
| * by linzhifeng@baidu.com 2013-1-8 |
| * -------- |
| this._valueList = []; |
| this.option = {splitNumber:5,power:100,precision:0}; |
| this._min = 1; this._max = 123; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : 0 150 [0, 30, 60, 90, 120, 150]', |
| (this._min == 0 && this._max == 150) ? 'success' : 'failed'); |
| |
| this._min = 10; this._max = 1923; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : 0 2000 [0, 400, 800, 1200, 1600, 2000]', |
| (this._min == 0 && this._max == 2000) ? 'success' : 'failed'); |
| |
| this._min = 10; this._max = 78; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : 0 100 [0, 20, 40, 60, 80, 100]', |
| (this._min == 0 && this._max == 100) ? 'success' : 'failed'); |
| |
| this._min = -31; this._max = -3; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -35 0 [-35, -28, -21, -14, -7, 0]', |
| (this._min == -35 && this._max == 0) ? 'success' : 'failed'); |
| |
| this._min = -51; this._max = 203; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -60 240 [-60, 0, 60, 120, 180, 240]', |
| (this._min == -60 && this._max == 240) ? 'success' : 'failed'); |
| |
| this._min = -251; this._max = 23; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -280 70 [-280, -210, -140, -70, 0, 70]', |
| (this._min == -280 && this._max == 70) ? 'success' : 'failed'); |
| |
| this.option.precision = 2; |
| this._min = 0.23; this._max = 0.78; console.log(this._min, this._max); this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : 0.00 1.00' |
| + '["0.00", "0.20", "0.40", "0.60", "0.80", "1.00"]', |
| (this._min == 0.00 && this._max == 1.00) ? 'success' : 'failed'); |
| |
| this._min = -12.23; this._max = -0.78; console.log(this._min, this._max); |
| this._reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -15.00 0.00' |
| + '["-15.00", "-12.00", "-9.00", "-6.00", "-3.00", "0.00"]', |
| (this._min == -15.00 && this._max == 0.00) ? 'success' : 'failed'); |
| |
| this._min = -0.23; this._max = 0.78; console.log(this._min, this._max); this._reformValue() |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -0.30 1.20' |
| + '["-0.30", "0.00", "0.30", "0.60", "0.90", "1.20"]', |
| (this._min == -0.30 && this._max == 1.20) ? 'success' : 'failed'); |
| |
| this._min = -1.23; this._max = 0.78; console.log(this._min, this._max); _reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -1.50 1.00' |
| + '["-1.50", "-1.00", "-0.50", "0.00", "0.50", "1.00"]', |
| (this._min == -1.50 && this._max == 1.00) ? 'success' : 'failed'); |
| |
| this.option.precision = 1; |
| this._min = -2.3; this._max = 0.5; console.log(this._min, this._max); _reformValue(); |
| console.log('result is :', this._min, this._max, this._valueList); |
| console.log('should be : -2.4 0.6' |
| + '["-2.4", "-1.8", "-1.2", "-0.6", "0.0", "0.6"]', |
| (this._min == -2.4 && this._max == 0.6) ? 'success' : 'failed'); |
| * -------- |
| */ |
| _reformValue : function (scale) { |
| var splitNumber = this.option.splitNumber; |
| var precision = this.option.precision; |
| var splitGap; |
| var power; |
| if (precision === 0) { // 整数 |
| power = this.option.power > 1 ? this.option.power : 1; |
| } |
| else { // 小数 |
| // 放大倍数后复用整数逻辑,最后再缩小回去 |
| power = Math.pow(10, precision); |
| this._min *= power; |
| this._max *= power; |
| power = this.option.power; |
| } |
| // console.log(this._min,this._max) |
| var total; |
| if (this._min >= 0 && this._max >= 0) { |
| // 双正 |
| if (!scale) { |
| // power自动降级 |
| while ((this._max / power < splitNumber) && power != 1) { |
| power = power / 10; |
| } |
| this._min = 0; |
| } |
| else { |
| // power自动降级 |
| while (this._min < power && power != 1) { |
| power = power / 10; |
| } |
| if (precision === 0) { // 整数 |
| // 满足power |
| this._min = Math.floor(this._min / power) * power; |
| this._max = Math.ceil(this._max / power) * power; |
| } |
| } |
| power = power > 1 ? power / 10 : 1; |
| total = this._max - this._min; |
| splitGap = Math.ceil((total / splitNumber) / power) * power; |
| this._max = this._min + splitGap * splitNumber; |
| } |
| else if (this._min <= 0 && this._max <= 0) { |
| // 双负 |
| power = -power; |
| if (!scale) { |
| // power自动降级 |
| while ((this._min / power < splitNumber) && power != -1) { |
| power = power / 10; |
| } |
| this._max = 0; |
| } |
| else { |
| // power自动降级 |
| while (this._max > power && power != -1) { |
| power = power / 10; |
| } |
| if (precision === 0) { // 整数 |
| // 满足power |
| this._min = Math.ceil(this._min / power) * power; |
| this._max = Math.floor(this._max / power) * power; |
| } |
| } |
| power = power < -1 ? power / 10 : -1; |
| total = this._min - this._max; |
| splitGap = -Math.ceil((total / splitNumber) / power) * power; |
| this._min = -splitGap * splitNumber + this._max; |
| } |
| else { |
| // 一正一负,确保0被选中 |
| total = this._max - this._min; |
| // power自动降级 |
| while ((total / power < splitNumber) && power != 1) { |
| power = power/10; |
| } |
| // 正数部分的分隔数 |
| var partSplitNumber = Math.round(this._max / total * splitNumber); |
| // 修正数据范围极度偏正向,留给负数一个 |
| partSplitNumber -= (partSplitNumber == splitNumber ? 1 : 0); |
| // 修正数据范围极度偏负向,留给正数一个 |
| partSplitNumber += partSplitNumber === 0 ? 1 : 0; |
| splitGap = (Math.ceil(Math.max( |
| this._max / partSplitNumber, |
| this._min / (partSplitNumber - splitNumber) |
| ) |
| / power)) |
| * power; |
| |
| this._max = splitGap * partSplitNumber; |
| this._min = splitGap * (partSplitNumber - splitNumber); |
| } |
| //console.log(this._min,this._max,'vvvvvrrrrrr') |
| this._valueList = []; |
| for (var i = 0; i <= splitNumber; i++) { |
| this._valueList.push(this._min + splitGap * i); |
| } |
| |
| if (precision !== 0) { // 小数 |
| // 放大倍数后复用整数逻辑,最后再缩小回去 |
| power = Math.pow(10, precision); |
| this._min = (this._min / power).toFixed(precision) - 0; |
| this._max = (this._max / power).toFixed(precision) - 0; |
| for (var i = 0; i <= splitNumber; i++) { |
| this._valueList[i] = |
| (this._valueList[i] / power).toFixed(precision) - 0; |
| } |
| } |
| this._reformLabelData(); |
| }, |
| |
| _customerValue : function () { |
| var splitNumber = this.option.splitNumber; |
| var precision = this.option.precision; |
| var splitGap = (this._max - this._min) / splitNumber; |
| |
| this._valueList = []; |
| for (var i = 0; i <= splitNumber; i++) { |
| this._valueList.push((this._min + splitGap * i).toFixed(precision) - 0); |
| } |
| this._reformLabelData(); |
| }, |
| |
| _reformLabelData : function () { |
| this._valueLabel = []; |
| var formatter = this.option.axisLabel.formatter; |
| if (formatter) { |
| for (var i = 0, l = this._valueList.length; i < l; i++) { |
| if (typeof formatter == 'function') { |
| this._valueLabel.push(formatter.call(this.myChart, this._valueList[i])); |
| } |
| else if (typeof formatter == 'string') { |
| this._valueLabel.push( |
| formatter.replace('{value}',this._valueList[i]) |
| ); |
| } |
| } |
| } |
| else { |
| // 每三位默认加,格式化 |
| for (var i = 0, l = this._valueList.length; i < l; i++) { |
| this._valueLabel.push(this.numAddCommas(this._valueList[i])); |
| } |
| } |
| |
| }, |
| |
| getExtremum : function () { |
| this._calculateValue(); |
| return { |
| min: this._min, |
| max: this._max |
| }; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption, newSeries) { |
| if (newOption) { |
| this.option = this.reformOption(newOption); |
| // 通用字体设置 |
| this.option.axisLabel.textStyle = zrUtil.merge( |
| this.option.axisLabel.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| this.series = newSeries; |
| } |
| if (this.zr) { // 数值轴的另外一个功能只是用来计算极值 |
| this.clear(); |
| this._buildShape(); |
| } |
| }, |
| |
| // 根据值换算位置 |
| getCoord : function (value) { |
| value = value < this._min ? this._min : value; |
| value = value > this._max ? this._max : value; |
| |
| var result; |
| if (!this.isHorizontal()) { |
| // 纵向 |
| result = this.grid.getYend() |
| - (value - this._min) |
| / (this._max - this._min) |
| * this.grid.getHeight(); |
| } |
| else { |
| // 横向 |
| result = this.grid.getX() |
| + (value - this._min) |
| / (this._max - this._min) |
| * this.grid.getWidth(); |
| } |
| |
| return result; |
| // Math.floor可能引起一些偏差,但性能会更好 |
| /* 准确更重要 |
| return (value == this._min || value == this._max) |
| ? result |
| : Math.floor(result); |
| */ |
| }, |
| |
| // 根据值换算绝对大小 |
| getCoordSize : function (value) { |
| if (!this.isHorizontal()) { |
| // 纵向 |
| return Math.abs(value / (this._max - this._min) * this.grid.getHeight()); |
| } |
| else { |
| // 横向 |
| return Math.abs(value / (this._max - this._min) * this.grid.getWidth()); |
| } |
| }, |
| |
| // 根据位置换算值 |
| getValueFromCoord : function(coord) { |
| var result; |
| if (!this.isHorizontal()) { |
| // 纵向 |
| coord = coord < this.grid.getY() ? this.grid.getY() : coord; |
| coord = coord > this.grid.getYend() ? this.grid.getYend() : coord; |
| result = this._max |
| - (coord - this.grid.getY()) |
| / this.grid.getHeight() |
| * (this._max - this._min); |
| } |
| else { |
| // 横向 |
| coord = coord < this.grid.getX() ? this.grid.getX() : coord; |
| coord = coord > this.grid.getXend() ? this.grid.getXend() : coord; |
| result = this._min |
| + (coord - this.grid.getX()) |
| / this.grid.getWidth() |
| * (this._max - this._min); |
| } |
| |
| return result.toFixed(2) - 0; |
| } |
| }; |
| |
| zrUtil.inherits(ValueAxis, Base); |
| |
| require('../component').define('valueAxis', ValueAxis); |
| |
| return ValueAxis; |
| }); |
| |
| |
| /** |
| * echarts组件类: 坐标轴 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * 直角坐标系中坐标轴数组,数组中每一项代表一条横轴(纵轴)坐标轴。 |
| * 标准(1.0)中规定最多同时存在2条横轴和2条纵轴 |
| * 单条横轴时可指定安放于grid的底部(默认)或顶部,2条同时存在时则默认第一条安放于底部,第二天安放于顶部 |
| * 单条纵轴时可指定安放于grid的左侧(默认)或右侧,2条同时存在时则默认第一条安放于左侧,第二天安放于右侧。 |
| * 坐标轴有两种类型,类目型和数值型(区别详见axis): |
| * 横轴通常为类目型,但条形图时则横轴为数值型,散点图时则横纵均为数值型 |
| * 纵轴通常为数值型,但条形图时则纵轴为类目型。 |
| * |
| */ |
| define('echarts/component/axis',['require','./base','zrender/shape/Line','../config','../util/ecData','zrender/tool/util','zrender/tool/color','./categoryAxis','./valueAxis','../component'],function (require) { |
| var Base = require('./base'); |
| |
| var LineShape = require('zrender/shape/Line'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表选项 |
| * @param {string=} option.xAxis.type 坐标轴类型,横轴默认为类目型'category' |
| * @param {string=} option.yAxis.type 坐标轴类型,纵轴默认为类目型'value' |
| * @param {Object} component 组件 |
| * @param {string} axisType 横走or纵轴 |
| */ |
| function Axis(ecTheme, messageCenter, zr, option, myChart, axisType) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.axisType = axisType; |
| this._axisList = []; |
| |
| this.refresh(option); |
| } |
| |
| Axis.prototype = { |
| type : ecConfig.COMPONENT_TYPE_AXIS, |
| axisBase : { |
| // 轴线 |
| _buildAxisLine : function () { |
| var lineWidth = this.option.axisLine.lineStyle.width; |
| var halfLineWidth = lineWidth / 2; |
| var axShape = { |
| _axisShape : 'axisLine', |
| zlevel : this._zlevelBase + 1, |
| hoverable : false |
| }; |
| switch (this.option.position) { |
| case 'left' : |
| axShape.style = { |
| xStart : this.grid.getX() - halfLineWidth, |
| yStart : this.grid.getYend(), |
| xEnd : this.grid.getX() - halfLineWidth, |
| yEnd : this.grid.getY(), |
| lineCap : 'round' |
| }; |
| break; |
| case 'right' : |
| axShape.style = { |
| xStart : this.grid.getXend() + halfLineWidth, |
| yStart : this.grid.getYend(), |
| xEnd : this.grid.getXend() + halfLineWidth, |
| yEnd : this.grid.getY(), |
| lineCap : 'round' |
| }; |
| break; |
| case 'bottom' : |
| axShape.style = { |
| xStart : this.grid.getX(), |
| yStart : this.grid.getYend() + halfLineWidth, |
| xEnd : this.grid.getXend(), |
| yEnd : this.grid.getYend() + halfLineWidth, |
| lineCap : 'round' |
| }; |
| break; |
| case 'top' : |
| axShape.style = { |
| xStart : this.grid.getX(), |
| yStart : this.grid.getY() - halfLineWidth, |
| xEnd : this.grid.getXend(), |
| yEnd : this.grid.getY() - halfLineWidth, |
| lineCap : 'round' |
| }; |
| break; |
| } |
| if (this.option.name !== '') { |
| axShape.style.text = this.option.name; |
| axShape.style.textPosition = this.option.nameLocation; |
| axShape.style.textFont = this.getFont(this.option.nameTextStyle); |
| if (this.option.nameTextStyle.align) { |
| axShape.style.textAlign = this.option.nameTextStyle.align; |
| } |
| if (this.option.nameTextStyle.baseline) { |
| axShape.style.textBaseline = this.option.nameTextStyle.baseline; |
| } |
| if (this.option.nameTextStyle.color) { |
| axShape.style.textColor = this.option.nameTextStyle.color; |
| } |
| } |
| axShape.style.strokeColor = this.option.axisLine.lineStyle.color; |
| |
| axShape.style.lineWidth = lineWidth; |
| // 亚像素优化 |
| if (this.isHorizontal()) { |
| // 横向布局,优化y |
| axShape.style.yStart |
| = axShape.style.yEnd |
| = this.subPixelOptimize(axShape.style.yEnd, lineWidth); |
| } |
| else { |
| // 纵向布局,优化x |
| axShape.style.xStart |
| = axShape.style.xEnd |
| = this.subPixelOptimize(axShape.style.xEnd, lineWidth); |
| } |
| |
| axShape.style.lineType = this.option.axisLine.lineStyle.type; |
| |
| axShape = new LineShape(axShape); |
| this.shapeList.push(axShape); |
| }, |
| |
| _axisLabelClickable : function(clickable, axShape) { |
| if (clickable) { |
| ecData.pack( |
| axShape, undefined, -1, undefined, -1, axShape.style.text |
| ); |
| axShape.hoverable = true; |
| axShape.clickable = true; |
| axShape.highlightStyle = { |
| color : zrColor.lift(axShape.style.color, 1), |
| brushType: 'fill' |
| }; |
| return axShape; |
| } |
| else { |
| return axShape; |
| } |
| }, |
| |
| refixAxisShape : function(zeroX, zeroY) { |
| if (!this.option.axisLine.onZero) { |
| return; |
| } |
| var tickLength; |
| if (this.isHorizontal() && typeof zeroY != 'undefined') { |
| // 横向布局调整纵向y |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| if (this.shapeList[i]._axisShape == 'axisLine') { |
| this.shapeList[i].style.yStart |
| = this.shapeList[i].style.yEnd |
| = this.subPixelOptimize( |
| zeroY, this.shapeList[i].stylelineWidth |
| ); |
| this.zr.modShape(this.shapeList[i].id); |
| } |
| else if (this.shapeList[i]._axisShape == 'axisTick') { |
| tickLength = this.shapeList[i].style.yEnd |
| - this.shapeList[i].style.yStart; |
| this.shapeList[i].style.yStart = zeroY - tickLength; |
| this.shapeList[i].style.yEnd = zeroY; |
| this.zr.modShape(this.shapeList[i].id); |
| } |
| } |
| } |
| if (!this.isHorizontal() && typeof zeroX != 'undefined') { |
| // 纵向布局调整横向x |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| if (this.shapeList[i]._axisShape == 'axisLine') { |
| this.shapeList[i].style.xStart |
| = this.shapeList[i].style.xEnd |
| = this.subPixelOptimize( |
| zeroX, this.shapeList[i].stylelineWidth |
| ); |
| this.zr.modShape(this.shapeList[i].id); |
| } |
| else if (this.shapeList[i]._axisShape == 'axisTick') { |
| tickLength = this.shapeList[i].style.xEnd |
| - this.shapeList[i].style.xStart; |
| this.shapeList[i].style.xStart = zeroX; |
| this.shapeList[i].style.xEnd = zeroX + tickLength; |
| this.zr.modShape(this.shapeList[i].id); |
| } |
| } |
| } |
| }, |
| |
| getPosition : function () { |
| return this.option.position; |
| }, |
| |
| isHorizontal : function() { |
| return this.option.position == 'bottom' || this.option.position == 'top'; |
| } |
| }, |
| /** |
| * 参数修正&默认值赋值,重载基类方法 |
| * @param {Object} opt 参数 |
| */ |
| reformOption : function (opt) { |
| // 不写或传了个空数值默认为数值轴 |
| if (!opt || (opt instanceof Array && opt.length === 0)) { |
| opt = [{type : ecConfig.COMPONENT_TYPE_AXIS_VALUE}]; |
| } |
| else if (!(opt instanceof Array)){ |
| opt = [opt]; |
| } |
| |
| // 最多两条,其他参数忽略 |
| if (opt.length > 2) { |
| opt = [opt[0],opt[1]]; |
| } |
| |
| if (this.axisType == 'xAxis') { |
| // 横轴位置默认配置 |
| if (!opt[0].position // 没配置或配置错 |
| || (opt[0].position != 'bottom' |
| && opt[0].position != 'top') |
| ) { |
| opt[0].position = 'bottom'; |
| } |
| if (opt.length > 1) { |
| opt[1].position = opt[0].position == 'bottom' |
| ? 'top' : 'bottom'; |
| } |
| |
| for (var i = 0, l = opt.length; i < l; i++) { |
| // 坐标轴类型,横轴默认为类目型'category' |
| opt[i].type = opt[i].type || 'category'; |
| // 标识轴类型&索引 |
| opt[i].xAxisIndex = i; |
| opt[i].yAxisIndex = -1; |
| } |
| } |
| else { |
| // 纵轴位置默认配置 |
| if (!opt[0].position // 没配置或配置错 |
| || (opt[0].position != 'left' |
| && opt[0].position != 'right') |
| ) { |
| opt[0].position = 'left'; |
| } |
| |
| if (opt.length > 1) { |
| opt[1].position = opt[0].position == 'left' |
| ? 'right' : 'left'; |
| } |
| |
| for (var i = 0, l = opt.length; i < l; i++) { |
| // 坐标轴类型,纵轴默认为数值型'value' |
| opt[i].type = opt[i].type || 'value'; |
| // 标识轴类型&索引 |
| opt[i].xAxisIndex = -1; |
| opt[i].yAxisIndex = i; |
| } |
| } |
| |
| return opt; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| var axisOption; |
| if (newOption) { |
| this.option = newOption; |
| if (this.axisType == 'xAxis') { |
| this.option.xAxis = this.reformOption(newOption.xAxis); |
| axisOption = this.option.xAxis; |
| } |
| else { |
| this.option.yAxis = this.reformOption(newOption.yAxis); |
| axisOption = this.option.yAxis; |
| } |
| this.series = newOption.series; |
| } |
| |
| var CategoryAxis = require('./categoryAxis'); |
| var ValueAxis = require('./valueAxis'); |
| var len = Math.max((axisOption && axisOption.length || 0), this._axisList.length); |
| for (var i = 0; i < len; i++) { |
| if (this._axisList[i] // 已有实例 |
| && newOption // 非空刷新 |
| && (!axisOption[i] || this._axisList[i].type != axisOption[i].type) // 类型不匹配 |
| ) { |
| this._axisList[i].dispose && this._axisList[i].dispose(); |
| this._axisList[i] = false; |
| } |
| |
| if (this._axisList[i]) { |
| this._axisList[i].refresh && this._axisList[i].refresh( |
| axisOption ? axisOption[i] : false, |
| this.series |
| ); |
| } |
| else if (axisOption && axisOption[i]) { |
| this._axisList[i] = axisOption[i].type == 'category' |
| ? new CategoryAxis( |
| this.ecTheme, this.messageCenter, this.zr, |
| axisOption[i], this.myChart, this.axisBase |
| ) |
| : new ValueAxis( |
| this.ecTheme, this.messageCenter, this.zr, |
| axisOption[i], this.myChart, this.axisBase, |
| this.series |
| ); |
| |
| } |
| } |
| }, |
| |
| /** |
| * 根据值换算位置 |
| * @param {number} idx 坐标轴索引0~1 |
| */ |
| getAxis : function (idx) { |
| return this._axisList[idx]; |
| }, |
| |
| clear : function () { |
| for (var i = 0, l = this._axisList.length; i < l; i++) { |
| this._axisList[i].dispose && this._axisList[i].dispose(); |
| } |
| this._axisList = []; |
| } |
| }; |
| |
| zrUtil.inherits(Axis, Base); |
| |
| require('../component').define('axis', Axis); |
| |
| return Axis; |
| }); |
| /** |
| * echarts组件: 网格 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/grid',['require','./base','zrender/shape/Rectangle','../config','zrender/tool/util','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表选项 |
| * @param {number=} option.grid.x 直角坐标系内绘图网格起始横坐标,数值单位px |
| * @param {number=} option.grid.y 直角坐标系内绘图网格起始纵坐标,数值单位px |
| * @param {number=} option.grid.width 直角坐标系内绘图网格宽度,数值单位px |
| * @param {number=} option.grid.height 直角坐标系内绘图网格高度,数值单位px |
| */ |
| function Grid(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.refresh(option); |
| } |
| |
| Grid.prototype = { |
| type : ecConfig.COMPONENT_TYPE_GRID, |
| |
| getX : function () { |
| return this._x; |
| }, |
| |
| getY : function () { |
| return this._y; |
| }, |
| |
| getWidth : function () { |
| return this._width; |
| }, |
| |
| getHeight : function () { |
| return this._height; |
| }, |
| |
| getXend : function () { |
| return this._x + this._width; |
| }, |
| |
| getYend : function () { |
| return this._y + this._height; |
| }, |
| |
| getArea : function () { |
| return { |
| x : this._x, |
| y : this._y, |
| width : this._width, |
| height : this._height |
| }; |
| }, |
| |
| /** |
| * 实在找不到合适的地方做了,各种粗暴的写法~ -_- |
| */ |
| refixAxisShape : function(component) { |
| var zeroX; |
| var zeroY; |
| var axisList = component.xAxis._axisList.concat(component.yAxis._axisList); |
| var len = axisList.length; |
| var axis; |
| while (len--) { |
| axis = axisList[len]; |
| if (axis.type == ecConfig.COMPONENT_TYPE_AXIS_VALUE |
| && axis._min < 0 |
| && axis._max >= 0 |
| ) { |
| axis.isHorizontal() |
| ? (zeroX = axis.getCoord(0)) |
| : (zeroY = axis.getCoord(0)); |
| } |
| } |
| if (typeof zeroX != 'undefined' || typeof zeroY != 'undefined') { |
| len = axisList.length; |
| while (len--) { |
| axisList[len].refixAxisShape(zeroX, zeroY); |
| } |
| } |
| }, |
| |
| refresh : function (newOption) { |
| if (newOption |
| || this._zrWidth != this.zr.getWidth() |
| || this._zrHeight != this.zr.getHeight() |
| ) { |
| this.clear(); |
| this.option = newOption || this.option; |
| this.option.grid = this.reformOption(this.option.grid); |
| |
| var gridOption = this.option.grid; |
| this._zrWidth = this.zr.getWidth(); |
| this._zrHeight = this.zr.getHeight(); |
| this._x = this.parsePercent(gridOption.x, this._zrWidth); |
| this._y = this.parsePercent(gridOption.y, this._zrHeight); |
| var x2 = this.parsePercent(gridOption.x2, this._zrWidth); |
| var y2 = this.parsePercent(gridOption.y2, this._zrHeight); |
| |
| |
| if (typeof gridOption.width == 'undefined') { |
| this._width = this._zrWidth - this._x - x2; |
| } |
| else { |
| this._width = this.parsePercent(gridOption.width, this._zrWidth); |
| } |
| |
| if (typeof gridOption.height == 'undefined') { |
| this._height = this._zrHeight - this._y - y2; |
| } |
| else { |
| this._height = this.parsePercent(gridOption.height, this._zrHeight); |
| } |
| |
| this._x = this.subPixelOptimize(this._x, gridOption.borderWidth); |
| this._y = this.subPixelOptimize(this._y, gridOption.borderWidth); |
| |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : this._x, |
| y : this._y, |
| width : this._width, |
| height : this._height, |
| brushType : gridOption.borderWidth > 0 ? 'both' : 'fill', |
| color : gridOption.backgroundColor, |
| strokeColor: gridOption.borderColor, |
| lineWidth : gridOption.borderWidth |
| // type : this.option.splitArea.areaStyle.type, |
| } |
| })); |
| this.zr.addShape(this.shapeList[0]); |
| } |
| } |
| }; |
| |
| zrUtil.inherits(Grid, Base); |
| |
| require('../component').define('grid', Grid); |
| |
| return Grid; |
| }); |
| /** |
| * echarts组件:数据区域缩放 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/dataZoom',['require','./base','zrender/shape/Rectangle','zrender/shape/Polygon','../util/shape/Icon','../config','zrender/tool/util','../component','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var PolygonShape = require('zrender/shape/Polygon'); |
| var IconShape = require('../util/shape/Icon'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表参数 |
| * @param {Object} component 组件 |
| */ |
| function DataZoom(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| var self = this; |
| self._ondrift = function (dx, dy) { |
| return self.__ondrift(this, dx, dy); |
| }; |
| self._ondragend = function () { |
| return self.__ondragend(); |
| }; |
| |
| this._fillerSize = 28; // 控件大小,水平布局为高,纵向布局为宽 |
| this._handleSize = 8; // 手柄大小 |
| // this._fillerShae; // 填充 |
| // this._startShape; // 起始手柄 |
| // this._endShape; // 结束手柄 |
| // this._startFrameShape; // 起始特效边框 |
| // this._endFrameShape; // 结束特效边框 |
| // this._syncTicket; |
| this._isSilence = false; |
| this._zoom = {}; |
| // this._originalData; |
| |
| this.option.dataZoom = this.reformOption(this.option.dataZoom); |
| this.zoomOption = this.option.dataZoom; |
| |
| // 位置参数,通过计算所得x, y, width, height |
| this._location = this._getLocation(); |
| // 缩放参数 |
| this._zoom = this._getZoom(); |
| this._backupData(); |
| |
| if (this.option.dataZoom.show) { |
| this._buildShape(); |
| } |
| this._syncData(); |
| } |
| |
| DataZoom.prototype = { |
| type : ecConfig.COMPONENT_TYPE_DATAZOOM, |
| _buildShape : function () { |
| this._buildBackground(); |
| this._buildFiller(); |
| this._buildHandle(); |
| this._buildFrame(); |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| this._syncFrameShape(); |
| }, |
| |
| /** |
| * 根据选项计算实体的位置坐标 |
| */ |
| _getLocation : function () { |
| var x; |
| var y; |
| var width; |
| var height; |
| var grid = this.component.grid; |
| |
| // 不指定则根据grid适配 |
| if (this.zoomOption.orient == 'horizontal') { |
| // 水平布局 |
| width = this.zoomOption.width || grid.getWidth(); |
| height = this.zoomOption.height || this._fillerSize; |
| x = typeof this.zoomOption.x != 'undefined' |
| ? this.zoomOption.x : grid.getX(); |
| y = typeof this.zoomOption.y != 'undefined' |
| ? this.zoomOption.y : (this.zr.getHeight() - height - 2); |
| } |
| else { |
| // 垂直布局 |
| width = this.zoomOption.width || this._fillerSize; |
| height = this.zoomOption.height || grid.getHeight(); |
| x = typeof this.zoomOption.x != 'undefined' |
| ? this.zoomOption.x : 2; |
| y = typeof this.zoomOption.y != 'undefined' |
| ? this.zoomOption.y : grid.getY(); |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : width, |
| height : height |
| }; |
| }, |
| |
| /** |
| * 计算缩放参数 |
| * 修正单坐标轴只传对象为数组。 |
| */ |
| _getZoom : function () { |
| var series = this.option.series; |
| var xAxis = this.option.xAxis; |
| if (xAxis && !(xAxis instanceof Array)) { |
| xAxis = [xAxis]; |
| this.option.xAxis = xAxis; |
| } |
| var yAxis = this.option.yAxis; |
| if (yAxis && !(yAxis instanceof Array)) { |
| yAxis = [yAxis]; |
| this.option.yAxis = yAxis; |
| } |
| |
| var zoomSeriesIndex = []; |
| var xAxisIndex; |
| var yAxisIndex; |
| |
| var zOptIdx = this.zoomOption.xAxisIndex; |
| if (xAxis && typeof zOptIdx == 'undefined') { |
| xAxisIndex = []; |
| for (var i = 0, l = xAxis.length; i < l; i++) { |
| // 横纵默认为类目轴 |
| if (xAxis[i].type == 'category' |
| || typeof xAxis[i].type == 'undefined' |
| ) { |
| xAxisIndex.push(i); |
| } |
| } |
| } |
| else { |
| if (zOptIdx instanceof Array) { |
| xAxisIndex = zOptIdx; |
| } |
| else if (typeof zOptIdx != 'undefined') { |
| xAxisIndex = [zOptIdx]; |
| } |
| else { |
| xAxisIndex = []; |
| } |
| } |
| |
| zOptIdx = this.zoomOption.yAxisIndex; |
| if (yAxis && typeof zOptIdx == 'undefined') { |
| yAxisIndex = []; |
| for (var i = 0, l = yAxis.length; i < l; i++) { |
| if (yAxis[i].type == 'category') { |
| yAxisIndex.push(i); |
| } |
| } |
| } |
| else { |
| if (zOptIdx instanceof Array) { |
| yAxisIndex = zOptIdx; |
| } |
| else if (typeof zOptIdx != 'undefined') { |
| yAxisIndex = [zOptIdx]; |
| } |
| else { |
| yAxisIndex = []; |
| } |
| } |
| |
| // 找到缩放控制的所有series |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type != ecConfig.CHART_TYPE_LINE |
| && series[i].type != ecConfig.CHART_TYPE_BAR |
| && series[i].type != ecConfig.CHART_TYPE_SCATTER |
| && series[i].type != ecConfig.CHART_TYPE_K |
| ) { |
| continue; |
| } |
| for (var j = 0, k = xAxisIndex.length; j < k; j++) { |
| if (xAxisIndex[j] == (series[i].xAxisIndex || 0)) { |
| zoomSeriesIndex.push(i); |
| break; |
| } |
| } |
| for (var j = 0, k = yAxisIndex.length; j < k; j++) { |
| if (yAxisIndex[j] == (series[i].yAxisIndex || 0)) { |
| zoomSeriesIndex.push(i); |
| break; |
| } |
| } |
| // 不指定接管坐标轴,则散点图被纳入接管范围 |
| if (series[i].type == ecConfig.CHART_TYPE_SCATTER |
| && typeof this.zoomOption.xAxisIndex == 'undefined' |
| && typeof this.zoomOption.yAxisIndex == 'undefined' |
| ) { |
| zoomSeriesIndex.push(i); |
| } |
| } |
| |
| var start = typeof this._zoom.start != 'undefined' |
| ? this._zoom.start |
| : (typeof this.zoomOption.start != 'undefined' ? this.zoomOption.start : 0); |
| var end = typeof this._zoom.end != 'undefined' |
| ? this._zoom.end |
| : (typeof this.zoomOption.end != 'undefined' ? this.zoomOption.end : 100); |
| /* |
| var start = typeof this.zoomOption.start != 'undefined' |
| && this.zoomOption.start >= 0 |
| && this.zoomOption.start <= 100 |
| ? this.zoomOption.start : 0; |
| var end = typeof this.zoomOption.end != 'undefined' |
| && this.zoomOption.end >= 0 |
| && this.zoomOption.end <= 100 |
| ? this.zoomOption.end : 100; |
| */ |
| if (start > end) { |
| // 大小颠倒自动翻转 |
| start = start + end; |
| end = start - end; |
| start = start - end; |
| } |
| var size = Math.round( |
| (end - start) / 100 |
| * (this.zoomOption.orient == 'horizontal' |
| ? this._location.width : this._location.height) |
| ); |
| return { |
| start : start, |
| end : end, |
| start2 : 0, |
| end2 : 100, |
| size : size, |
| xAxisIndex : xAxisIndex, |
| yAxisIndex : yAxisIndex, |
| seriesIndex : zoomSeriesIndex, |
| scatterMap : this._zoom.scatterMap || {} |
| }; |
| }, |
| |
| _backupData : function () { |
| this._originalData = { |
| xAxis : {}, |
| yAxis : {}, |
| series : {} |
| }; |
| var xAxis = this.option.xAxis; |
| var xAxisIndex = this._zoom.xAxisIndex; |
| for (var i = 0, l = xAxisIndex.length; i < l; i++) { |
| this._originalData.xAxis[xAxisIndex[i]] = xAxis[xAxisIndex[i]].data; |
| } |
| |
| var yAxis = this.option.yAxis; |
| var yAxisIndex = this._zoom.yAxisIndex; |
| for (var i = 0, l = yAxisIndex.length; i < l; i++) { |
| this._originalData.yAxis[yAxisIndex[i]] = yAxis[yAxisIndex[i]].data; |
| } |
| |
| var series = this.option.series; |
| var seriesIndex = this._zoom.seriesIndex; |
| var serie; |
| for (var i = 0, l = seriesIndex.length; i < l; i++) { |
| serie = series[seriesIndex[i]]; |
| this._originalData.series[seriesIndex[i]] = serie.data; |
| if (serie.type == ecConfig.CHART_TYPE_SCATTER) { |
| this._calculScatterMap(seriesIndex[i]); |
| } |
| } |
| }, |
| |
| _calculScatterMap : function (seriesIndex) { |
| this._zoom.scatterMap = this._zoom.scatterMap || {}; |
| this._zoom.scatterMap[seriesIndex] = this._zoom.scatterMap[seriesIndex] || {}; |
| var componentLibrary = require('../component'); |
| // x轴极值 |
| var Axis = componentLibrary.get('axis'); |
| var axisOption = zrUtil.clone(this.option.xAxis); |
| if (axisOption instanceof Array) { |
| axisOption[0].type = 'value'; |
| axisOption[0].boundary = [0, 0]; |
| axisOption[1] && (axisOption[1].type = 'value', axisOption[1].boundary = [0, 0]); |
| } |
| else { |
| axisOption.type = 'value'; |
| axisOption.boundary = [0, 0]; |
| } |
| var vAxis = new Axis( |
| this.ecTheme, |
| null, // messageCenter |
| false, // this.zr |
| { |
| xAxis: axisOption, |
| series : this.option.series |
| }, |
| this, |
| 'xAxis' |
| ); |
| var axisIndex = this.option.series[seriesIndex].xAxisIndex || 0; |
| this._zoom.scatterMap[seriesIndex].x = vAxis.getAxis(axisIndex).getExtremum(); |
| vAxis.dispose(); |
| |
| // y轴极值 |
| axisOption = zrUtil.clone(this.option.yAxis); |
| if (axisOption instanceof Array) { |
| axisOption[0].type = 'value'; |
| axisOption[1] && (axisOption[1].type = 'value', axisOption[1].boundary = [0, 0]); |
| } |
| else { |
| axisOption.type = 'value'; |
| axisOption.boundary = [0, 0]; |
| } |
| vAxis = new Axis( |
| this.ecTheme, |
| null, // messageCenter |
| false, // this.zr |
| { |
| yAxis: axisOption, |
| series : this.option.series |
| }, |
| this, |
| 'yAxis' |
| ); |
| axisIndex = this.option.series[seriesIndex].yAxisIndex || 0; |
| this._zoom.scatterMap[seriesIndex].y = vAxis.getAxis(axisIndex).getExtremum(); |
| vAxis.dispose(); |
| // console.log(this._zoom.scatterMap); |
| }, |
| |
| _buildBackground : function () { |
| var width = this._location.width; |
| var height = this._location.height; |
| |
| // 背景 |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : this._location.x, |
| y : this._location.y, |
| width : width, |
| height : height, |
| color : this.zoomOption.backgroundColor |
| } |
| })); |
| |
| // 数据阴影 |
| var maxLength = 0; |
| var xAxis = this._originalData.xAxis; |
| var xAxisIndex = this._zoom.xAxisIndex; |
| for (var i = 0, l = xAxisIndex.length; i < l; i++) { |
| maxLength = Math.max( |
| maxLength, xAxis[xAxisIndex[i]].length |
| ); |
| } |
| var yAxis = this._originalData.yAxis; |
| var yAxisIndex = this._zoom.yAxisIndex; |
| for (var i = 0, l = yAxisIndex.length; i < l; i++) { |
| maxLength = Math.max( |
| maxLength, yAxis[yAxisIndex[i]].length |
| ); |
| } |
| |
| var seriesIndex = this._zoom.seriesIndex[0]; |
| var data = this._originalData.series[seriesIndex]; |
| var maxValue = Number.MIN_VALUE; |
| var minValue = Number.MAX_VALUE; |
| var value; |
| for (var i = 0, l = data.length; i < l; i++) { |
| value = typeof data[i] != 'undefined' |
| ? (typeof data[i].value != 'undefined' ? data[i].value : data[i]) |
| : 0; |
| if (this.option.series[seriesIndex].type == ecConfig.CHART_TYPE_K) { |
| value = value[1]; // 收盘价 |
| } |
| if (isNaN(value)) { |
| value = 0; |
| } |
| maxValue = Math.max(maxValue, value); |
| minValue = Math.min(minValue, value); |
| } |
| var valueRange = maxValue - minValue; |
| |
| var pointList = []; |
| var x = width / (maxLength - (maxLength > 1 ? 1 : 0)); |
| var y = height / (maxLength - (maxLength > 1 ? 1 : 0)); |
| var step = 1; |
| if (this.zoomOption.orient == 'horizontal' && x < 1) { |
| step = Math.floor(maxLength * 3 / width); |
| } |
| else if (this.zoomOption.orient == 'vertical' && y < 1){ |
| step = Math.floor(maxLength * 3 / height); |
| } |
| |
| for (var i = 0, l = maxLength; i < l; i += step) { |
| value = typeof data[i] != 'undefined' |
| ? (typeof data[i].value != 'undefined' |
| ? data[i].value : data[i]) |
| : 0; |
| if (this.option.series[seriesIndex].type == ecConfig.CHART_TYPE_K) { |
| value = value[1]; // 收盘价 |
| } |
| if (isNaN(value)) { |
| value = 0; |
| } |
| if (this.zoomOption.orient == 'horizontal') { |
| pointList.push([ |
| this._location.x + x * i, |
| this._location.y + height - 1 - Math.round( |
| (value - minValue) / valueRange * (height - 10) |
| ) |
| ]); |
| } |
| else { |
| pointList.push([ |
| this._location.x + 1 + Math.round( |
| (value - minValue) / valueRange * (width - 10) |
| ), |
| this._location.y + y * i |
| ]); |
| } |
| } |
| if (this.zoomOption.orient == 'horizontal') { |
| pointList.push([ |
| this._location.x + width, |
| this._location.y + height |
| ]); |
| pointList.push([ |
| this._location.x, this._location.y + height |
| ]); |
| } |
| else { |
| pointList.push([ |
| this._location.x, this._location.y + height |
| ]); |
| pointList.push([ |
| this._location.x, this._location.y |
| ]); |
| } |
| |
| this.shapeList.push(new PolygonShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| pointList : pointList, |
| color : this.zoomOption.dataBackgroundColor |
| }, |
| hoverable : false |
| })); |
| }, |
| |
| /** |
| * 构建填充物 |
| */ |
| _buildFiller : function () { |
| this._fillerShae = { |
| zlevel : this._zlevelBase, |
| draggable : true, |
| ondrift : this._ondrift, |
| ondragend : this._ondragend, |
| _type : 'filler' |
| }; |
| |
| if (this.zoomOption.orient == 'horizontal') { |
| // 横向 |
| this._fillerShae.style = { |
| x : this._location.x |
| + Math.round(this._zoom.start / 100 * this._location.width) |
| + this._handleSize, |
| y : this._location.y, |
| width : this._zoom.size - this._handleSize * 2, |
| height : this._location.height, |
| color : this.zoomOption.fillerColor, |
| // strokeColor : '#fff', // this.zoomOption.handleColor, |
| // lineWidth: 2, |
| text : ':::', |
| textPosition : 'inside' |
| }; |
| } |
| else { |
| // 纵向 |
| this._fillerShae.style ={ |
| x : this._location.x, |
| y : this._location.y |
| + Math.round(this._zoom.start / 100 * this._location.height) |
| + this._handleSize, |
| width : this._location.width, |
| height : this._zoom.size - this._handleSize * 2, |
| color : this.zoomOption.fillerColor, |
| // strokeColor : '#fff', // this.zoomOption.handleColor, |
| // lineWidth: 2, |
| text : '::', |
| textPosition : 'inside' |
| }; |
| } |
| |
| this._fillerShae.highlightStyle = { |
| brushType: 'fill', |
| color : 'rgba(0,0,0,0)' |
| /* |
| color : require('zrender/tool/color').alpha( |
| this._fillerShae.style.color, 0 |
| ) |
| */ |
| }; |
| this._fillerShae = new RectangleShape(this._fillerShae); |
| this.shapeList.push(this._fillerShae); |
| }, |
| |
| /** |
| * 构建拖拽手柄 |
| */ |
| _buildHandle : function () { |
| this._startShape = { |
| zlevel : this._zlevelBase, |
| draggable : true, |
| style : { |
| iconType: 'rectangle', |
| x : this._location.x, |
| y : this._location.y, |
| width : this._handleSize, |
| height : this._handleSize, |
| color : this.zoomOption.handleColor, |
| text : '=', |
| textPosition : 'inside' |
| }, |
| highlightStyle : { |
| brushType: 'fill' |
| }, |
| ondrift : this._ondrift, |
| ondragend : this._ondragend |
| }; |
| |
| if (this.zoomOption.orient == 'horizontal') { |
| this._startShape.style.height = this._location.height; |
| this._endShape = zrUtil.clone(this._startShape); |
| |
| this._startShape.style.x = this._fillerShae.style.x - this._handleSize, |
| this._endShape.style.x = this._fillerShae.style.x |
| + this._fillerShae.style.width; |
| } |
| else { |
| this._startShape.style.width = this._location.width; |
| this._endShape = zrUtil.clone(this._startShape); |
| |
| this._startShape.style.y = this._fillerShae.style.y - this._handleSize; |
| this._endShape.style.y = this._fillerShae.style.y |
| + this._fillerShae.style.height; |
| } |
| this._startShape = new IconShape(this._startShape); |
| this._endShape = new IconShape(this._endShape); |
| this.shapeList.push(this._startShape); |
| this.shapeList.push(this._endShape); |
| }, |
| |
| /** |
| * 构建特效边框 |
| */ |
| _buildFrame : function () { |
| // 特效框线,亚像素优化 |
| var x = this.subPixelOptimize(this._location.x, 1); |
| var y = this.subPixelOptimize(this._location.y, 1); |
| this._startFrameShape = { |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : x, |
| y : y, |
| width : this._location.width - (x > this._location.x ? 1 : 0), |
| height : this._location.height - (y > this._location.y ? 1 : 0), |
| lineWidth: 1, |
| brushType: 'stroke', |
| strokeColor : this.zoomOption.handleColor |
| } |
| }; |
| this._endFrameShape = zrUtil.clone(this._startFrameShape); |
| |
| this._startFrameShape = new RectangleShape(this._startFrameShape); |
| this._endFrameShape = new RectangleShape(this._endFrameShape); |
| this.shapeList.push(this._startFrameShape); |
| this.shapeList.push(this._endFrameShape); |
| return; |
| }, |
| |
| _syncHandleShape : function () { |
| if (this.zoomOption.orient == 'horizontal') { |
| this._startShape.style.x = this._fillerShae.style.x - this._handleSize; |
| this._endShape.style.x = this._fillerShae.style.x |
| + this._fillerShae.style.width; |
| |
| this._zoom.start = Math.floor( |
| (this._startShape.style.x - this._location.x) |
| / this._location.width * 100 |
| ); |
| this._zoom.end = Math.ceil( |
| (this._endShape.style.x + this._handleSize - this._location.x) |
| / this._location.width * 100 |
| ); |
| } |
| else { |
| this._startShape.style.y = this._fillerShae.style.y - this._handleSize; |
| this._endShape.style.y = this._fillerShae.style.y |
| + this._fillerShae.style.height; |
| this._zoom.start = Math.floor( |
| (this._startShape.style.y - this._location.y) |
| / this._location.height * 100 |
| ); |
| this._zoom.end = Math.ceil( |
| (this._endShape.style.y + this._handleSize - this._location.y) |
| / this._location.height * 100 |
| ); |
| } |
| |
| this.zr.modShape(this._startShape.id); |
| this.zr.modShape(this._endShape.id); |
| |
| // 同步边框 |
| this._syncFrameShape(); |
| |
| this.zr.refresh(); |
| }, |
| |
| _syncFillerShape : function () { |
| var a; |
| var b; |
| if (this.zoomOption.orient == 'horizontal') { |
| a = this._startShape.style.x; |
| b = this._endShape.style.x; |
| this._fillerShae.style.x = Math.min(a, b) + this._handleSize; |
| this._fillerShae.style.width = Math.abs(a - b) - this._handleSize; |
| this._zoom.start = Math.floor( |
| (Math.min(a, b) - this._location.x) |
| / this._location.width * 100 |
| ); |
| this._zoom.end = Math.ceil( |
| (Math.max(a, b) + this._handleSize - this._location.x) |
| / this._location.width * 100 |
| ); |
| } |
| else { |
| a = this._startShape.style.y; |
| b = this._endShape.style.y; |
| this._fillerShae.style.y = Math.min(a, b) + this._handleSize; |
| this._fillerShae.style.height = Math.abs(a - b) - this._handleSize; |
| this._zoom.start = Math.floor( |
| (Math.min(a, b) - this._location.y) |
| / this._location.height * 100 |
| ); |
| this._zoom.end = Math.ceil( |
| (Math.max(a, b) + this._handleSize - this._location.y) |
| / this._location.height * 100 |
| ); |
| } |
| |
| this.zr.modShape(this._fillerShae.id); |
| |
| // 同步边框 |
| this._syncFrameShape(); |
| |
| this.zr.refresh(); |
| }, |
| |
| _syncFrameShape : function () { |
| if (this.zoomOption.orient == 'horizontal') { |
| this._startFrameShape.style.width = |
| this._fillerShae.style.x - this._location.x; |
| this._endFrameShape.style.x = |
| this._fillerShae.style.x + this._fillerShae.style.width; |
| this._endFrameShape.style.width = |
| this._location.x + this._location.width - this._endFrameShape.style.x; |
| } |
| else { |
| this._startFrameShape.style.height = |
| this._fillerShae.style.y - this._location.y; |
| this._endFrameShape.style.y = |
| this._fillerShae.style.y + this._fillerShae.style.height; |
| this._endFrameShape.style.height = |
| this._location.y + this._location.height - this._endFrameShape.style.y; |
| } |
| |
| this.zr.modShape(this._startFrameShape.id); |
| this.zr.modShape(this._endFrameShape.id); |
| }, |
| |
| _syncShape : function () { |
| if (!this.zoomOption.show) { |
| // 没有伸缩控件 |
| return; |
| } |
| if (this.zoomOption.orient == 'horizontal') { |
| this._startShape.style.x = this._location.x |
| + this._zoom.start / 100 * this._location.width; |
| this._endShape.style.x = this._location.x |
| + this._zoom.end / 100 * this._location.width |
| - this._handleSize; |
| |
| this._fillerShae.style.x = this._startShape.style.x + this._handleSize; |
| this._fillerShae.style.width = this._endShape.style.x |
| - this._startShape.style.x |
| - this._handleSize; |
| } |
| else { |
| this._startShape.style.y = this._location.y |
| + this._zoom.start / 100 * this._location.height; |
| this._endShape.style.y = this._location.y |
| + this._zoom.end / 100 * this._location.height |
| - this._handleSize; |
| |
| this._fillerShae.style.y = this._startShape.style.y + this._handleSize; |
| this._fillerShae.style.height = this._endShape.style.y |
| - this._startShape.style.y |
| - this._handleSize; |
| } |
| |
| this.zr.modShape(this._startShape.id); |
| this.zr.modShape(this._endShape.id); |
| this.zr.modShape(this._fillerShae.id); |
| // 同步边框 |
| this._syncFrameShape(); |
| this.zr.refresh(); |
| }, |
| |
| _syncData : function (dispatchNow) { |
| var target; |
| var start; |
| var end; |
| var length; |
| var data; |
| |
| for (var key in this._originalData) { |
| target = this._originalData[key]; |
| for (var idx in target) { |
| data = target[idx]; |
| if (typeof data == 'undefined') { |
| continue; |
| } |
| length = data.length; |
| start = Math.floor(this._zoom.start / 100 * length); |
| end = Math.ceil(this._zoom.end / 100 * length); |
| if (this.option[key][idx].type != ecConfig.CHART_TYPE_SCATTER) { |
| this.option[key][idx].data = data.slice(start, end); |
| } |
| else { |
| // 散点图特殊处理 |
| this.option[key][idx].data = this._synScatterData(idx, data); |
| } |
| } |
| } |
| |
| if (!this._isSilence && (this.zoomOption.realtime || dispatchNow)) { |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.DATA_ZOOM, |
| null, |
| {zoom: this._zoom}, |
| this.myChart |
| ); |
| } |
| |
| //this.zoomOption.start = this._zoom.start; |
| //this.zoomOption.end = this._zoom.end; |
| }, |
| |
| _synScatterData : function (seriesIndex, data) { |
| if (this._zoom.start === 0 |
| && this._zoom.end == 100 |
| && this._zoom.start2 === 0 |
| && this._zoom.end2 == 100 |
| ) { |
| return data; |
| } |
| var newData = []; |
| var scale = this._zoom.scatterMap[seriesIndex]; |
| var total; |
| var xStart; |
| var xEnd; |
| var yStart; |
| var yEnd; |
| |
| if (this.zoomOption.orient == 'horizontal') { |
| total = scale.x.max - scale.x.min; |
| xStart = this._zoom.start / 100 * total + scale.x.min; |
| xEnd = this._zoom.end / 100 * total + scale.x.min; |
| |
| total = scale.y.max - scale.y.min; |
| yStart = this._zoom.start2 / 100 * total + scale.y.min; |
| yEnd = this._zoom.end2 / 100 * total + scale.y.min; |
| } |
| else { |
| total = scale.x.max - scale.x.min; |
| xStart = this._zoom.start2 / 100 * total + scale.x.min; |
| xEnd = this._zoom.end2 / 100 * total + scale.x.min; |
| |
| total = scale.y.max - scale.y.min; |
| yStart = this._zoom.start / 100 * total + scale.y.min; |
| yEnd = this._zoom.end / 100 * total + scale.y.min; |
| } |
| |
| // console.log(xStart,xEnd,yStart,yEnd); |
| var value; |
| for (var i = 0, l = data.length; i < l; i++) { |
| value = data[i].value || data[i]; |
| if (value[0] >= xStart |
| && value[0] <= xEnd |
| && value[1] >= yStart |
| && value[1] <= yEnd |
| ) { |
| newData.push(data[i]); |
| } |
| } |
| |
| return newData; |
| }, |
| /** |
| * 拖拽范围控制 |
| */ |
| __ondrift : function (shape, dx, dy) { |
| if (this.zoomOption.zoomLock) { |
| // zoomLock时把handle转成filler的拖拽 |
| shape = this._fillerShae; |
| } |
| |
| var detailSize = shape._type == 'filler' ? this._handleSize : 0; |
| if (this.zoomOption.orient == 'horizontal') { |
| if (shape.style.x + dx - detailSize <= this._location.x) { |
| shape.style.x = this._location.x + detailSize; |
| } |
| else if (shape.style.x + dx + shape.style.width + detailSize |
| >= this._location.x + this._location.width |
| ) { |
| shape.style.x = this._location.x + this._location.width |
| - shape.style.width - detailSize; |
| } |
| else { |
| shape.style.x += dx; |
| } |
| } |
| else { |
| if (shape.style.y + dy - detailSize <= this._location.y) { |
| shape.style.y = this._location.y + detailSize; |
| } |
| else if (shape.style.y + dy + shape.style.height + detailSize |
| >= this._location.y + this._location.height |
| ) { |
| shape.style.y = this._location.y + this._location.height |
| - shape.style.height - detailSize; |
| } |
| else { |
| shape.style.y += dy; |
| } |
| } |
| |
| if (shape._type == 'filler') { |
| this._syncHandleShape(); |
| } |
| else { |
| this._syncFillerShape(); |
| } |
| |
| if (this.zoomOption.realtime) { |
| this._syncData(); |
| } |
| |
| return true; |
| }, |
| |
| __ondragend : function () { |
| this.isDragend = true; |
| }, |
| |
| /** |
| * 数据项被拖拽出去 |
| */ |
| ondragend : function (param, status) { |
| if (!this.isDragend || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| |
| !this.zoomOption.realtime && this._syncData(); |
| |
| // 别status = {}赋值啊!! |
| status.dragOut = true; |
| status.dragIn = true; |
| if (!this._isSilence && !this.zoomOption.realtime) { |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.DATA_ZOOM, |
| null, |
| {zoom: this._zoom}, |
| this.myChart |
| ); |
| } |
| status.needRefresh = false; // 会有消息触发fresh,不用再刷一遍 |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| |
| return; |
| }, |
| |
| ondataZoom : function (param, status) { |
| status.needRefresh = true; |
| return; |
| }, |
| |
| absoluteZoom : function (param) { |
| //this.zoomOption.start = |
| this._zoom.start = param.start; |
| //this.zoomOption.end = |
| this._zoom.end = param.end; |
| //this.zoomOption.start2 = |
| this._zoom.start2 = param.start2; |
| //this.zoomOption.end2 = |
| this._zoom.end2 = param.end2; |
| this._syncShape(); |
| this._syncData(true); |
| return; |
| }, |
| |
| rectZoom : function (param) { |
| if (!param) { |
| // 重置拖拽 |
| //this.zoomOption.start = |
| //this.zoomOption.start2 = |
| this._zoom.start = |
| this._zoom.start2 = 0; |
| |
| //this.zoomOption.end = |
| //this.zoomOption.end2 = |
| this._zoom.end = |
| this._zoom.end2 = 100; |
| |
| this._syncShape(); |
| this._syncData(true); |
| return this._zoom; |
| } |
| var gridArea = this.component.grid.getArea(); |
| var rect = { |
| x : param.x, |
| y : param.y, |
| width : param.width, |
| height : param.height |
| }; |
| // 修正方向框选 |
| if (rect.width < 0) { |
| rect.x += rect.width; |
| rect.width = -rect.width; |
| } |
| if (rect.height < 0) { |
| rect.y += rect.height; |
| rect.height = -rect.height; |
| } |
| // console.log(rect,this._zoom); |
| |
| // 剔除无效缩放 |
| if (rect.x > gridArea.x + gridArea.width |
| || rect.y > gridArea.y + gridArea.height |
| ) { |
| return false; // 无效缩放 |
| } |
| |
| // 修正框选超出 |
| if (rect.x < gridArea.x) { |
| rect.x = gridArea.x; |
| } |
| if (rect.x + rect.width > gridArea.x + gridArea.width) { |
| rect.width = gridArea.x + gridArea.width - rect.x; |
| } |
| if (rect.y + rect.height > gridArea.y + gridArea.height) { |
| rect.height = gridArea.y + gridArea.height - rect.y; |
| } |
| |
| var total; |
| var sdx = (rect.x - gridArea.x) / gridArea.width; |
| var edx = 1 - (rect.x + rect.width - gridArea.x) / gridArea.width; |
| var sdy = 1 - (rect.y + rect.height - gridArea.y) / gridArea.height; |
| var edy = (rect.y - gridArea.y) / gridArea.height; |
| //console.log('this',sdy,edy,this._zoom.start,this._zoom.end) |
| if (this.zoomOption.orient == 'horizontal') { |
| total = this._zoom.end - this._zoom.start; |
| this._zoom.start += total * sdx; |
| this._zoom.end -= total * edx; |
| |
| total = this._zoom.end2 - this._zoom.start2; |
| this._zoom.start2 += total * sdy; |
| this._zoom.end2 -= total * edy; |
| } |
| else { |
| total = this._zoom.end - this._zoom.start; |
| this._zoom.start += total * sdy; |
| this._zoom.end -= total * edy; |
| |
| total = this._zoom.end2 - this._zoom.start2; |
| this._zoom.start2 += total * sdx; |
| this._zoom.end2 -= total * edx; |
| } |
| //console.log(this._zoom.start,this._zoom.end,this._zoom.start2,this._zoom.end2) |
| //this.zoomOption.start = this._zoom.start; |
| //this.zoomOption.end = this._zoom.end; |
| //this.zoomOption.start2 = this._zoom.start2; |
| //this.zoomOption.end2 = this._zoom.end2; |
| //console.log(rect,gridArea,this._zoom,total) |
| this._syncShape(); |
| this._syncData(true); |
| return this._zoom; |
| }, |
| |
| syncBackupData : function (curOption) { |
| var start; |
| var target = this._originalData['series']; |
| var curSeries = curOption.series; |
| var curData; |
| for (var i = 0, l = curSeries.length; i < l; i++) { |
| curData = curSeries[i].data; |
| if (target[i]) { |
| // dataZoom接管的 |
| start = Math.floor(this._zoom.start / 100 * target[i].length); |
| } |
| else { |
| // 非dataZoom接管 |
| start = 0; |
| } |
| for (var j = 0, k = curData.length; j < k; j++) { |
| //optionBackup.series[i].data[j + start] = curData[j]; |
| if (target[i]) { |
| // 同步内部备份 |
| target[i][j + start] = curData[j]; |
| } |
| } |
| } |
| }, |
| |
| syncOption : function(magicOption) { |
| this.silence(true); |
| this.option = magicOption; |
| |
| this.clear(); |
| // 位置参数,通过计算所得x, y, width, height |
| this._location = this._getLocation(); |
| // 缩放参数 |
| this._zoom = this._getZoom(); |
| |
| this._backupData(); |
| if (this.option.dataZoom && this.option.dataZoom.show) { |
| this._buildShape(); |
| } |
| this._syncData(); |
| |
| this.silence(false); |
| }, |
| |
| silence : function (s) { |
| this._isSilence = s; |
| }, |
| |
| getRealDataIndex : function (sIdx, dIdx) { |
| if (!this._originalData || (this._zoom.start === 0 && this._zoom.end == 100)) { |
| return dIdx; |
| } |
| var sreies = this._originalData.series; |
| if (sreies[sIdx]) { |
| return Math.floor(this._zoom.start / 100 * sreies[sIdx].length) + dIdx; |
| } |
| return -1; |
| }, |
| |
| /** |
| * 避免dataZoom带来两次refresh,不设refresh接口,resize重复一下buildshape逻辑 |
| */ |
| resize : function () { |
| this.clear(); |
| |
| // 位置参数,通过计算所得x, y, width, height |
| this._location = this._getLocation(); |
| // 缩放参数 |
| this._zoom = this._getZoom(); |
| |
| if (this.option.dataZoom.show) { |
| this._buildShape(); |
| } |
| } |
| }; |
| |
| zrUtil.inherits(DataZoom, Base); |
| |
| require('../component').define('dataZoom', DataZoom); |
| |
| return DataZoom; |
| }); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:handlePolygon,dataRange手柄 |
| */ |
| define('echarts/util/shape/HandlePolygon',['require','zrender/shape/Base','zrender/shape/Polygon','zrender/tool/util'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var PolygonShape = require('zrender/shape/Polygon'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function HandlePolygon(options) { |
| Base.call(this, options); |
| } |
| |
| HandlePolygon.prototype = { |
| type : 'handle-polygon', |
| /** |
| * 创建多边形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| PolygonShape.prototype.buildPath( |
| ctx, style |
| ); |
| }, |
| isCover : function (x, y) { |
| var originPos = this.getTansform(x, y); |
| x = originPos[0]; |
| y = originPos[1]; |
| |
| // 不能缓存rect! |
| var rect = this.style.rect; |
| if (x >= rect.x |
| && x <= (rect.x + rect.width) |
| && y >= rect.y |
| && y <= (rect.y + rect.height) |
| ) { |
| // 矩形内 |
| return true; |
| } |
| else { |
| return false; |
| } |
| } |
| }; |
| zrUtil.inherits(HandlePolygon, Base); |
| |
| return HandlePolygon; |
| }); |
| |
| /** |
| * echarts组件:值域 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/component/dataRange',['require','./base','zrender/shape/Text','zrender/shape/Rectangle','../util/shape/HandlePolygon','../config','zrender/tool/util','zrender/tool/area','zrender/tool/color','zrender/tool/color','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var HandlePolygonShape = require('../util/shape/HandlePolygon'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrArea = require('zrender/tool/area'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} option 图表参数 |
| * @param {Object=} selected 用于状态保持 |
| */ |
| function DataRange(ecTheme, messageCenter, zr, option, myChart) { |
| if (typeof this.query(option, 'dataRange.min') == 'undefined' |
| || typeof this.query(option, 'dataRange.max') == 'undefined' |
| ) { |
| console.error('option.dataRange.min or option.dataRange.max has not been defined.'); |
| return; |
| } |
| |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| var self = this; |
| self._ondrift = function(dx, dy) { |
| return self.__ondrift(this, dx, dy); |
| }; |
| self._ondragend = function() { |
| return self.__ondragend(); |
| }; |
| self._dataRangeSelected = function(param) { |
| return self.__dataRangeSelected(param); |
| }; |
| this._selectedMap = {}; |
| this._range = {}; |
| |
| this.refresh(option); |
| } |
| |
| DataRange.prototype = { |
| type : ecConfig.COMPONENT_TYPE_DATARANGE, |
| _textGap : 10, // 非值文字间隔 |
| _buildShape : function () { |
| // 值域元素组的位置参数,通过计算所得x, y, width, height |
| this._itemGroupLocation = this._getItemGroupLocation(); |
| this._buildBackground(); |
| if (this.dataRangeOption.splitNumber <= 0 |
| || this.dataRangeOption.calculable |
| ) { |
| this._buildGradient(); |
| } |
| else { |
| this._buildItem(); |
| } |
| |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| |
| this._syncShapeFromRange(); |
| }, |
| |
| /** |
| * 构建图例型的值域元素 |
| */ |
| _buildItem : function () { |
| var data = this._valueTextList; |
| var dataLength = data.length; |
| var itemName; |
| var itemShape; |
| var textShape; |
| var font = this.getFont(this.dataRangeOption.textStyle); |
| |
| var lastX = this._itemGroupLocation.x; |
| var lastY = this._itemGroupLocation.y; |
| var itemWidth = this.dataRangeOption.itemWidth; |
| var itemHeight = this.dataRangeOption.itemHeight; |
| var itemGap = this.dataRangeOption.itemGap; |
| var textHeight = zrArea.getTextHeight('国', font); |
| var color; |
| |
| if (this.dataRangeOption.orient == 'vertical' |
| && this.dataRangeOption.x == 'right' |
| ) { |
| lastX = this._itemGroupLocation.x |
| + this._itemGroupLocation.width |
| - itemWidth; |
| } |
| var needValueText = true; |
| if (this.dataRangeOption.text) { |
| needValueText = false; |
| // 第一个文字 |
| if (this.dataRangeOption.text[0]) { |
| textShape = this._getTextShape( |
| lastX, lastY, this.dataRangeOption.text[0] |
| ); |
| if (this.dataRangeOption.orient == 'horizontal') { |
| lastX += zrArea.getTextWidth( |
| this.dataRangeOption.text[0], |
| font |
| ) |
| + this._textGap; |
| } |
| else { |
| lastY += textHeight + this._textGap; |
| textShape.style.y += textHeight / 2 + this._textGap; |
| textShape.style.textBaseline = 'bottom'; |
| } |
| this.shapeList.push(new TextShape(textShape)); |
| } |
| } |
| |
| for (var i = 0; i < dataLength; i++) { |
| itemName = data[i]; |
| color = this.getColor((dataLength - i) * this._gap + this.dataRangeOption.min); |
| // 图形 |
| itemShape = this._getItemShape( |
| lastX, lastY, |
| itemWidth, itemHeight, |
| (this._selectedMap[i] ? color : '#ccc') |
| ); |
| itemShape._idx = i; |
| itemShape.onclick = this._dataRangeSelected; |
| this.shapeList.push(new RectangleShape(itemShape)); |
| |
| if (needValueText) { |
| // 文字 |
| textShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| x : lastX + itemWidth + 5, |
| y : lastY, |
| color : this._selectedMap[i] |
| ? this.dataRangeOption.textStyle.color |
| : '#ccc', |
| text: data[i], |
| textFont: font, |
| textBaseline: 'top' |
| }, |
| highlightStyle:{ |
| brushType: 'fill' |
| }, |
| clickable : true |
| }; |
| if (this.dataRangeOption.orient == 'vertical' |
| && this.dataRangeOption.x == 'right' |
| ) { |
| textShape.style.x -= (itemWidth + 10); |
| textShape.style.textAlign = 'right'; |
| } |
| textShape._idx = i; |
| textShape.onclick = this._dataRangeSelected; |
| this.shapeList.push(new TextShape(textShape)); |
| } |
| |
| if (this.dataRangeOption.orient == 'horizontal') { |
| lastX += itemWidth |
| + (needValueText ? 5 : 0) |
| + (needValueText |
| ? zrArea.getTextWidth(itemName, font) |
| : 0) |
| + itemGap; |
| } |
| else { |
| lastY += itemHeight + itemGap; |
| } |
| } |
| |
| if (!needValueText && this.dataRangeOption.text[1]) { |
| if (this.dataRangeOption.orient == 'horizontal') { |
| lastX = lastX - itemGap + this._textGap; |
| } |
| else { |
| lastY = lastY - itemGap + this._textGap; |
| } |
| // 最后一个文字 |
| textShape = this._getTextShape( |
| lastX, lastY, this.dataRangeOption.text[1] |
| ); |
| |
| if (this.dataRangeOption.orient != 'horizontal') { |
| textShape.style.y -= 5; |
| textShape.style.textBaseline = 'top'; |
| } |
| |
| this.shapeList.push(new TextShape(textShape)); |
| } |
| }, |
| |
| /** |
| * 构建渐变型的值域元素 |
| */ |
| _buildGradient : function () { |
| var itemShape; |
| var textShape; |
| var font = this.getFont(this.dataRangeOption.textStyle); |
| |
| var lastX = this._itemGroupLocation.x; |
| var lastY = this._itemGroupLocation.y; |
| var itemWidth = this.dataRangeOption.itemWidth; |
| var itemHeight = this.dataRangeOption.itemHeight; |
| var textHeight = zrArea.getTextHeight('国', font); |
| |
| |
| var needValueText = true; |
| if (this.dataRangeOption.text) { |
| needValueText = false; |
| // 第一个文字 |
| if (this.dataRangeOption.text[0]) { |
| textShape = this._getTextShape( |
| lastX, lastY, this.dataRangeOption.text[0] |
| ); |
| if (this.dataRangeOption.orient == 'horizontal') { |
| lastX += zrArea.getTextWidth( |
| this.dataRangeOption.text[0], |
| font |
| ) |
| + this._textGap; |
| } |
| else { |
| lastY += textHeight + this._textGap; |
| textShape.style.y += textHeight / 2 + this._textGap; |
| textShape.style.textBaseline = 'bottom'; |
| } |
| this.shapeList.push(new TextShape(textShape)); |
| } |
| } |
| |
| var zrColor = require('zrender/tool/color'); |
| var per = 1 / (this.dataRangeOption.color.length - 1); |
| var colorList = []; |
| for (var i = 0, l = this.dataRangeOption.color.length; i < l; i++) { |
| colorList.push([i * per, this.dataRangeOption.color[i]]); |
| } |
| if (this.dataRangeOption.orient == 'horizontal') { |
| itemShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| x : lastX, |
| y : lastY, |
| width : itemWidth * 10, |
| height : itemHeight, |
| color : zrColor.getLinearGradient( |
| lastX, lastY, lastX + itemWidth * 10, lastY, |
| colorList |
| ) |
| }, |
| hoverable : false |
| }; |
| lastX += itemWidth * 10 + this._textGap; |
| } |
| else { |
| itemShape = { |
| zlevel : this._zlevelBase, |
| style : { |
| x : lastX, |
| y : lastY, |
| width : itemWidth, |
| height : itemHeight * 10, |
| color : zrColor.getLinearGradient( |
| lastX, lastY, lastX, lastY + itemHeight * 10, |
| colorList |
| ) |
| }, |
| hoverable : false |
| }; |
| lastY += itemHeight * 10 + this._textGap; |
| } |
| this.shapeList.push(new RectangleShape(itemShape)); |
| if (this.dataRangeOption.calculable) { |
| // 可计算元素的位置缓存 |
| this._calculableLocation = itemShape.style; |
| this._buildFiller(); |
| this._bulidMask(); |
| this._bulidHandle(); |
| } |
| |
| if (!needValueText && this.dataRangeOption.text[1]) { |
| // 最后一个文字 |
| textShape = this._getTextShape( |
| lastX, lastY, this.dataRangeOption.text[1] |
| ); |
| |
| this.shapeList.push(new TextShape(textShape)); |
| } |
| }, |
| |
| /** |
| * 构建填充物 |
| */ |
| _buildFiller : function () { |
| this._fillerShae = { |
| zlevel : this._zlevelBase + 1, |
| style : { |
| x : this._calculableLocation.x, |
| y : this._calculableLocation.y, |
| width : this._calculableLocation.width, |
| height : this._calculableLocation.height, |
| color : 'rgba(255,255,255,0)' |
| }, |
| highlightStyle : { |
| strokeColor : 'rgba(255,255,255,0.5)', |
| lineWidth : 1 |
| }, |
| draggable : true, |
| ondrift : this._ondrift, |
| ondragend : this._ondragend, |
| _type : 'filler' |
| }; |
| this._fillerShae = new RectangleShape(this._fillerShae); |
| this.shapeList.push(this._fillerShae); |
| }, |
| |
| /** |
| * 构建拖拽手柄 |
| */ |
| _bulidHandle : function () { |
| var x = this._calculableLocation.x; |
| var y = this._calculableLocation.y; |
| var width = this._calculableLocation.width; |
| var height = this._calculableLocation.height; |
| |
| var font = this.getFont(this.dataRangeOption.textStyle); |
| var textHeight = zrArea.getTextHeight('国', font); |
| var textWidth = Math.max( |
| zrArea.getTextWidth(this._textFormat(this.dataRangeOption.max), font), |
| zrArea.getTextWidth(this._textFormat(this.dataRangeOption.min), font) |
| ) + 2; |
| |
| var pointListStart; |
| var textXStart; |
| var textYStart; |
| var coverRectStart; |
| var pointListEnd; |
| var textXEnd; |
| var textYEnd; |
| var coverRectEnd; |
| if (this.dataRangeOption.orient == 'horizontal') { |
| // 水平 |
| if (this.dataRangeOption.y != 'bottom') { |
| // 手柄统统在下方 |
| pointListStart = [ |
| [x, y], |
| [x, y + height + textHeight], |
| [x - textHeight, y + height + textHeight], |
| [x - 1, y + height], |
| [x - 1, y] |
| |
| ]; |
| textXStart = x - textWidth / 2 - textHeight; |
| textYStart = y + height + textHeight / 2 + 2; |
| coverRectStart = { |
| x : x - textWidth - textHeight, |
| y : y + height, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| |
| pointListEnd = [ |
| [x + width, y], |
| [x + width, y + height + textHeight], |
| [x + width + textHeight, y + height + textHeight], |
| [x + width + 1, y + height], |
| [x + width + 1, y] |
| ]; |
| textXEnd = x + width + textWidth / 2 + textHeight; |
| textYEnd = textYStart; |
| coverRectEnd = { |
| x : x + width, |
| y : y + height, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| } |
| else { |
| // 手柄在上方 |
| pointListStart = [ |
| [x, y + height], |
| [x, y - textHeight], |
| [x - textHeight, y - textHeight], |
| [x - 1, y], |
| [x - 1, y + height] |
| |
| ]; |
| textXStart = x - textWidth / 2 - textHeight; |
| textYStart = y - textHeight / 2 - 2; |
| coverRectStart = { |
| x : x - textWidth - textHeight, |
| y : y - textHeight, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| |
| pointListEnd = [ |
| [x + width, y + height], |
| [x + width, y - textHeight], |
| [x + width + textHeight, y - textHeight], |
| [x + width + 1, y], |
| [x + width + 1, y + height] |
| ]; |
| textXEnd = x + width + textWidth / 2 + textHeight; |
| textYEnd = textYStart; |
| coverRectEnd = { |
| x : x + width, |
| y : y - textHeight, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| } |
| } |
| else { |
| textWidth += textHeight; |
| // 垂直 |
| if (this.dataRangeOption.x != 'right') { |
| // 手柄统统在右侧 |
| pointListStart = [ |
| [x, y], |
| [x + width + textHeight, y], |
| [x + width + textHeight, y - textHeight], |
| [x + width, y - 1], |
| [x, y - 1] |
| ]; |
| textXStart = x + width + textWidth / 2 + textHeight / 2; |
| textYStart = y - textHeight / 2; |
| coverRectStart = { |
| x : x + width, |
| y : y - textHeight, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| |
| pointListEnd = [ |
| [x, y + height], |
| [x + width + textHeight, y + height], |
| [x + width + textHeight, y + textHeight + height], |
| [x + width, y + 1 + height], |
| [x, y + height + 1] |
| ]; |
| textXEnd = textXStart; |
| textYEnd = y + height + textHeight / 2; |
| coverRectEnd = { |
| x : x + width, |
| y : y + height, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| } |
| else { |
| // 手柄在左侧 |
| pointListStart = [ |
| [x + width, y], |
| [x - textHeight, y], |
| [x - textHeight, y - textHeight], |
| [x, y - 1], |
| [x + width, y - 1] |
| ]; |
| textXStart = x - textWidth / 2 - textHeight / 2; |
| textYStart = y - textHeight / 2; |
| coverRectStart = { |
| x : x - textWidth - textHeight, |
| y : y - textHeight, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| |
| pointListEnd = [ |
| [x + width, y + height], |
| [x - textHeight, y + height], |
| [x - textHeight, y + textHeight + height], |
| [x, y + 1 + height], |
| [x + width, y + height + 1] |
| ]; |
| textXEnd = textXStart; |
| textYEnd = y + height + textHeight / 2; |
| coverRectEnd = { |
| x : x - textWidth - textHeight, |
| y : y + height, |
| width : textWidth + textHeight, |
| height : textHeight |
| }; |
| } |
| } |
| |
| this._startShape = { |
| style : { |
| pointList : pointListStart, |
| text : this._textFormat(this.dataRangeOption.max), |
| textX : textXStart, |
| textY : textYStart, |
| color : this.getColor(this.dataRangeOption.max), |
| rect : coverRectStart, |
| x : pointListStart[0][0], |
| y : pointListStart[0][1], |
| _x : pointListStart[0][0], // 拖拽区域控制缓存 |
| _y : pointListStart[0][1] |
| } |
| }; |
| this._startShape.highlightStyle = { |
| strokeColor : this._startShape.style.color, |
| lineWidth : 1 |
| }; |
| |
| this._endShape = { |
| style : { |
| pointList : pointListEnd, |
| text : this._textFormat(this.dataRangeOption.min), |
| textX : textXEnd, |
| textY : textYEnd, |
| color : this.getColor(this.dataRangeOption.min), |
| rect : coverRectEnd, |
| x : pointListEnd[0][0], |
| y : pointListEnd[0][1], |
| _x : pointListEnd[0][0], // 拖拽区域控制缓存 |
| _y : pointListEnd[0][1] |
| } |
| }; |
| this._endShape.highlightStyle = { |
| strokeColor : this._endShape.style.color, |
| lineWidth : 1 |
| }; |
| |
| // 统一参数 |
| this._startShape.zlevel = this._endShape.zlevel = this._zlevelBase + 1; |
| this._startShape.draggable = this._endShape.draggable = true; |
| this._startShape.ondrift = this._endShape.ondrift = this._ondrift; |
| this._startShape.ondragend = this._endShape.ondragend = this._ondragend; |
| |
| this._startShape.style.textColor = this._endShape.style.textColor |
| = this.dataRangeOption.textStyle.color; |
| this._startShape.style.textAlign = this._endShape.style.textAlign = 'center'; |
| this._startShape.style.textPosition = this._endShape.style.textPosition = 'specific'; |
| this._startShape.style.textBaseline = this._endShape.style.textBaseline = 'middle'; |
| // for ondrif计算统一 |
| this._startShape.style.width = this._endShape.style.width = 0; |
| this._startShape.style.height = this._endShape.style.height = 0; |
| this._startShape.style.textPosition = this._endShape.style.textPosition = 'specific'; |
| |
| this._startShape = new HandlePolygonShape(this._startShape); |
| this._endShape = new HandlePolygonShape(this._endShape); |
| this.shapeList.push(this._startShape); |
| this.shapeList.push(this._endShape); |
| }, |
| |
| _bulidMask : function () { |
| var x = this._calculableLocation.x; |
| var y = this._calculableLocation.y; |
| var width = this._calculableLocation.width; |
| var height = this._calculableLocation.height; |
| this._startMask = { |
| zlevel : this._zlevelBase + 1, |
| style : { |
| x : x, |
| y : y, |
| width : this.dataRangeOption.orient == 'horizontal' |
| ? 0 : width, |
| height : this.dataRangeOption.orient == 'horizontal' |
| ? height : 0, |
| color : '#ccc' |
| }, |
| hoverable:false |
| }; |
| this._endMask = { |
| zlevel : this._zlevelBase + 1, |
| style : { |
| x : this.dataRangeOption.orient == 'horizontal' |
| ? x + width : x, |
| y : this.dataRangeOption.orient == 'horizontal' |
| ? y : y + height, |
| width : this.dataRangeOption.orient == 'horizontal' |
| ? 0 : width, |
| height : this.dataRangeOption.orient == 'horizontal' |
| ? height : 0, |
| color : '#ccc' |
| }, |
| hoverable:false |
| }; |
| this._startMask = new RectangleShape(this._startMask); |
| this._endMask = new RectangleShape(this._endMask); |
| this.shapeList.push(this._startMask); |
| this.shapeList.push(this._endMask); |
| }, |
| |
| _buildBackground : function () { |
| var pTop = this.dataRangeOption.padding[0]; |
| var pRight = this.dataRangeOption.padding[1]; |
| var pBottom = this.dataRangeOption.padding[2]; |
| var pLeft = this.dataRangeOption.padding[3]; |
| |
| this.shapeList.push(new RectangleShape({ |
| zlevel : this._zlevelBase, |
| hoverable :false, |
| style : { |
| x : this._itemGroupLocation.x - pLeft, |
| y : this._itemGroupLocation.y - pTop, |
| width : this._itemGroupLocation.width + pLeft + pRight, |
| height : this._itemGroupLocation.height + pTop + pBottom, |
| brushType : this.dataRangeOption.borderWidth === 0 |
| ? 'fill' : 'both', |
| color : this.dataRangeOption.backgroundColor, |
| strokeColor : this.dataRangeOption.borderColor, |
| lineWidth : this.dataRangeOption.borderWidth |
| } |
| })); |
| }, |
| |
| /** |
| * 根据选项计算值域实体的位置坐标 |
| */ |
| _getItemGroupLocation : function () { |
| var data = this._valueTextList; |
| var dataLength = data.length; |
| var itemGap = this.dataRangeOption.itemGap; |
| var itemWidth = this.dataRangeOption.itemWidth; |
| var itemHeight = this.dataRangeOption.itemHeight; |
| var totalWidth = 0; |
| var totalHeight = 0; |
| var font = this.getFont(this.dataRangeOption.textStyle); |
| var textHeight = zrArea.getTextHeight('国', font); |
| |
| if (this.dataRangeOption.orient == 'horizontal') { |
| // 水平布局,计算总宽度 |
| if (this.dataRangeOption.text |
| || this.dataRangeOption.splitNumber <= 0 |
| || this.dataRangeOption.calculable |
| ) { |
| // 指定文字或线性渐变 |
| totalWidth = |
| ((this.dataRangeOption.splitNumber <= 0 |
| || this.dataRangeOption.calculable) |
| ? (itemWidth * 10 + itemGap) |
| : dataLength * (itemWidth + itemGap)) |
| + (this.dataRangeOption.text |
| && typeof this.dataRangeOption.text[0] != 'undefined' |
| ? (zrArea.getTextWidth( |
| this.dataRangeOption.text[0], |
| font |
| ) + this._textGap) |
| : 0) |
| + (this.dataRangeOption.text |
| && typeof this.dataRangeOption.text[1] != 'undefined' |
| ? (zrArea.getTextWidth( |
| this.dataRangeOption.text[1], |
| font |
| ) + this._textGap) |
| : 0); |
| } |
| else { |
| // 值标签 |
| itemWidth += 5; |
| for (var i = 0; i < dataLength; i++) { |
| totalWidth += itemWidth |
| + zrArea.getTextWidth( |
| data[i], |
| font |
| ) |
| + itemGap; |
| } |
| } |
| totalWidth -= itemGap; // 减去最后一个的itemGap |
| totalHeight = Math.max(textHeight, itemHeight); |
| } |
| else { |
| // 垂直布局,计算总高度 |
| var maxWidth; |
| if (this.dataRangeOption.text |
| || this.dataRangeOption.splitNumber <= 0 |
| || this.dataRangeOption.calculable |
| ) { |
| // 指定文字或线性渐变 |
| totalHeight = |
| ((this.dataRangeOption.splitNumber <= 0 |
| || this.dataRangeOption.calculable) |
| ? (itemHeight * 10 + itemGap) |
| : dataLength * (itemHeight + itemGap)) |
| + (this.dataRangeOption.text |
| && typeof this.dataRangeOption.text[0] != 'undefined' |
| ? (this._textGap + textHeight) |
| : 0) |
| + (this.dataRangeOption.text |
| && typeof this.dataRangeOption.text[1] != 'undefined' |
| ? (this._textGap + textHeight) |
| : 0); |
| |
| maxWidth = Math.max( |
| zrArea.getTextWidth( |
| (this.dataRangeOption.text && this.dataRangeOption.text[0]) |
| || '', |
| font |
| ), |
| zrArea.getTextWidth( |
| (this.dataRangeOption.text && this.dataRangeOption.text[1]) |
| || '', |
| font |
| ) |
| ); |
| totalWidth = Math.max(itemWidth, maxWidth); |
| } |
| else { |
| totalHeight = (itemHeight + itemGap) * dataLength; |
| // 值标签 |
| itemWidth += 5; |
| maxWidth = 0; |
| for (var i = 0; i < dataLength; i++) { |
| maxWidth = Math.max( |
| maxWidth, |
| zrArea.getTextWidth( |
| data[i], |
| font |
| ) |
| ); |
| } |
| totalWidth = itemWidth + maxWidth; |
| } |
| totalHeight -= itemGap; // 减去最后一个的itemGap; |
| } |
| |
| var x; |
| var zrWidth = this.zr.getWidth(); |
| switch (this.dataRangeOption.x) { |
| case 'center' : |
| x = Math.floor((zrWidth - totalWidth) / 2); |
| break; |
| case 'left' : |
| x = this.dataRangeOption.padding[3] |
| + this.dataRangeOption.borderWidth; |
| break; |
| case 'right' : |
| x = zrWidth |
| - totalWidth |
| - this.dataRangeOption.padding[1] |
| - this.dataRangeOption.borderWidth; |
| break; |
| default : |
| x = this.parsePercent(this.dataRangeOption.x, zrWidth); |
| x = isNaN(x) ? 0 : x; |
| break; |
| } |
| |
| var y; |
| var zrHeight = this.zr.getHeight(); |
| switch (this.dataRangeOption.y) { |
| case 'top' : |
| y = this.dataRangeOption.padding[0] |
| + this.dataRangeOption.borderWidth; |
| break; |
| case 'bottom' : |
| y = zrHeight |
| - totalHeight |
| - this.dataRangeOption.padding[2] |
| - this.dataRangeOption.borderWidth; |
| break; |
| case 'center' : |
| y = Math.floor((zrHeight - totalHeight) / 2); |
| break; |
| default : |
| y = this.parsePercent(this.dataRangeOption.y, zrHeight); |
| y = isNaN(y) ? 0 : y; |
| break; |
| } |
| |
| if (this.dataRangeOption.calculable) { |
| // 留出手柄控件 |
| var handlerWidth = Math.max( |
| zrArea.getTextWidth(this.dataRangeOption.max, font), |
| zrArea.getTextWidth(this.dataRangeOption.min, font) |
| ) + textHeight; |
| if (this.dataRangeOption.orient == 'horizontal') { |
| if (x < handlerWidth) { |
| x = handlerWidth; |
| } |
| if (x + totalWidth + handlerWidth > zrWidth) { |
| x -= handlerWidth; |
| } |
| } |
| else { |
| if (y < textHeight) { |
| y = textHeight; |
| } |
| if (y + totalHeight + textHeight > zrHeight) { |
| y -= textHeight; |
| } |
| } |
| } |
| |
| return { |
| x : x, |
| y : y, |
| width : totalWidth, |
| height : totalHeight |
| }; |
| }, |
| |
| // 指定文本 |
| _getTextShape : function (x, y, text) { |
| return { |
| zlevel : this._zlevelBase, |
| style : { |
| x : (this.dataRangeOption.orient == 'horizontal' |
| ? x |
| : this._itemGroupLocation.x |
| + this._itemGroupLocation.width / 2 |
| ), |
| y : (this.dataRangeOption.orient == 'horizontal' |
| ? this._itemGroupLocation.y |
| + this._itemGroupLocation.height / 2 |
| : y |
| ), |
| color : this.dataRangeOption.textStyle.color, |
| text: text, |
| textFont: this.getFont(this.dataRangeOption.textStyle), |
| textBaseline: (this.dataRangeOption.orient == 'horizontal' |
| ? 'middle' : 'top'), |
| textAlign: (this.dataRangeOption.orient == 'horizontal' |
| ? 'left' : 'center') |
| }, |
| hoverable : false |
| }; |
| }, |
| |
| // 色尺legend item shape |
| _getItemShape : function (x, y, width, height, color) { |
| return { |
| zlevel : this._zlevelBase, |
| style : { |
| x : x, |
| y : y + 1, |
| width : width, |
| height : height - 2, |
| color : color |
| }, |
| highlightStyle: { |
| strokeColor: color, |
| lineWidth : 1 |
| }, |
| clickable : true |
| }; |
| }, |
| |
| /** |
| * 拖拽范围控制 |
| */ |
| __ondrift : function (shape, dx, dy) { |
| var x = this._calculableLocation.x; |
| var y = this._calculableLocation.y; |
| var width = this._calculableLocation.width; |
| var height = this._calculableLocation.height; |
| |
| if (this.dataRangeOption.orient == 'horizontal') { |
| if (shape.style.x + dx <= x) { |
| shape.style.x = x; |
| } |
| else if (shape.style.x + dx + shape.style.width >= x + width) { |
| shape.style.x = x + width - shape.style.width; |
| } |
| else { |
| shape.style.x += dx; |
| } |
| } |
| else { |
| if (shape.style.y + dy <= y) { |
| shape.style.y = y; |
| } |
| else if (shape.style.y + dy + shape.style.height >= y + height) { |
| shape.style.y = y + height - shape.style.height; |
| } |
| else { |
| shape.style.y += dy; |
| } |
| } |
| |
| if (shape._type == 'filler') { |
| this._syncHandleShape(); |
| } |
| else { |
| this._syncFillerShape(shape); |
| } |
| |
| if (this.dataRangeOption.realtime) { |
| this._syncData(); |
| } |
| |
| return true; |
| }, |
| |
| __ondragend : function () { |
| this.isDragend = true; |
| }, |
| |
| /** |
| * 数据项被拖拽出去 |
| */ |
| ondragend : function (param, status) { |
| if (!this.isDragend || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| |
| !this.dataRangeOption.realtime && this._syncData(); |
| |
| // 别status = {}赋值啊!! |
| status.dragOut = true; |
| status.dragIn = true; |
| |
| if (!this.dataRangeOption.realtime && false) { |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.DATA_RANGE, |
| null, |
| { |
| range : { |
| start : this._range.end, |
| end : this._range.start |
| } |
| }, |
| this.myChart |
| ); |
| } |
| |
| status.needRefresh = false; // 会有消息触发fresh,不用再刷一遍 |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| |
| return; |
| }, |
| |
| // 外部传入range |
| _syncShapeFromRange : function () { |
| var range = this.dataRangeOption.range || {}; |
| // 做一个反转 |
| this._range.end = typeof this._range.end != 'undefined' |
| ? this._range.end |
| : (typeof range.start != 'undefined' ? range.start : 0); |
| this._range.start = typeof this._range.start != 'undefined' |
| ? this._range.start |
| : (typeof range.end != 'undefined' ? range.end : 100); |
| |
| if (this._range.start != 100 || this._range.end !== 0) { |
| // 非默认满值同步一下图形 |
| if (this.dataRangeOption.orient == 'horizontal') { |
| // 横向 |
| var width = this._fillerShae.style.width; |
| this._fillerShae.style.x += |
| width * (100 - this._range.start) / 100; |
| this._fillerShae.style.width = |
| width * (this._range.start - this._range.end) / 100; |
| } |
| else { |
| // 纵向 |
| var height = this._fillerShae.style.height; |
| this._fillerShae.style.y += |
| height * (100 - this._range.start) / 100; |
| this._fillerShae.style.height = |
| height * (this._range.start - this._range.end) / 100; |
| } |
| this.zr.modShape(this._fillerShae.id); |
| this._syncHandleShape(); |
| } |
| }, |
| |
| _syncHandleShape : function () { |
| var x = this._calculableLocation.x; |
| var y = this._calculableLocation.y; |
| var width = this._calculableLocation.width; |
| var height = this._calculableLocation.height; |
| |
| if (this.dataRangeOption.orient == 'horizontal') { |
| this._startShape.style.x = this._fillerShae.style.x; |
| this._startMask.style.width = this._startShape.style.x - x; |
| |
| this._endShape.style.x = this._fillerShae.style.x |
| + this._fillerShae.style.width; |
| this._endMask.style.x = this._endShape.style.x; |
| this._endMask.style.width = x + width - this._endShape.style.x; |
| |
| this._range.start = Math.ceil( |
| 100 - (this._startShape.style.x - x) / width * 100 |
| ); |
| this._range.end = Math.floor( |
| 100 - (this._endShape.style.x - x) / width * 100 |
| ); |
| } |
| else { |
| this._startShape.style.y = this._fillerShae.style.y; |
| this._startMask.style.height = this._startShape.style.y - y; |
| |
| this._endShape.style.y = this._fillerShae.style.y |
| + this._fillerShae.style.height; |
| this._endMask.style.y = this._endShape.style.y; |
| this._endMask.style.height = y + height - this._endShape.style.y; |
| |
| this._range.start = Math.ceil( |
| 100 - (this._startShape.style.y - y) / height * 100 |
| ); |
| this._range.end = Math.floor( |
| 100 - (this._endShape.style.y - y) / height * 100 |
| ); |
| } |
| |
| this._syncShape(); |
| }, |
| |
| _syncFillerShape : function (e) { |
| var x = this._calculableLocation.x; |
| var y = this._calculableLocation.y; |
| var width = this._calculableLocation.width; |
| var height = this._calculableLocation.height; |
| |
| var a; |
| var b; |
| if (this.dataRangeOption.orient == 'horizontal') { |
| a = this._startShape.style.x; |
| b = this._endShape.style.x; |
| if (e.id == this._startShape.id && a >= b) { |
| // _startShape触发 |
| b = a; |
| this._endShape.style.x = a; |
| } |
| else if (e.id == this._endShape.id && a >= b) { |
| // _endShape触发 |
| a = b; |
| this._startShape.style.x = a; |
| } |
| this._fillerShae.style.x = a; |
| this._fillerShae.style.width = b - a; |
| this._startMask.style.width = a - x; |
| this._endMask.style.x = b; |
| this._endMask.style.width = x + width - b; |
| |
| this._range.start = Math.ceil(100 - (a - x) / width * 100); |
| this._range.end = Math.floor(100 - (b - x) / width * 100); |
| } |
| else { |
| a = this._startShape.style.y; |
| b = this._endShape.style.y; |
| if (e.id == this._startShape.id && a >= b) { |
| // _startShape触发 |
| b = a; |
| this._endShape.style.y = a; |
| } |
| else if (e.id == this._endShape.id && a >= b) { |
| // _endShape触发 |
| a = b; |
| this._startShape.style.y = a; |
| } |
| this._fillerShae.style.y = a; |
| this._fillerShae.style.height = b - a; |
| this._startMask.style.height = a - y; |
| this._endMask.style.y = b; |
| this._endMask.style.height = y + height - b; |
| |
| this._range.start = Math.ceil(100 - (a - y) / height * 100); |
| this._range.end = Math.floor(100 - (b - y) / height * 100); |
| } |
| |
| this._syncShape(); |
| }, |
| |
| _syncShape : function () { |
| this._startShape.position = [ |
| this._startShape.style.x - this._startShape.style._x, |
| this._startShape.style.y - this._startShape.style._y |
| ]; |
| |
| this._startShape.style.text = this._textFormat( |
| this._gap * this._range.start + this.dataRangeOption.min |
| ); |
| |
| this._startShape.style.color |
| = this._startShape.highlightStyle.strokeColor |
| = this.getColor( |
| this._gap * this._range.start + this.dataRangeOption.min |
| ); |
| |
| this._endShape.position = [ |
| this._endShape.style.x - this._endShape.style._x, |
| this._endShape.style.y - this._endShape.style._y |
| ]; |
| |
| this._endShape.style.text = this._textFormat( |
| this._gap * this._range.end + this.dataRangeOption.min |
| ); |
| |
| this._endShape.style.color |
| = this._endShape.highlightStyle.strokeColor |
| = this.getColor( |
| this._gap * this._range.end + this.dataRangeOption.min |
| ); |
| |
| this.zr.modShape(this._startShape.id); |
| this.zr.modShape(this._endShape.id); |
| this.zr.modShape(this._startMask.id); |
| this.zr.modShape(this._endMask.id); |
| this.zr.modShape(this._fillerShae.id); |
| this.zr.refresh(); |
| }, |
| |
| _syncData : function () { |
| if (this.dataRangeOption.realtime) { |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.DATA_RANGE, |
| null, |
| { |
| range : { |
| start : this._range.end, |
| end : this._range.start |
| } |
| }, |
| this.myChart |
| ); |
| } |
| }, |
| |
| |
| __dataRangeSelected : function (param) { |
| var idx = param.target._idx; |
| this._selectedMap[idx] = !this._selectedMap[idx]; |
| this.messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this.myChart); |
| }, |
| |
| _textFormat : function(valueStart, valueEnd) { |
| valueStart = valueStart.toFixed(this.dataRangeOption.precision); |
| valueEnd = typeof valueEnd != 'undefined' |
| ? valueEnd.toFixed(this.dataRangeOption.precision) : ''; |
| if (this.dataRangeOption.formatter) { |
| if (typeof this.dataRangeOption.formatter == 'string') { |
| return this.dataRangeOption.formatter.replace('{value}', valueStart) |
| .replace('{value2}', valueEnd); |
| } |
| else if (typeof this.dataRangeOption.formatter == 'function') { |
| return this.dataRangeOption.formatter.call( |
| this.myChart, valueStart, valueEnd |
| ); |
| } |
| } |
| |
| if (valueEnd !== '') { |
| return valueStart + ' - ' + valueEnd; |
| } |
| |
| return valueStart; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.option.dataRange = this.reformOption(this.option.dataRange); |
| // 补全padding属性 |
| this.option.dataRange.padding = this.reformCssArray( |
| this.option.dataRange.padding |
| ); |
| this.dataRangeOption = this.option.dataRange; |
| |
| var splitNumber = this.dataRangeOption.splitNumber <= 0 |
| || this.dataRangeOption.calculable |
| ? 100 |
| : this.dataRangeOption.splitNumber; |
| this._colorList = zrColor.getGradientColors( |
| this.dataRangeOption.color, |
| Math.max( |
| (splitNumber - this.dataRangeOption.color.length) |
| / (this.dataRangeOption.color.length - 1), |
| 0 |
| ) + 1 |
| ); |
| |
| if (this._colorList.length > splitNumber) { |
| var len = this._colorList.length; |
| var newColorList = [this._colorList[0]]; |
| var step = len / (splitNumber - 1); |
| for (var i = 1; i < splitNumber - 1; i++) { |
| newColorList.push(this._colorList[Math.floor(i * step)]); |
| } |
| newColorList.push(this._colorList[len - 1]); |
| this._colorList = newColorList; |
| } |
| // console.log(this._colorList.length) |
| |
| var precision = this.dataRangeOption.precision; |
| this._gap = (this.dataRangeOption.max - this.dataRangeOption.min) / splitNumber; |
| while (this._gap.toFixed(precision) - 0 != this._gap && precision < 5) { |
| // 精度自适应 |
| precision++; |
| } |
| this.dataRangeOption.precision = precision; |
| |
| this._gap = ( |
| (this.dataRangeOption.max - this.dataRangeOption.min) / splitNumber |
| ).toFixed(precision) - 0; |
| |
| this._valueTextList = []; |
| for (var i = 0; i < splitNumber; i++) { |
| this._selectedMap[i] = true; |
| this._valueTextList.unshift( |
| this._textFormat( |
| i * this._gap + this.dataRangeOption.min, |
| (i + 1) * this._gap + this.dataRangeOption.min |
| ) |
| ); |
| } |
| } |
| |
| this.clear(); |
| this._buildShape(); |
| }, |
| |
| getColor : function (value) { |
| if (isNaN(value)) { |
| return null; |
| } |
| |
| if (value < this.dataRangeOption.min) { |
| value = this.dataRangeOption.min; |
| } |
| else if (value > this.dataRangeOption.max) { |
| value = this.dataRangeOption.max; |
| } |
| |
| if (this.dataRangeOption.calculable) { |
| if (value - (this._gap * this._range.start + this.dataRangeOption.min) > 0.00005 |
| || value - (this._gap * this._range.end + this.dataRangeOption.min) < -0.00005) { |
| return null; |
| } |
| } |
| |
| var idx = this._colorList.length - Math.ceil( |
| (value - this.dataRangeOption.min) |
| / (this.dataRangeOption.max - this.dataRangeOption.min) |
| * this._colorList.length |
| ); |
| if (idx == this._colorList.length) { |
| idx--; |
| } |
| //console.log(value, idx,this._colorList[idx]) |
| if (this._selectedMap[idx]) { |
| return this._colorList[idx]; |
| } |
| else { |
| return null; |
| } |
| } |
| }; |
| |
| zrUtil.inherits(DataRange, Base); |
| |
| require('../component').define('dataRange', DataRange); |
| |
| return DataRange; |
| }); |
| |
| |
| |
| /** |
| * echarts图表类:散点图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/scatter',['require','../component/base','./base','../util/shape/Symbol','../component/axis','../component/grid','../component/dataZoom','../component/dataRange','../config','zrender/tool/util','zrender/tool/color','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var SymbolShape = require('../util/shape/Symbol'); |
| // 组件依赖 |
| require('../component/axis'); |
| require('../component/grid'); |
| require('../component/dataZoom'); |
| require('../component/dataRange'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Scatter(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this.refresh(option); |
| } |
| |
| Scatter.prototype = { |
| type : ecConfig.CHART_TYPE_SCATTER, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| this._sIndex2ColorMap = {}; // series默认颜色索引,seriesIndex索引到color |
| this._symbol = this.option.symbolList; |
| this._sIndex2ShapeMap = {}; // series图形类型,seriesIndex索引到_symbol |
| |
| this.selectedMap = {}; |
| this.xMarkMap = {}; |
| |
| var legend = this.component.legend; |
| var seriesArray = []; |
| var serie; // 临时映射变量 |
| var serieName; // 临时映射变量 |
| var iconShape; |
| var iconType; |
| for (var i = 0, l = series.length; i < l; i++) { |
| serie = series[i]; |
| serieName = serie.name; |
| if (serie.type == ecConfig.CHART_TYPE_SCATTER) { |
| series[i] = this.reformOption(series[i]); |
| this._sIndex2ShapeMap[i] = this.query(serie, 'symbol') |
| || this._symbol[i % this._symbol.length]; |
| if (legend){ |
| this.selectedMap[serieName] = legend.isSelected(serieName); |
| |
| this._sIndex2ColorMap[i] = zrColor.alpha(legend.getColor(serieName), 0.5); |
| |
| iconShape = legend.getItemShape(serieName); |
| if (iconShape) { |
| // 回调legend,换一个更形象的icon |
| var iconType = this._sIndex2ShapeMap[i]; |
| iconShape.style.brushType = iconType.match('empty') ? 'stroke' : 'both'; |
| iconType = iconType.replace('empty', '').toLowerCase(); |
| |
| if (iconType.match('rectangle')) { |
| iconShape.style.x += Math.round( |
| (iconShape.style.width - iconShape.style.height) / 2 |
| ); |
| iconShape.style.width = iconShape.style.height; |
| } |
| |
| if (iconType.match('star')) { |
| iconShape.style.n = (iconType.replace('star','') - 0) || 5; |
| iconType = 'star'; |
| } |
| |
| if (iconType.match('image')) { |
| iconShape.style.image = iconType.replace( |
| new RegExp('^image:\\/\\/'), '' |
| ); |
| iconShape.style.x += Math.round( |
| (iconShape.style.width - iconShape.style.height) / 2 |
| ); |
| iconShape.style.width = iconShape.style.height; |
| iconType = 'image'; |
| } |
| |
| iconShape.style.iconType = iconType; |
| legend.setItemShape(serieName, iconShape); |
| } |
| } |
| else { |
| this.selectedMap[serieName] = true; |
| this._sIndex2ColorMap[i] = this.zr.getColor(i); |
| } |
| |
| if (this.selectedMap[serieName]) { |
| seriesArray.push(i); |
| } |
| } |
| } |
| |
| this._buildSeries(seriesArray); |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建类目轴为水平方向的散点图系列 |
| */ |
| _buildSeries : function (seriesArray) { |
| if (seriesArray.length === 0) { |
| return; |
| } |
| var series = this.series; |
| var seriesIndex; |
| var serie; |
| var data; |
| var value; |
| var xAxis; |
| var yAxis; |
| |
| var pointList = {}; |
| var x; |
| var y; |
| for (var j = 0, k = seriesArray.length; j < k; j++) { |
| seriesIndex = seriesArray[j]; |
| serie = series[seriesIndex]; |
| if (serie.data.length === 0) { |
| continue; |
| } |
| |
| xAxis = this.component.xAxis.getAxis(serie.xAxisIndex || 0); |
| yAxis = this.component.yAxis.getAxis(serie.yAxisIndex || 0); |
| |
| pointList[seriesIndex] = []; |
| for (var i = 0, l = serie.data.length; i < l; i++) { |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| if (value == '-' || value.length < 2) { |
| // 数据格式不符 |
| continue; |
| } |
| x = xAxis.getCoord(value[0]); |
| y = yAxis.getCoord(value[1]); |
| pointList[seriesIndex].push([ |
| x, // 横坐标 |
| y, // 纵坐标 |
| i, // 数据index |
| data.name || '' // 名称 |
| ]); |
| |
| } |
| this.xMarkMap[seriesIndex] = this._markMap( |
| xAxis, yAxis, serie.data, pointList[seriesIndex] |
| ); |
| this.buildMark(seriesIndex); |
| } |
| |
| // console.log(pointList) |
| this._buildPointList(pointList); |
| }, |
| |
| _markMap : function (xAxis, yAxis, data, pointList) { |
| var xMarkMap = { |
| min0 : Number.POSITIVE_INFINITY, |
| max0 : Number.NEGATIVE_INFINITY, |
| sum0 : 0, |
| counter0 : 0, |
| average0 : 0, |
| min1 : Number.POSITIVE_INFINITY, |
| max1 : Number.NEGATIVE_INFINITY, |
| sum1 : 0, |
| counter1 : 0, |
| average1 : 0 |
| }; |
| var value; |
| for (var i = 0, l = pointList.length; i < l; i++) { |
| /** |
| x, // 横坐标 |
| y, // 纵坐标 |
| i, // 数据index |
| data.name || '' // 名称 |
| */ |
| value = data[pointList[i][2]].value || data[pointList[i][2]]; |
| // 横轴 |
| if (xMarkMap.min0 > value[0]) { |
| xMarkMap.min0 = value[0]; |
| xMarkMap.minY0 = pointList[i][1]; |
| xMarkMap.minX0 = pointList[i][0]; |
| } |
| if (xMarkMap.max0 < value[0]) { |
| xMarkMap.max0 = value[0]; |
| xMarkMap.maxY0 = pointList[i][1]; |
| xMarkMap.maxX0 = pointList[i][0]; |
| } |
| xMarkMap.sum0 += value[0]; |
| xMarkMap.counter0++; |
| |
| // 纵轴 |
| if (xMarkMap.min1 > value[1]) { |
| xMarkMap.min1 = value[1]; |
| xMarkMap.minY1 = pointList[i][1]; |
| xMarkMap.minX1 = pointList[i][0]; |
| } |
| if (xMarkMap.max1 < value[1]) { |
| xMarkMap.max1 = value[1]; |
| xMarkMap.maxY1 = pointList[i][1]; |
| xMarkMap.maxX1 = pointList[i][0]; |
| } |
| xMarkMap.sum1 += value[1]; |
| xMarkMap.counter1++; |
| } |
| |
| var gridX = this.component.grid.getX(); |
| var gridXend = this.component.grid.getXend(); |
| var gridY = this.component.grid.getY(); |
| var gridYend = this.component.grid.getYend(); |
| |
| xMarkMap.average0 = (xMarkMap.sum0 / xMarkMap.counter0).toFixed(2) - 0; |
| var x = xAxis.getCoord(xMarkMap.average0); |
| // 横轴平均纵向 |
| xMarkMap.averageLine0 = [ |
| [x, gridYend], |
| [x, gridY] |
| ]; |
| xMarkMap.minLine0 = [ |
| [xMarkMap.minX0, gridYend], |
| [xMarkMap.minX0, gridY] |
| ]; |
| xMarkMap.maxLine0 = [ |
| [xMarkMap.maxX0, gridYend], |
| [xMarkMap.maxX0, gridY] |
| ]; |
| |
| xMarkMap.average1 = (xMarkMap.sum1 / xMarkMap.counter1).toFixed(2) - 0; |
| var y = yAxis.getCoord(xMarkMap.average1); |
| // 纵轴平均横向 |
| xMarkMap.averageLine1 = [ |
| [gridX, y], |
| [gridXend, y] |
| ]; |
| xMarkMap.minLine1 = [ |
| [gridX, xMarkMap.minY1], |
| [gridXend, xMarkMap.minY1] |
| ]; |
| xMarkMap.maxLine1 = [ |
| [gridX, xMarkMap.maxY1], |
| [gridXend, xMarkMap.maxY1] |
| ]; |
| |
| return xMarkMap; |
| }, |
| |
| /** |
| * 生成折线和折线上的拐点 |
| */ |
| _buildPointList : function (pointList) { |
| var series = this.series; |
| var serie; |
| var seriesPL; |
| var singlePoint; |
| var shape; |
| for (var seriesIndex in pointList) { |
| serie = series[seriesIndex]; |
| seriesPL = pointList[seriesIndex]; |
| if (serie.large && serie.data.length > serie.largeThreshold) { |
| this.shapeList.push(this._getLargeSymbol( |
| seriesPL, |
| this.getItemStyleColor( |
| this.query( |
| serie, 'itemStyle.normal.color' |
| ), |
| seriesIndex, |
| -1 |
| ) || this._sIndex2ColorMap[seriesIndex] |
| )); |
| continue; |
| } |
| |
| /* |
| * pointlist=[ |
| * 0 x, |
| * 1 y, |
| * 2 数据index |
| * 3 名称 |
| * ] |
| */ |
| |
| for (var i = 0, l = seriesPL.length; i < l; i++) { |
| singlePoint = seriesPL[i]; |
| shape = this._getSymbol( |
| seriesIndex, // seriesIndex |
| singlePoint[2], // dataIndex |
| singlePoint[3], // name |
| singlePoint[0], // x |
| singlePoint[1] // y |
| ); |
| shape && this.shapeList.push(shape); |
| } |
| } |
| // console.log(this.shapeList) |
| }, |
| |
| /** |
| * 生成折线图上的拐点图形 |
| */ |
| _getSymbol : function (seriesIndex, dataIndex, name, x, y) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| |
| var dataRange = this.component.dataRange; |
| var rangColor; |
| if (dataRange) { |
| rangColor = isNaN(data[2]) |
| ? this._sIndex2ColorMap[seriesIndex] |
| : dataRange.getColor(data[2]); |
| if (!rangColor) { |
| return null; |
| } |
| } |
| else { |
| rangColor = this._sIndex2ColorMap[seriesIndex]; |
| } |
| |
| var itemShape = this.getSymbolShape( |
| serie, seriesIndex, data, dataIndex, name, |
| x, y, |
| this._sIndex2ShapeMap[seriesIndex], |
| rangColor, |
| 'rgba(0,0,0,0)', |
| 'vertical' |
| ); |
| itemShape.zlevel = this._zlevelBase; |
| itemShape._main = true; |
| return itemShape; |
| }, |
| |
| _getLargeSymbol : function (pointList, nColor) { |
| return new SymbolShape({ |
| zlevel : this._zlevelBase, |
| _main : true, |
| hoverable: false, |
| style : { |
| pointList : pointList, |
| color : nColor, |
| strokeColor : nColor |
| }, |
| highlightStyle : { |
| pointList : [] |
| } |
| }); |
| }, |
| |
| // 位置转换 |
| getMarkCoord : function (seriesIndex, mpData) { |
| var serie = this.series[seriesIndex]; |
| var xMarkMap = this.xMarkMap[seriesIndex]; |
| var xAxis = this.component.xAxis.getAxis(serie.xAxisIndex); |
| var yAxis = this.component.yAxis.getAxis(serie.yAxisIndex); |
| var pos; |
| |
| if (mpData.type |
| && (mpData.type == 'max' || mpData.type == 'min' || mpData.type == 'average') |
| ) { |
| // 特殊值内置支持 |
| // 默认取纵值 |
| var valueIndex = typeof mpData.valueIndex != 'undefined' |
| ? mpData.valueIndex : 1; |
| pos = [ |
| xMarkMap[mpData.type + 'X' + valueIndex], |
| xMarkMap[mpData.type + 'Y' + valueIndex], |
| xMarkMap[mpData.type + 'Line' + valueIndex], |
| xMarkMap[mpData.type + valueIndex] |
| ]; |
| } |
| else { |
| pos = [ |
| typeof mpData.xAxis != 'string' && xAxis.getCoordByIndex |
| ? xAxis.getCoordByIndex(mpData.xAxis || 0) |
| : xAxis.getCoord(mpData.xAxis || 0), |
| |
| typeof mpData.yAxis != 'string' && yAxis.getCoordByIndex |
| ? yAxis.getCoordByIndex(mpData.yAxis || 0) |
| : yAxis.getCoord(mpData.yAxis || 0) |
| ]; |
| } |
| |
| return pos; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| }, |
| |
| /** |
| * 值域响应 |
| * @param {Object} param |
| * @param {Object} status |
| */ |
| ondataRange : function (param, status) { |
| if (this.component.dataRange) { |
| this.refresh(); |
| status.needRefresh = true; |
| } |
| return; |
| } |
| }; |
| |
| zrUtil.inherits(Scatter, ChartBase); |
| zrUtil.inherits(Scatter, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('scatter', Scatter); |
| |
| return Scatter; |
| }); |
| /** |
| * echarts图表类:K线图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/k',['require','../component/base','./base','../util/shape/Candle','../component/axis','../component/grid','../component/dataZoom','../config','../util/ecData','zrender/tool/util','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var CandleShape = require('../util/shape/Candle'); |
| // 组件依赖 |
| require('../component/axis'); |
| require('../component/grid'); |
| require('../component/dataZoom'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function K(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this.refresh(option); |
| } |
| |
| K.prototype = { |
| type : ecConfig.CHART_TYPE_K, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| this.selectedMap = {}; |
| |
| // 水平垂直双向series索引 ,position索引到seriesIndex |
| var _position2sIndexMap = { |
| top : [], |
| bottom : [] |
| }; |
| var xAxis; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type == ecConfig.CHART_TYPE_K) { |
| series[i] = this.reformOption(series[i]); |
| xAxis = this.component.xAxis.getAxis(series[i].xAxisIndex); |
| if (xAxis.type == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| _position2sIndexMap[xAxis.getPosition()].push(i); |
| } |
| } |
| } |
| //console.log(_position2sIndexMap) |
| for (var position in _position2sIndexMap) { |
| if (_position2sIndexMap[position].length > 0) { |
| this._buildSinglePosition( |
| position, _position2sIndexMap[position] |
| ); |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个方向上的K线图 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSinglePosition : function (position, seriesArray) { |
| var mapData = this._mapData(seriesArray); |
| var locationMap = mapData.locationMap; |
| var maxDataLength = mapData.maxDataLength; |
| |
| if (maxDataLength === 0 || locationMap.length === 0) { |
| return; |
| } |
| this._buildHorizontal(seriesArray, maxDataLength, locationMap); |
| |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| this.buildMark(seriesArray[i]); |
| } |
| }, |
| |
| /** |
| * 数据整形 |
| * 数组位置映射到系列索引 |
| */ |
| _mapData : function (seriesArray) { |
| var series = this.series; |
| var serie; // 临时映射变量 |
| var serieName; // 临时映射变量 |
| var legend = this.component.legend; |
| var locationMap = []; // 需要返回的东西:数组位置映射到系列索引 |
| var maxDataLength = 0; // 需要返回的东西:最大数据长度 |
| // 计算需要显示的个数和分配位置并记在下面这个结构里 |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| serie = series[seriesArray[i]]; |
| serieName = serie.name; |
| if (legend){ |
| this.selectedMap[serieName] = legend.isSelected(serieName); |
| } else { |
| this.selectedMap[serieName] = true; |
| } |
| |
| if (this.selectedMap[serieName]) { |
| locationMap.push(seriesArray[i]); |
| } |
| // 兼职帮算一下最大长度 |
| maxDataLength = Math.max(maxDataLength, serie.data.length); |
| } |
| return { |
| locationMap : locationMap, |
| maxDataLength : maxDataLength |
| }; |
| }, |
| |
| /** |
| * 构建类目轴为水平方向的K线图系列 |
| */ |
| _buildHorizontal : function (seriesArray, maxDataLength, locationMap) { |
| var series = this.series; |
| // 确定类目轴和数值轴,同一方向随便找一个即可 |
| var seriesIndex; |
| var serie; |
| var xAxisIndex; |
| var categoryAxis; |
| var yAxisIndex; // 数值轴各异 |
| var valueAxis; // 数值轴各异 |
| |
| var pointList = {}; |
| var candleWidth; |
| var data; |
| var value; |
| var barMaxWidth; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| seriesIndex = locationMap[j]; |
| serie = series[seriesIndex]; |
| |
| xAxisIndex = serie.xAxisIndex || 0; |
| categoryAxis = this.component.xAxis.getAxis(xAxisIndex); |
| candleWidth = serie.barWidth |
| || Math.floor(categoryAxis.getGap() / 2); |
| barMaxWidth = serie.barMaxWidth; |
| if (barMaxWidth && barMaxWidth < candleWidth) { |
| candleWidth = barMaxWidth; |
| } |
| yAxisIndex = serie.yAxisIndex || 0; |
| valueAxis = this.component.yAxis.getAxis(yAxisIndex); |
| |
| pointList[seriesIndex] = []; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| if (typeof categoryAxis.getNameByIndex(i) == 'undefined') { |
| // 系列数据超出类目轴长度 |
| break; |
| } |
| |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| if (value == '-' || value.length != 4) { |
| // 数据格式不符 |
| continue; |
| } |
| pointList[seriesIndex].push([ |
| categoryAxis.getCoordByIndex(i), // 横坐标 |
| candleWidth, |
| valueAxis.getCoord(value[0]), // 纵坐标:开盘 |
| valueAxis.getCoord(value[1]), // 纵坐标:收盘 |
| valueAxis.getCoord(value[2]), // 纵坐标:最低 |
| valueAxis.getCoord(value[3]), // 纵坐标:最高 |
| i, // 数据index |
| categoryAxis.getNameByIndex(i) // 类目名称 |
| ]); |
| } |
| } |
| // console.log(pointList) |
| this._buildKLine(seriesArray, pointList); |
| }, |
| |
| /** |
| * 生成K线 |
| */ |
| _buildKLine : function (seriesArray, pointList) { |
| var series = this.series; |
| // normal: |
| var nLineWidth; |
| var nLineColor; |
| var nLineColor0; // 阴线 |
| var nColor; |
| var nColor0; // 阴线 |
| |
| // emphasis: |
| var eLineWidth; |
| var eLineColor; |
| var eLineColor0; |
| var eColor; |
| var eColor0; |
| |
| var serie; |
| var queryTarget; |
| var data; |
| var seriesPL; |
| var singlePoint; |
| var candleType; |
| |
| var seriesIndex; |
| for (var sIdx = 0, len = seriesArray.length; sIdx < len; sIdx++) { |
| seriesIndex = seriesArray[sIdx]; |
| serie = series[seriesIndex]; |
| seriesPL = pointList[seriesIndex]; |
| |
| if (this._isLarge(seriesPL)) { |
| seriesPL = this._getLargePointList(seriesPL); |
| } |
| |
| if (serie.type == ecConfig.CHART_TYPE_K |
| && typeof seriesPL != 'undefined' |
| ) { |
| // 多级控制 |
| queryTarget = serie; |
| nLineWidth = this.query( |
| queryTarget, 'itemStyle.normal.lineStyle.width' |
| ); |
| nLineColor = this.query( |
| queryTarget, 'itemStyle.normal.lineStyle.color' |
| ); |
| nLineColor0 = this.query( |
| queryTarget, 'itemStyle.normal.lineStyle.color0' |
| ); |
| nColor = this.query( |
| queryTarget, 'itemStyle.normal.color' |
| ); |
| nColor0 = this.query( |
| queryTarget, 'itemStyle.normal.color0' |
| ); |
| |
| eLineWidth = this.query( |
| queryTarget, 'itemStyle.emphasis.lineStyle.width' |
| ); |
| eLineColor = this.query( |
| queryTarget, 'itemStyle.emphasis.lineStyle.color' |
| ); |
| eLineColor0 = this.query( |
| queryTarget, 'itemStyle.emphasis.lineStyle.color0' |
| ); |
| eColor = this.query( |
| queryTarget, 'itemStyle.emphasis.color' |
| ); |
| eColor0 = this.query( |
| queryTarget, 'itemStyle.emphasis.color0' |
| ); |
| |
| /* |
| * pointlist=[ |
| * 0 x, |
| * 1 width, |
| * 2 y0, |
| * 3 y1, |
| * 4 y2, |
| * 5 y3, |
| * 6 dataIndex, |
| * 7 categoryName |
| * ] |
| */ |
| for (var i = 0, l = seriesPL.length; i < l; i++) { |
| singlePoint = seriesPL[i]; |
| data = serie.data[singlePoint[6]]; |
| queryTarget = data; |
| candleType = singlePoint[3] < singlePoint[2]; |
| this.shapeList.push(this._getCandle( |
| seriesIndex, // seriesIndex |
| singlePoint[6], // dataIndex |
| singlePoint[7], // name |
| |
| singlePoint[0], // x |
| singlePoint[1], // width |
| singlePoint[2], // y开盘 |
| singlePoint[3], // y收盘 |
| singlePoint[4], // y最低 |
| singlePoint[5], // y最高 |
| |
| // 填充颜色 |
| candleType |
| ? (this.query( // 阳 |
| queryTarget, 'itemStyle.normal.color' |
| ) || nColor) |
| : (this.query( // 阴 |
| queryTarget, 'itemStyle.normal.color0' |
| ) || nColor0), |
| |
| // 线宽 |
| this.query( |
| queryTarget, 'itemStyle.normal.lineStyle.width' |
| ) || nLineWidth, |
| |
| // 线色 |
| candleType |
| ? (this.query( // 阳 |
| queryTarget, |
| 'itemStyle.normal.lineStyle.color' |
| ) || nLineColor) |
| : (this.query( // 阴 |
| queryTarget, |
| 'itemStyle.normal.lineStyle.color0' |
| ) || nLineColor0), |
| |
| //------------高亮 |
| |
| // 填充颜色 |
| candleType |
| ? (this.query( // 阳 |
| queryTarget, 'itemStyle.emphasis.color' |
| ) || eColor || nColor) |
| : (this.query( // 阴 |
| queryTarget, 'itemStyle.emphasis.color0' |
| ) || eColor0 || nColor0), |
| |
| // 线宽 |
| this.query( |
| queryTarget, 'itemStyle.emphasis.lineStyle.width' |
| ) || eLineWidth || nLineWidth, |
| |
| // 线色 |
| candleType |
| ? (this.query( // 阳 |
| queryTarget, |
| 'itemStyle.emphasis.lineStyle.color' |
| ) || eLineColor || nLineColor) |
| : (this.query( // 阴 |
| queryTarget, |
| 'itemStyle.emphasis.lineStyle.color0' |
| ) || eLineColor0 || nLineColor0) |
| )); |
| } |
| } |
| } |
| // console.log(this.shapeList) |
| }, |
| |
| _isLarge : function(singlePL) { |
| return singlePL[0][1] < 0.5; |
| }, |
| |
| /** |
| * 大规模pointList优化 |
| */ |
| _getLargePointList : function(singlePL) { |
| var total = this.component.grid.getWidth(); |
| var len = singlePL.length; |
| var newList = []; |
| for (var i = 0; i < total; i++) { |
| newList[i] = singlePL[Math.floor(len / total * i)]; |
| } |
| return newList; |
| }, |
| |
| /** |
| * 生成K线图上的图形 |
| */ |
| _getCandle : function ( |
| seriesIndex, dataIndex, name, |
| x, width, y0, y1, y2, y3, |
| nColor, nLinewidth, nLineColor, |
| eColor, eLinewidth, eLineColor |
| ) { |
| var series = this.series; |
| var itemShape = { |
| zlevel : this._zlevelBase, |
| clickable: true, |
| style : { |
| x : x, |
| y : [y0, y1, y2, y3], |
| width : width, |
| color : nColor, |
| strokeColor : nLineColor, |
| lineWidth : nLinewidth, |
| brushType : 'both' |
| }, |
| highlightStyle : { |
| color : eColor, |
| strokeColor : eLineColor, |
| lineWidth : eLinewidth |
| }, |
| _seriesIndex: seriesIndex |
| }; |
| ecData.pack( |
| itemShape, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| name |
| ); |
| |
| itemShape = new CandleShape(itemShape); |
| return itemShape; |
| }, |
| |
| // 位置转换 |
| getMarkCoord : function (seriesIndex, mpData) { |
| var serie = this.series[seriesIndex]; |
| var xAxis = this.component.xAxis.getAxis(serie.xAxisIndex); |
| var yAxis = this.component.yAxis.getAxis(serie.yAxisIndex); |
| |
| return [ |
| typeof mpData.xAxis != 'string' && xAxis.getCoordByIndex |
| ? xAxis.getCoordByIndex(mpData.xAxis || 0) |
| : xAxis.getCoord(mpData.xAxis || 0), |
| |
| typeof mpData.yAxis != 'string' && yAxis.getCoordByIndex |
| ? yAxis.getCoordByIndex(mpData.yAxis || 0) |
| : yAxis.getCoord(mpData.yAxis || 0) |
| ]; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| }, |
| |
| /** |
| * 动画设定 |
| */ |
| addDataAnimation : function (params) { |
| var series = this.series; |
| var aniMap = {}; // seriesIndex索引参数 |
| for (var i = 0, l = params.length; i < l; i++) { |
| aniMap[params[i][0]] = params[i]; |
| } |
| var x; |
| var dx; |
| var y; |
| var serie; |
| var seriesIndex; |
| var dataIndex; |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| seriesIndex = this.shapeList[i]._seriesIndex; |
| if (aniMap[seriesIndex] && !aniMap[seriesIndex][3]) { |
| // 有数据删除才有移动的动画 |
| if (this.shapeList[i].type == 'candle') { |
| dataIndex = ecData.get(this.shapeList[i], 'dataIndex'); |
| serie = series[seriesIndex]; |
| if (aniMap[seriesIndex][2] |
| && dataIndex == serie.data.length - 1 |
| ) { |
| // 队头加入删除末尾 |
| this.zr.delShape(this.shapeList[i].id); |
| continue; |
| } |
| else if (!aniMap[seriesIndex][2] && dataIndex === 0) { |
| // 队尾加入删除头部 |
| this.zr.delShape(this.shapeList[i].id); |
| continue; |
| } |
| dx = this.component.xAxis.getAxis( |
| serie.xAxisIndex || 0 |
| ).getGap(); |
| x = aniMap[seriesIndex][2] ? dx : -dx; |
| y = 0; |
| this.zr.animate(this.shapeList[i].id, '') |
| .when( |
| 500, |
| {position : [x, y]} |
| ) |
| .start(); |
| } |
| } |
| } |
| } |
| }; |
| |
| zrUtil.inherits(K, ChartBase); |
| zrUtil.inherits(K, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('k', K); |
| |
| return K; |
| }); |
| /** |
| * echarts坐标处理方法 |
| * |
| * @author Neil (杨骥, yangji01@baidu.com) |
| */ |
| |
| define( |
| 'echarts/util/coordinates',['require','zrender/tool/math'],function (require) { |
| var zrMath = require('zrender/tool/math'); |
| |
| /** |
| * 极坐标转直角坐标 |
| * |
| * @param {number} 半径 |
| * @param {number} 角度 |
| * |
| * @return {Array.<number>} 直角坐标[x,y] |
| */ |
| function polar2cartesian(r, theta) { |
| return [r * zrMath.sin(theta), r*zrMath.cos(theta)]; |
| } |
| |
| /** |
| * 直角坐标转极坐标 |
| * |
| * @param {number} 横坐标 |
| * @param {number} 纵坐标 |
| * |
| * @return {Array.<number>} 极坐标[r,theta] |
| */ |
| function cartesian2polar(x, y) { |
| return [Math.sqrt(x * x + y * y), Math.atan(y / x)]; |
| } |
| |
| return { |
| polar2cartesian : polar2cartesian, |
| cartesian2polar : cartesian2polar |
| }; |
| } |
| ); |
| /** |
| * echarts组件类:极坐标 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Neil (杨骥, yangji01@baidu.com) |
| * |
| */ |
| define('echarts/component/polar',['require','./base','zrender/shape/Text','zrender/shape/Line','zrender/shape/Polygon','zrender/shape/Circle','zrender/shape/Ring','../config','zrender/tool/util','../util/coordinates','../component'],function (require) { |
| var Base = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var PolygonShape = require('zrender/shape/Polygon'); |
| var Circle = require('zrender/shape/Circle'); |
| var Ring = require('zrender/shape/Ring'); |
| |
| var ecConfig = require('../config'); |
| var zrUtil = require('zrender/tool/util'); |
| var ecCoordinates = require('../util/coordinates'); |
| |
| function Polar(ecTheme, messageCenter, zr, option, myChart) { |
| Base.call(this, ecTheme, messageCenter, zr, option, myChart); |
| |
| this.refresh(option); |
| } |
| |
| Polar.prototype = { |
| type : ecConfig.COMPONENT_TYPE_POLAR, |
| |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| for (var i = 0; i < this.polar.length; i ++) { |
| this._index = i; |
| this.reformOption(this.polar[i]); |
| |
| this._queryTarget = [this.polar[i], this.option]; |
| this._createVector(i); |
| this._buildSpiderWeb(i); |
| |
| this._buildText(i); |
| |
| this._adjustIndicatorValue(i); |
| this._addAxisLabel(i); |
| } |
| |
| for (var i = 0; i < this.shapeList.length; i ++) { |
| this.zr.addShape(this.shapeList[i]); |
| } |
| }, |
| |
| /** |
| * 生成蜘蛛网顶点坐标 |
| * @param {number} polar的index |
| */ |
| _createVector : function (index) { |
| var item = this.polar[index]; |
| var indicator = this.deepQuery(this._queryTarget, 'indicator'); |
| var length = indicator.length; |
| var startAngle = item.startAngle ; |
| var dStep = 2 * Math.PI / length; |
| var radius = this._getRadius(); |
| var __ecIndicator = item.__ecIndicator = []; |
| var vector; |
| |
| for (var i = 0 ;i < length ; i ++) { |
| vector = ecCoordinates.polar2cartesian( |
| radius, startAngle * Math.PI / 180 + dStep * i |
| ); |
| __ecIndicator.push({ |
| // 将图形翻转 |
| vector : [vector[1], -vector[0]] |
| }); |
| } |
| }, |
| |
| /** |
| * 获取外圈的半径 |
| * |
| * @return {number} |
| */ |
| _getRadius : function () { |
| var item = this.polar[this._index]; |
| return this.parsePercent( |
| item.radius, |
| Math.min(this.zr.getWidth(), this.zr.getHeight()) / 2 |
| ); |
| }, |
| |
| /** |
| * 构建蜘蛛网 |
| * @param {number} polar的index |
| */ |
| _buildSpiderWeb : function (index) { |
| var item = this.polar[index]; |
| var __ecIndicator = item.__ecIndicator; |
| var splitArea = item.splitArea; |
| var splitLine = item.splitLine; |
| |
| var center = this.getCenter(index); |
| var splitNumber = item.splitNumber; |
| |
| var strokeColor = splitLine.lineStyle.color; |
| var lineWidth = splitLine.lineStyle.width; |
| var show = splitLine.show; |
| |
| var axisLine = this.deepQuery(this._queryTarget, 'axisLine'); |
| |
| this._addArea( |
| __ecIndicator, splitNumber, center, |
| splitArea, strokeColor, lineWidth, show |
| ); |
| |
| this._addLine( |
| __ecIndicator, center, axisLine |
| ); |
| }, |
| |
| /** |
| * 绘制axisLabel |
| */ |
| _addAxisLabel : function (index) { |
| var item = this.polar[index]; |
| var indicator = this.deepQuery(this._queryTarget, 'indicator'); |
| var __ecIndicator = item.__ecIndicator; |
| var axisLabel; |
| var vector; |
| var style; |
| var newStyle; |
| var splitNumber = this.deepQuery(this._queryTarget, 'splitNumber'); |
| var center = this.getCenter(index); |
| var vector; |
| var value; |
| var text; |
| var theta; |
| // var startAngle = this.deepQuery(this._queryTarget, 'startAngle'); |
| var offset; |
| var precision = this.deepQuery(this._queryTarget, 'precision'); |
| |
| for (var i = 0; i < indicator.length; i ++) { |
| axisLabel = this.deepQuery( |
| [indicator[i], item, this.option], 'axisLabel' |
| ); |
| |
| if (axisLabel.show) { |
| style = {}; |
| style.textFont = this.getFont(); |
| //Todo: bug fix |
| style = zrUtil.merge(style, axisLabel); |
| style.lineWidth = style.width; |
| |
| vector = __ecIndicator[i].vector; |
| value = __ecIndicator[i].value; |
| theta = i / indicator.length * 2 * Math.PI; |
| offset = axisLabel.offset || 10; |
| |
| for (var j = 1 ; j <= splitNumber; j ++) { |
| newStyle = zrUtil.merge({}, style); |
| text = |
| j * (value.max - value.min) / splitNumber |
| + value.min; |
| if (precision) { |
| text = text.toFixed(precision); |
| } |
| newStyle.text = this.numAddCommas(text); |
| newStyle.x = j * vector[0] / splitNumber |
| + Math.cos(theta) * offset + center[0]; |
| newStyle.y = j * vector[1] / splitNumber |
| + Math.sin(theta) * offset + center[1]; |
| |
| this.shapeList.push(new TextShape({ |
| zlevel : this._zlevelBase, |
| style : newStyle, |
| draggable : false, |
| hoverable : false |
| })); |
| } |
| } |
| } |
| }, |
| |
| /** |
| * 绘制坐标头的文字 |
| * @param {number} polar的index |
| */ |
| _buildText : function (index) { |
| var item = this.polar[index]; |
| var __ecIndicator = item.__ecIndicator; |
| var vector; |
| var indicator = this.deepQuery(this._queryTarget, 'indicator'); |
| var center = this.getCenter(index); |
| var style; |
| var textAlign; |
| var name; |
| var rotation; |
| var x = 0; |
| var y = 0; |
| var margin; |
| var textStyle; |
| |
| for (var i = 0; i < indicator.length; i ++) { |
| name = this.deepQuery( |
| [indicator[i], item, this.option], 'name' |
| ); |
| |
| if (!name.show) { |
| continue; |
| } |
| textStyle = this.deepQuery( |
| [name, item, this.option], |
| 'textStyle' |
| ); |
| |
| style = {}; |
| |
| style.textFont = this.getFont(textStyle); |
| style.color = textStyle.color; |
| |
| if (typeof name.formatter == 'function') { |
| style.text = name.formatter.call(this.myChart, indicator[i].text, i); |
| } |
| else if (typeof name.formatter == 'string'){ |
| style.text = name.formatter.replace( |
| '{value}', indicator[i].text |
| ); |
| } |
| else { |
| style.text = indicator[i].text; |
| } |
| __ecIndicator[i].text = style.text; |
| |
| vector = __ecIndicator[i].vector; |
| |
| if (Math.round(vector[0]) > 0) { |
| textAlign = 'left'; |
| } |
| else if (Math.round(vector[0]) < 0) { |
| textAlign = 'right'; |
| } |
| else { |
| textAlign = 'center'; |
| } |
| |
| if (!name.margin) { |
| vector = this._mapVector(vector, center, 1.2); |
| } |
| else { |
| margin = name.margin; |
| x = vector[0] > 0 ? margin : - margin; |
| y = vector[1] > 0 ? margin : - margin; |
| |
| x = vector[0] === 0 ? 0 : x; |
| y = vector[1] === 0 ? 0 : y; |
| vector = this._mapVector(vector, center, 1); |
| } |
| |
| |
| style.textAlign = textAlign; |
| style.x = vector[0] + x; |
| style.y = vector[1] + y; |
| |
| if (name.rotate) { |
| rotation = [ |
| name.rotate / 180 * Math.PI, |
| vector[0], vector[1] |
| ]; |
| } |
| else { |
| rotation = [0, 0, 0]; |
| } |
| |
| this.shapeList.push(new TextShape({ |
| zlevel : this._zlevelBase, |
| style : style, |
| draggable : false, |
| hoverable : false, |
| rotation : rotation |
| })); |
| } |
| }, |
| |
| getIndicatorText : function(polarIndex, indicatorIndex) { |
| return this.polar[polarIndex] |
| && this.polar[polarIndex].__ecIndicator[indicatorIndex] |
| && this.polar[polarIndex].__ecIndicator[indicatorIndex].text; |
| }, |
| |
| /** |
| * 添加一个隐形的盒子 当做drop的容器 暴露给外部的图形类使用 |
| * @param {number} polar的index |
| * @return {Object} 添加的盒子图形 |
| */ |
| getDropBox : function (index) { |
| var index = index || 0; |
| var item = this.polar[index]; |
| var center = this.getCenter(index); |
| var __ecIndicator = item.__ecIndicator; |
| var len = __ecIndicator.length; |
| var pointList = []; |
| var vector; |
| var shape; |
| var type = item.type; |
| |
| if (type == 'polygon') { |
| for (var i = 0; i < len; i ++) { |
| vector = __ecIndicator[i].vector; |
| pointList.push(this._mapVector(vector, center, 1.2)); |
| } |
| shape = this._getShape( |
| pointList, 'fill', 'rgba(0,0,0,0)', '', 1 |
| ); |
| } else if (type == 'circle') { |
| shape = this._getCircle( |
| '', 1, 1.2, center, 'fill', 'rgba(0,0,0,0)' |
| ); |
| } |
| |
| return shape; |
| }, |
| |
| /** |
| * 绘制蜘蛛网的正n变形 |
| * |
| * @param {Array<Object>} 指标数组 |
| * @param {number} 分割线数量 |
| * @param {Array<number>} 中点坐标 |
| * @param {Object} 分割区域对象 |
| * @param {string} 线条颜色 |
| * @param {number} 线条宽度 |
| */ |
| _addArea : function ( |
| __ecIndicator, splitNumber, center, |
| splitArea, strokeColor, lineWidth, show |
| ) { |
| var shape; |
| var scale; |
| var scale1; |
| var pointList; |
| var type = this.deepQuery(this._queryTarget, 'type'); |
| |
| for (var i = 0; i < splitNumber ; i ++ ) { |
| scale = (splitNumber - i) / splitNumber; |
| |
| if (show) { |
| if (type == 'polygon') { |
| pointList = this._getPointList( |
| __ecIndicator, scale, center); |
| shape = this._getShape( |
| pointList, 'stroke', '', strokeColor, lineWidth |
| ); |
| } else if (type == 'circle') { |
| shape = this._getCircle( |
| strokeColor, lineWidth, scale, center, 'stroke' |
| ); |
| } |
| |
| this.shapeList.push(shape); |
| } |
| |
| if (splitArea.show) { |
| scale1 = (splitNumber - i - 1) / splitNumber; |
| this._addSplitArea( |
| __ecIndicator, splitArea, scale, scale1, center, i |
| ); |
| } |
| } |
| }, |
| |
| /** |
| * 绘制圆 |
| * |
| * @param {string} strokeColor |
| * @param {number} lineWidth |
| * @param {number} scale |
| * @param {Array.<number>} center |
| * @param {string} brushType |
| * @param {string} color |
| * @return {Circle} |
| */ |
| _getCircle : function ( |
| strokeColor, lineWidth, scale, center, brushType, color |
| ) { |
| var radius = this._getRadius(); |
| return new Circle({ |
| zlevel : this._zlevelBase, |
| style: { |
| x: center[0], |
| y: center[1], |
| r: radius * scale, |
| brushType: brushType, |
| strokeColor: strokeColor, |
| lineWidth: lineWidth, |
| color: color |
| }, |
| hoverable : false, |
| draggable : false |
| }); |
| }, |
| |
| /** |
| * 绘制圆环 |
| * |
| * @param {string} color 间隔颜色 |
| * @param {number} scale0 小圆的scale |
| * @param {number} scale1 大圆的scale |
| * @param {Array.<number>} center 圆点 |
| * @return {Ring} |
| */ |
| _getRing : function (color, scale0, scale1, center) { |
| var radius = this._getRadius(); |
| return new Ring({ |
| zlevel : this._zlevelBase, |
| style : { |
| x : center[0], |
| y : center[1], |
| r : scale0 * radius, |
| r0 : scale1 * radius, |
| color : color, |
| brushType : 'fill' |
| }, |
| hoverable : false, |
| draggable : false |
| }); |
| }, |
| |
| /** |
| * 获取需要绘制的多边形的点集 |
| * @param {Object} serie的指标参数 |
| * @param {number} 缩小的系数 |
| * @param {Array<number>} 中点坐标 |
| * |
| * @return {Array<Array<number>>} 返回绘制的点集 |
| */ |
| _getPointList : function (__ecIndicator, scale, center) { |
| var pointList = []; |
| var len = __ecIndicator.length; |
| var vector; |
| |
| for (var i = 0 ; i < len ; i ++ ) { |
| vector = __ecIndicator[i].vector; |
| |
| pointList.push(this._mapVector(vector, center, scale)); |
| } |
| return pointList; |
| }, |
| |
| /** |
| * 获取绘制的图形 |
| * @param {Array<Array<number>>} 绘制的点集 |
| * @param {string} 绘制方式 stroke | fill | both 描边 | 填充 | 描边 + 填充 |
| * @param {string} 颜色 |
| * @param {string} 描边颜色 |
| * @param {number} 线条宽度 |
| * @return {Object} 绘制的图形对象 |
| */ |
| _getShape : function ( |
| pointList, brushType, color, strokeColor, lineWidth |
| ) { |
| return new PolygonShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| pointList : pointList, |
| brushType : brushType, |
| color : color, |
| strokeColor : strokeColor, |
| lineWidth : lineWidth |
| }, |
| hoverable : false, |
| draggable : false |
| }); |
| }, |
| |
| /** |
| * 绘制填充区域 |
| */ |
| _addSplitArea : function ( |
| __ecIndicator, splitArea, scale, scale1, center, colorInd |
| ) { |
| var indLen = __ecIndicator.length; |
| var color; |
| var colorArr = splitArea.areaStyle.color; |
| var colorLen; |
| |
| var vector; |
| var vector1; |
| var pointList = []; |
| var indLen = __ecIndicator.length; |
| var shape; |
| |
| var type = this.deepQuery(this._queryTarget, 'type'); |
| |
| if (typeof colorArr == 'string') { |
| colorArr = [colorArr]; |
| } |
| colorLen = colorArr.length; |
| color = colorArr[ colorInd % colorLen]; |
| |
| if (type == 'polygon') { |
| for (var i = 0; i < indLen ; i ++) { |
| pointList = []; |
| vector = __ecIndicator[i].vector; |
| vector1 = __ecIndicator[(i + 1) % indLen].vector; |
| |
| pointList.push(this._mapVector(vector, center, scale)); |
| pointList.push(this._mapVector(vector, center, scale1)); |
| pointList.push(this._mapVector(vector1, center, scale1)); |
| pointList.push(this._mapVector(vector1, center, scale)); |
| |
| shape = this._getShape( |
| pointList, 'fill', color, '', 1 |
| ); |
| this.shapeList.push(shape); |
| } |
| } else if (type == 'circle') { |
| shape = this._getRing(color, scale, scale1, center); |
| this.shapeList.push(shape); |
| } |
| }, |
| |
| /** |
| * 转换坐标 |
| * |
| * @param {Array<number>} 原始坐标 |
| * @param {Array<number>} 中点坐标 |
| * @param {number} 缩小的倍数 |
| * |
| * @return {Array<number>} 转换后的坐标 |
| */ |
| _mapVector : function (vector, center, scale) { |
| return [ |
| vector[0] * scale + center[0], |
| vector[1] * scale + center[1] |
| ]; |
| }, |
| |
| /** |
| * 获取中心点位置 暴露给外部图形类使用 |
| * @param {number} polar的index |
| */ |
| getCenter : function (index) { |
| var index = index || 0; |
| return this.parseCenter(this.zr, this.polar[index].center); |
| }, |
| |
| /** |
| * 绘制从中点出发的线 |
| * |
| * @param {Array<Object>} 指标对象 |
| * @param {Array<number>} 中点坐标 |
| * @param {string} 线条颜色 |
| * @param {number} 线条宽度 |
| * @param {string} 线条绘制类型 |
| * solid | dotted | dashed 实线 | 点线 | 虚线 |
| */ |
| _addLine : function ( |
| __ecIndicator, center, axisLine |
| ) { |
| var indLen = __ecIndicator.length; |
| var line; |
| var vector; |
| var lineStyle = axisLine.lineStyle; |
| var strokeColor = lineStyle.color; |
| var lineWidth = lineStyle.width; |
| var lineType = lineStyle.type; |
| |
| for (var i = 0; i < indLen ; i ++ ) { |
| vector = __ecIndicator[i].vector; |
| line = this._getLine( |
| center[0], center[1], |
| vector[0] + center[0], |
| vector[1] + center[1], |
| strokeColor, lineWidth, lineType |
| ); |
| this.shapeList.push(line); |
| } |
| }, |
| |
| /** |
| * 获取线条对象 |
| * @param {number} 出发点横坐标 |
| * @param {number} 出发点纵坐标 |
| * @param {number} 终点横坐标 |
| * @param {number} 终点纵坐标 |
| * @param {string} 线条颜色 |
| * @param {number} 线条宽度 |
| * @param {string} 线条类型 |
| * |
| * @return {Object} 线条对象 |
| */ |
| _getLine : function ( |
| xStart, yStart, xEnd, yEnd, strokeColor, lineWidth, lineType |
| ) { |
| return new LineShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| xStart : xStart, |
| yStart : yStart, |
| xEnd : xEnd, |
| yEnd : yEnd, |
| strokeColor : strokeColor, |
| lineWidth : lineWidth, |
| lineType : lineType |
| }, |
| hoverable : false |
| }); |
| }, |
| |
| /** |
| * 调整指标的值,当indicator中存在max时设置为固定值 |
| * @param {number} polar的index |
| */ |
| _adjustIndicatorValue : function (index) { |
| var item = this.polar[index]; |
| var indicator = this.deepQuery(this._queryTarget, 'indicator'); |
| var len = indicator.length; |
| var __ecIndicator = item.__ecIndicator; |
| var value; |
| var max; |
| var min; |
| var data = this._getSeriesData(index); |
| var splitNumber = item.splitNumber; |
| |
| var boundaryGap = this.deepQuery(this._queryTarget, 'boundaryGap'); |
| var precision = this.deepQuery(this._queryTarget, 'precision'); |
| var power = this.deepQuery(this._queryTarget, 'power'); |
| var scale = this.deepQuery(this._queryTarget, 'scale'); |
| |
| for (var i = 0; i < len ; i ++ ) { |
| if (typeof indicator[i].max == 'number') { |
| max = indicator[i].max; |
| min = indicator[i].min || 0; |
| value = { |
| max : max, |
| min : min |
| }; |
| } |
| else { |
| value = this._findValue( |
| data, i, splitNumber, |
| boundaryGap, precision, power, scale |
| ); |
| } |
| |
| __ecIndicator[i].value = value; |
| } |
| }, |
| |
| /** |
| * 将series中的数据拿出来,如果没有polarIndex属性,默认为零 |
| * @param {number} polar 的index |
| * @param {Array<Object>} 需要处理的数据 |
| */ |
| _getSeriesData : function (index) { |
| var data = []; |
| var serie; |
| var serieData; |
| var legend = this.component.legend; |
| var polarIndex; |
| |
| for (var i = 0; i < this.series.length; i ++) { |
| serie = this.series[i]; |
| if (serie.type != ecConfig.CHART_TYPE_RADAR) { |
| continue; |
| } |
| serieData = serie.data || []; |
| for (var j = 0; j < serieData.length; j ++) { |
| polarIndex = this.deepQuery( |
| [serieData[j], serie, this.option], 'polarIndex' |
| ) || 0; |
| if (polarIndex == index |
| && (!legend || legend.isSelected(serieData[j].name)) |
| ) { |
| data.push(serieData[j]); |
| } |
| } |
| } |
| return data; |
| }, |
| |
| /** |
| * 查找指标合适的值 |
| * |
| * 如果只有一组数据以数据中的最大值作为最大值 0为最小值 |
| * 如果是多组,使用同一维度的进行比较 选出最大值最小值 |
| * 对它们进行处理 |
| * @param {Object} serie 的 data |
| * @param {number} 指标的序号 |
| * @param {boolean} boundaryGap 两端留白 |
| * @param {number} precision 小数精度 |
| * @param {number} power 整数精度 |
| * @return {Object} 指标的最大值最小值 |
| */ |
| _findValue : function ( |
| data, index, splitNumber, boundaryGap, precision, power, scale |
| ) { |
| var max; |
| var min; |
| var value; |
| var delta; |
| var str; |
| var len = 0; |
| var max0; |
| var min0; |
| var one; |
| |
| if (!data || data.length === 0) { |
| return; |
| } |
| |
| function _compare(item) { |
| (item > max || max === undefined) && (max = item); |
| (item < min || min === undefined) && (min = item); |
| } |
| |
| if (data.length == 1) { |
| min = 0; |
| } |
| if (data.length != 1) { |
| for (var i = 0; i < data.length; i ++) { |
| value = typeof data[i].value[index].value != 'undefined' |
| ? data[i].value[index].value : data[i].value[index]; |
| _compare(value); |
| } |
| } |
| else { |
| one = data[0]; |
| for (var i = 0; i < one.value.length; i ++) { |
| _compare( |
| typeof one.value[i].value != 'undefined' |
| ? one.value[i].value : one.value[i] |
| ); |
| } |
| } |
| |
| if (data.length != 1) { |
| if (scale) { |
| delta = this._getDelta( |
| max, min, splitNumber, precision, power |
| ); |
| |
| if (delta >= 1) { |
| min = Math.floor(min / delta) * delta - delta; |
| } |
| else if (delta === 0) { |
| if (max > 0) { |
| min0 = 0; |
| max0 = 2 * max; |
| } |
| else if (max === 0) { |
| min0 = 0; |
| max0 = 100; |
| } |
| else { |
| max0 = 0; |
| min0 = 2 * min; |
| } |
| |
| return { |
| max : max0, |
| min : min0 |
| }; |
| } |
| else { |
| str = (delta + '').split('.')[1]; |
| len = str.length; |
| min = Math.floor( |
| min * Math.pow(10, len)) / Math.pow(10, len |
| ) - delta; |
| } |
| |
| if (Math.abs(min) <= delta) { |
| min = 0; |
| } |
| |
| max = min + Math.floor(delta * Math.pow(10, len) |
| * (splitNumber + 1)) / Math.pow(10, len) ; |
| } |
| else { |
| min = min > 0 ? 0 : min; |
| } |
| } |
| |
| if (boundaryGap) { |
| max = max > 0 ? max * 1.2 : max * 0.8; |
| min = min > 0 ? min * 0.8 : min * 1.2; |
| } |
| |
| return { |
| max : max, |
| min : min |
| }; |
| }, |
| |
| /** |
| * 获取最大值与最小值中间比较合适的差值 |
| * @param {number} max; |
| * @param {number} min |
| * @param {number} precision 小数精度 |
| * @param {number} power 整数精度 |
| * @return {number} delta |
| */ |
| _getDelta : function (max , min, splitNumber, precision, power) { |
| var delta = (max - min) / splitNumber; |
| var str; |
| var n; |
| |
| if (delta > 1) { |
| if (!power) { |
| str = (delta + '').split('.')[0]; |
| n = str.length; |
| if (str.charAt(0) >= 5) { |
| return Math.pow(10, n); |
| } |
| else { |
| return (str.charAt(0) - 0 + 1 ) * Math.pow(10, n - 1); |
| } |
| } |
| else { |
| delta = Math.ceil(delta); |
| if (delta % power > 0) { |
| return (Math.ceil(delta / power) + 1) * power; |
| } |
| else { |
| return delta; |
| } |
| } |
| } |
| else if (delta == 1) { |
| return 1; |
| } |
| else if (delta === 0) { |
| return 0; |
| } |
| else { |
| if (!precision) { |
| str = (delta + '').split('.')[1]; |
| n = 0; |
| while (str[n] == '0') { |
| n ++ ; |
| } |
| |
| if (str[n] >= 5) { |
| return '0.' + str.substring(0, n + 1) - 0 |
| + 1 / Math.pow(10, n); |
| } |
| else { |
| return '0.' + str.substring(0, n + 1) - 0 |
| + 1 / Math.pow(10, n + 1); |
| } |
| } |
| else { |
| return Math.ceil(delta * Math.pow(10, precision)) |
| / Math.pow(10, precision); |
| } |
| } |
| }, |
| |
| /** |
| * 获取每个指标上某个value对应的坐标 |
| * @param {number} polarIndex |
| * @param {number} indicatorIndex |
| * @param {number} value |
| * @return {Array<number>} 对应坐标 |
| */ |
| getVector : function (polarIndex, indicatorIndex, value) { |
| polarIndex = polarIndex || 0; |
| indicatorIndex = indicatorIndex || 0; |
| var __ecIndicator = this.polar[polarIndex].__ecIndicator; |
| |
| if (indicatorIndex >= __ecIndicator.length) { |
| return ; |
| } |
| |
| var indicator = this.polar[polarIndex].__ecIndicator[indicatorIndex]; |
| var center = this.getCenter(polarIndex); |
| var vector = indicator.vector; |
| var max = indicator.value.max; |
| var min = indicator.value.min; |
| var alpha; |
| |
| if (typeof value == 'undefined') { |
| return center; |
| } |
| |
| switch (value) { |
| case 'min' : |
| value = min; |
| break; |
| case 'max' : |
| value = max; |
| break; |
| case 'center' : |
| value = (max + min) / 2; |
| break; |
| } |
| |
| if (max != min) { |
| alpha = (value - min) / (max - min); |
| } |
| else { |
| alpha = 0.5; |
| } |
| |
| return this._mapVector(vector, center, alpha); |
| }, |
| |
| /** |
| * 判断一个点是否在网内 |
| * @param {Array<number>} 坐标 |
| * @return {number} 返回polarindex 返回-1表示不在任何polar |
| */ |
| isInside : function (vector) { |
| var polar = this.getNearestIndex(vector); |
| |
| if (polar) { |
| return polar.polarIndex; |
| } |
| return -1; |
| }, |
| |
| /** |
| * 如果一个点在网内,返回离它最近的数据轴的index |
| * @param {Array<number>} 坐标 |
| * @return {Object} | false |
| * polarIndex |
| * valueIndex |
| */ |
| getNearestIndex : function (vector) { |
| var item; |
| var center; |
| var radius; |
| var polarVector; |
| var startAngle; |
| var indicator; |
| var len; |
| var angle; |
| var finalAngle; |
| for (var i = 0 ; i < this.polar.length; i ++) { |
| item = this.polar[i]; |
| center = this.getCenter(i); |
| if (vector[0] == center[0] && vector[1] == center[1]) { |
| return { |
| polarIndex : i, |
| valueIndex : 0 |
| }; |
| } |
| radius = this._getRadius(); |
| startAngle = item.startAngle; |
| indicator = item.indicator; |
| len = indicator.length; |
| angle = 2 * Math.PI / len; |
| // 注意y轴的翻转 |
| polarVector = ecCoordinates.cartesian2polar( |
| vector[0] - center[0], center[1] - vector[1] |
| ); |
| if (vector[0] - center[0] < 0) { |
| polarVector[1] += Math.PI; |
| } |
| if (polarVector[1] < 0) { |
| polarVector[1] += 2 * Math.PI; |
| } |
| |
| |
| // 减去startAngle的偏移量 再加2PI变成正数 |
| finalAngle = polarVector[1] - |
| startAngle / 180 * Math.PI + Math.PI * 2; |
| |
| if (Math.abs(Math.cos(finalAngle % (angle / 2))) * radius |
| > polarVector[0]) |
| { |
| return { |
| polarIndex : i, |
| valueIndex : Math.floor( |
| (finalAngle + angle / 2 ) / angle |
| ) % len |
| }; |
| } |
| } |
| }, |
| |
| /** |
| * 获取指标信息 |
| * @param {number} polarIndex |
| * @return {Array<Object>} indicator |
| */ |
| getIndicator : function (index) { |
| var index = index || 0; |
| return this.polar[index].indicator; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.polar = this.option.polar; |
| this.series = this.option.series; |
| } |
| this.clear(); |
| this._buildShape(); |
| } |
| }; |
| |
| zrUtil.inherits(Polar, Base); |
| |
| require('../component').define('polar', Polar); |
| |
| return Polar; |
| }); |
| /** |
| * echarts图表类:雷达图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Neil (杨骥, yangji01@baidu.com) |
| * |
| */ |
| |
| define('echarts/chart/radar',['require','../component/base','./base','zrender/shape/Polygon','../component/polar','../config','../util/ecData','zrender/tool/util','zrender/tool/color','../util/accMath','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var PolygonShape = require('zrender/shape/Polygon'); |
| // 组件依赖 |
| require('../component/polar'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Radar(ecTheme, messageCenter, zr, option, myChart) { |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this.refresh(option); |
| } |
| |
| Radar.prototype = { |
| type : ecConfig.CHART_TYPE_RADAR, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| this.selectedMap = {}; |
| this._symbol = this.option.symbolList; |
| this._queryTarget; |
| this._dropBoxList = []; |
| this._radarDataCounter = 0; |
| |
| var series = this.series; |
| var legend = this.component.legend; |
| var serieName; |
| for (var i = 0, l = series.length; i < l ; i ++) { |
| if (series[i].type == ecConfig.CHART_TYPE_RADAR) { |
| this.serie = this.reformOption(series[i]); |
| serieName = this.serie.name || ''; |
| // 系列图例开关 |
| this.selectedMap[serieName] = |
| legend ? legend.isSelected(serieName) : true; |
| |
| if (this.selectedMap[serieName]) { |
| this._queryTarget = [this.serie, this.option]; |
| |
| // 添加可拖拽提示框,多系列共用一个极坐标,第一个优先 |
| if (this.deepQuery(this._queryTarget, 'calculable')) { |
| this._addDropBox(i); |
| } |
| this._buildSingleRadar(i); |
| this.buildMark(i); |
| } |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建数据图形 |
| * @param {number} 序列的index |
| */ |
| _buildSingleRadar : function (index) { |
| var legend = this.component.legend; |
| var iconShape; |
| var data = this.serie.data; |
| var defaultColor; |
| var name; |
| var pointList; |
| var calculable = this.deepQuery(this._queryTarget, 'calculable'); |
| |
| for (var i = 0; i < data.length; i ++) { |
| name = data[i].name || ''; |
| |
| // 图例开关 |
| this.selectedMap[name] = legend |
| ? legend.isSelected(name) |
| : true; |
| if (!this.selectedMap[name]) { |
| continue; |
| } |
| |
| // 默认颜色策略 |
| if (legend) { |
| // 有图例则从图例中获取颜色定义 |
| defaultColor = legend.getColor(name); |
| iconShape = legend.getItemShape(name); |
| if (iconShape) { |
| // 回调legend,换一个更形象的icon |
| iconShape.style.brushType = this.deepQuery( |
| [data[i], this.serie], 'itemStyle.normal.areaStyle' |
| ) ? 'both' : 'stroke'; |
| legend.setItemShape(name, iconShape); |
| } |
| } |
| else { |
| // 全局颜色定义 |
| defaultColor = this.zr.getColor(i); |
| } |
| |
| pointList = this._getPointList(this.serie.polarIndex, data[i]); |
| // 添加拐点形状 |
| this._addSymbol(pointList, defaultColor, i, index, this.serie.polarIndex); |
| // 添加数据形状 |
| this._addDataShape( |
| pointList, defaultColor, data[i], |
| index, i, calculable |
| ); |
| this._radarDataCounter++; |
| } |
| }, |
| |
| /** |
| * 获取数据的点集 |
| * @param {number} polarIndex |
| * @param {Array<Object>} 处理的数据 |
| * @return {Array<Array<number>>} 点集 |
| */ |
| _getPointList : function (polarIndex, dataArr) { |
| var pointList = []; |
| var vector; |
| var polar = this.component.polar; |
| |
| for (var i = 0, l = dataArr.value.length; i < l; i++) { |
| vector = polar.getVector( |
| polarIndex, |
| i, |
| typeof dataArr.value[i].value != 'undefined' |
| ? dataArr.value[i].value : dataArr.value[i] |
| ); |
| if (vector) { |
| pointList.push(vector); |
| } |
| } |
| return pointList; |
| }, |
| |
| /** |
| * 添加拐点 |
| * @param {Array<Array<number>>} pointList 点集 |
| * @param {string} defaultColor 默认填充颜色 |
| * @param {object} data 数据 |
| * @param {number} serieIndex |
| */ |
| _addSymbol : function (pointList, defaultColor, dataIndex, seriesIndex, polarIndex) { |
| var series = this.series; |
| var itemShape; |
| var polar = this.component.polar; |
| |
| for (var i = 0, l = pointList.length; i < l; i++) { |
| itemShape = this.getSymbolShape( |
| this.deepMerge( |
| [series[seriesIndex].data[dataIndex], series[seriesIndex]] |
| ), |
| seriesIndex, |
| series[seriesIndex].data[dataIndex].value[i], i, |
| polar.getIndicatorText(polarIndex, i), |
| pointList[i][0], // x |
| pointList[i][1], // y |
| this._symbol[this._radarDataCounter % this._symbol.length], |
| defaultColor, |
| '#fff', |
| 'vertical' |
| ); |
| itemShape.zlevel = this._zlevelBase + 1; |
| ecData.set(itemShape, 'data', series[seriesIndex].data[dataIndex]); |
| ecData.set(itemShape, 'value', series[seriesIndex].data[dataIndex].value); |
| ecData.set(itemShape, 'dataIndex', dataIndex); |
| ecData.set(itemShape, 'special', i); |
| this.shapeList.push(itemShape); |
| } |
| }, |
| |
| /** |
| * 添加数据图形 |
| * @param {Array<Array<number>>} pointList 点集 |
| * @param {string} defaultColor 默认填充颜色 |
| * @param {object} data 数据 |
| * @param {number} serieIndex |
| * @param {number} dataIndex |
| * @param {boolean} calcalable |
| */ |
| _addDataShape : function ( |
| pointList, defaultColor, data, |
| seriesIndex, dataIndex, calculable |
| ) { |
| var series = this.series; |
| // 多级控制 |
| var queryTarget = [data, this.serie]; |
| var nColor = this.getItemStyleColor( |
| this.deepQuery( |
| queryTarget, 'itemStyle.normal.color' |
| ), |
| seriesIndex, |
| dataIndex, |
| data |
| ); |
| var nLineWidth = this.deepQuery( |
| queryTarget, 'itemStyle.normal.lineStyle.width' |
| ); |
| var nLineType = this.deepQuery( |
| queryTarget, 'itemStyle.normal.lineStyle.type' |
| ); |
| var nAreaColor = this.deepQuery( |
| queryTarget, 'itemStyle.normal.areaStyle.color' |
| ); |
| var nIsAreaFill = this.deepQuery( |
| queryTarget, 'itemStyle.normal.areaStyle' |
| ); |
| var shape = { |
| zlevel : this._zlevelBase, |
| style : { |
| pointList : pointList, |
| brushType : nIsAreaFill ? 'both' : 'stroke', |
| color : nAreaColor |
| || nColor |
| || zrColor.alpha(defaultColor,0.5), |
| strokeColor : nColor || defaultColor, |
| lineWidth : nLineWidth, |
| lineType : nLineType |
| }, |
| highlightStyle : { |
| brushType : this.deepQuery( |
| queryTarget, |
| 'itemStyle.emphasis.areaStyle' |
| ) || nIsAreaFill |
| ? 'both' : 'stroke', |
| color : this.deepQuery( |
| queryTarget, |
| 'itemStyle.emphasis.areaStyle.color' |
| ) |
| || nAreaColor |
| || nColor |
| || zrColor.alpha(defaultColor,0.5), |
| strokeColor : this.getItemStyleColor( |
| this.deepQuery( |
| queryTarget, 'itemStyle.emphasis.color' |
| ), |
| seriesIndex, |
| dataIndex, |
| data |
| ) |
| || nColor || defaultColor, |
| lineWidth : this.deepQuery( |
| queryTarget, |
| 'itemStyle.emphasis.lineStyle.width' |
| ) || nLineWidth, |
| lineType : this.deepQuery( |
| queryTarget, |
| 'itemStyle.emphasis.lineStyle.type' |
| ) || nLineType |
| } |
| }; |
| ecData.pack( |
| shape, |
| series[seriesIndex], // 系列 |
| seriesIndex, // 系列索引 |
| data, // 数据 |
| dataIndex, // 数据索引 |
| data.name, // 数据名称 |
| // 附加指标信息 |
| this.component.polar.getIndicator(series[seriesIndex].polarIndex) |
| ); |
| if (calculable) { |
| shape.draggable = true; |
| this.setCalculable(shape); |
| } |
| |
| shape = new PolygonShape(shape); |
| this.shapeList.push(shape); |
| }, |
| |
| /** |
| * 增加外围接受框 |
| * @param {number} serie的序列 |
| */ |
| _addDropBox : function (index) { |
| var series = this.series; |
| var polarIndex = this.deepQuery( |
| this._queryTarget, 'polarIndex' |
| ); |
| if (!this._dropBoxList[polarIndex]) { |
| var shape = this.component.polar.getDropBox(polarIndex); |
| shape.zlevel = this._zlevelBase; |
| this.setCalculable(shape); |
| ecData.pack(shape, series, index, undefined, -1); |
| this.shapeList.push(shape); |
| this._dropBoxList[polarIndex] = true; |
| } |
| }, |
| |
| /** |
| * 数据项被拖拽出去,重载基类方法 |
| */ |
| ondragend : function (param, status) { |
| var series = this.series; |
| if (!this.isDragend || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| |
| var target = param.target; // 被拖拽图形元素 |
| |
| var seriesIndex = ecData.get(target, 'seriesIndex'); |
| var dataIndex = ecData.get(target, 'dataIndex'); |
| |
| // 被拖拽的图形是饼图sector,删除被拖拽走的数据 |
| this.component.legend && this.component.legend.del( |
| series[seriesIndex].data[dataIndex].name |
| ); |
| |
| series[seriesIndex].data.splice(dataIndex, 1); |
| |
| // 别status = {}赋值啊!! |
| status.dragOut = true; |
| status.needRefresh = true; |
| |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| |
| return; |
| }, |
| |
| /** |
| * 数据项被拖拽进来, 重载基类方法 |
| */ |
| ondrop : function (param, status) { |
| var series = this.series; |
| if (!this.isDrop || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| |
| var target = param.target; // 拖拽安放目标 |
| var dragged = param.dragged; // 当前被拖拽的图形对象 |
| |
| var seriesIndex = ecData.get(target, 'seriesIndex'); |
| var dataIndex = ecData.get(target, 'dataIndex'); |
| |
| var data; |
| var legend = this.component.legend; |
| var value; |
| |
| if (dataIndex == -1) { |
| data = { |
| value : ecData.get(dragged, 'value'), |
| name : ecData.get(dragged, 'name') |
| }; |
| |
| series[seriesIndex].data.push(data); |
| |
| legend && legend.add( |
| data.name, |
| dragged.style.color || dragged.style.strokeColor |
| ); |
| } |
| else { |
| // 数据被拖拽到某个数据项上,数据修改 |
| var accMath = require('../util/accMath'); |
| data = series[seriesIndex].data[dataIndex]; |
| legend && legend.del(data.name); |
| data.name += this.option.nameConnector |
| + ecData.get(dragged, 'name'); |
| value = ecData.get(dragged, 'value'); |
| for (var i = 0 ; i < value.length; i ++) { |
| data.value[i] = accMath.accAdd(data.value[i], value[i]); |
| } |
| |
| legend && legend.add( |
| data.name, |
| dragged.style.color || dragged.style.strokeColor |
| ); |
| } |
| |
| // 别status = {}赋值啊!! |
| status.dragIn = status.dragIn || true; |
| |
| // 处理完拖拽事件后复位 |
| this.isDrop = false; |
| |
| return; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| } |
| }; |
| |
| zrUtil.inherits(Radar, ChartBase); |
| zrUtil.inherits(Radar, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('radar', Radar); |
| |
| return Radar; |
| }); |
| /** |
| * zrender |
| * |
| * @author pissang (https://github.com/pissang) |
| * |
| * shape类:chord |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'chord', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| center : {array}, |
| source0 : {number}, |
| source1 : {number}, |
| target0 : {number}, |
| target1 : {number}, |
| r : {number}, |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| */ |
| define('echarts/util/shape/Chord',['require','zrender/shape/Base','zrender/tool/util'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var zrUtil = require('zrender/tool/util'); |
| var _ctx = zrUtil.getContext(); |
| |
| function ChordShape(options) { |
| Base.call(this, options); |
| } |
| |
| ChordShape.prototype = { |
| type : 'chord', |
| |
| // center, source0, source1, target0, target1, r |
| buildPath : function (ctx, style) { |
| var PI2 = Math.PI * 2; |
| var cx = style.center[0]; |
| var cy = style.center[1]; |
| var r = style.r; |
| var s0 = style.source0 / 180 * Math.PI; |
| var s1 = style.source1 / 180 * Math.PI; |
| var t0 = style.target0 / 180 * Math.PI; |
| var t1 = style.target1 / 180 * Math.PI; |
| var sx0 = cx + Math.cos(PI2 - s0) * r; |
| var sy0 = cy - Math.sin(PI2 - s0) * r; |
| var sx1 = cx + Math.cos(PI2 - s1) * r; |
| var sy1 = cy - Math.sin(PI2 - s1) * r; |
| var tx0 = cx + Math.cos(PI2 - t0) * r; |
| var ty0 = cy - Math.sin(PI2 - t0) * r; |
| var tx1 = cx + Math.cos(PI2 - t1) * r; |
| var ty1 = cy - Math.sin(PI2 - t1) * r; |
| |
| ctx.moveTo(sx0, sy0); |
| ctx.arc(cx, cy, style.r, s0, s1, false); |
| ctx.bezierCurveTo( |
| (cx - sx1) * 0.70 + sx1, |
| (cy - sy1) * 0.70 + sy1, |
| (cx - tx0) * 0.70 + tx0, |
| (cy - ty0) * 0.70 + ty0, |
| tx0, ty0 |
| ); |
| // Chord to self |
| if (style.source0 === style.target0 && |
| style.source1 === style.target1) { |
| return; |
| } |
| ctx.arc(cx, cy, style.r, t0, t1, false); |
| ctx.bezierCurveTo( |
| (cx - tx1) * 0.70 + tx1, |
| (cy - ty1) * 0.70 + ty1, |
| (cx - sx0) * 0.70 + sx0, |
| (cy - sy0) * 0.70 + sy0, |
| sx0, sy0 |
| ); |
| }, |
| |
| getRect : function (){ |
| return { |
| x : 0, |
| y : 0, |
| width : 0, |
| height : 0 |
| }; |
| }, |
| |
| isCover : function (x, y) { |
| if (!_ctx.isPointInPath) { // In ie |
| return false; |
| } |
| var originPos = this.getTansform(x, y); |
| x = originPos[0]; |
| y = originPos[1]; |
| |
| _ctx.beginPath(); |
| ChordShape.prototype.buildPath.call(null, _ctx, this.style); |
| _ctx.closePath(); |
| |
| return _ctx.isPointInPath(x, y); |
| } |
| }; |
| |
| zrUtil.inherits(ChordShape, Base); |
| |
| return ChordShape; |
| }); |
| define('echarts/util/kwargs',[],function (){ |
| function kwargs(func, defaults) { |
| /*jshint maxlen : 200*/ |
| var removeComments = new RegExp('(\\/\\*[\\w\\\'\\,\\(\\)\\s\\r\\n\\*]*\\*\\/)|(\\/\\/[\\w\\s\\\'][^\\n\\r]*$)|(<![\\-\\-\\s\\w\\>\\/]*>)', 'gim'); |
| var removeWhitespc = new RegExp('\\s+', 'gim'); |
| var matchSignature = new RegExp('function.*?\\((.*?)\\)', 'i'); |
| // get the argument names from function source |
| var names = func.toString() |
| .replace(removeComments, '') |
| .replace(removeWhitespc, '') |
| .match(matchSignature)[1] |
| .split(','); |
| |
| // Check the existance of default, if not create an object |
| if(defaults !== Object(defaults)){ |
| defaults = {}; |
| } |
| |
| return function () { |
| var args = Array.prototype.slice.call(arguments); |
| var kwargs = args[args.length - 1]; |
| |
| // Check the existance of the kwargs |
| if (kwargs && kwargs.constructor === Object) { |
| args.pop(); |
| } |
| else{ |
| kwargs = {}; |
| } |
| |
| // Fill the arguments and apply them |
| for (var i = 0; i < names.length; i++) { |
| var name = names[i]; |
| if (name in kwargs) { |
| args[i] = kwargs[name]; |
| } |
| else if(name in defaults && args[i] == null){ |
| args[i] = defaults[name]; |
| } |
| } |
| |
| return func.apply(this, args); |
| }; |
| } |
| // As function prototype |
| // Function.prototype.kwargs = kwargs; |
| return kwargs; |
| }); |
| /** |
| * Numpy like n-dimensional array proccessing class |
| * http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html |
| * |
| * @author pissang (https://github.com/pissang/) |
| */ |
| define('echarts/util/ndarray',['require','./kwargs'],function (require) { |
| |
| |
| |
| var kwargs = require('./kwargs'); |
| |
| var ArraySlice = Array.prototype.slice; |
| |
| // Polyfill of Typed Array |
| this.Int32Array = window.Int32Array || Array; |
| this.Int16Array = window.Int16Array || Array; |
| this.Int8Array = window.Int8Array || Array; |
| this.Uint32Array = window.Uint32Array || Array; |
| this.Uint16Array = window.Uint16Array || Array; |
| this.Uint8Array = window.Uint8Array || Array; |
| this.Float32Array = window.Float32Array || Array; |
| this.Float64Array = window.Float64Array || Array; |
| |
| // Map of numpy dtype and typed array |
| // http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html#arrays-dtypes |
| // http://www.khronos.org/registry/typedarray/specs/latest/ |
| var ArrayConstructor = { |
| 'int32' : this.Int32Array, |
| 'int16' : this.Int16Array, |
| 'int8' : this.Int8Array, |
| 'uint32' : this.Uint32Array, |
| 'uint16' : this.Uint16Array, |
| 'uint8' : this.Uint8Array, |
| // 'uint8c' is not existed in numpy |
| 'uint8c' : this.Uint8ClampedArray, |
| 'float32' : this.Float32Array, |
| 'float64' : this.Float64Array, |
| 'number' : Array |
| }; |
| |
| var dTypeStrideMap = { |
| 'int32' : 4, |
| 'int16' : 2, |
| 'int8' : 1, |
| 'uint32' : 4, |
| 'uint16' : 2, |
| 'uint8' : 1, |
| 'uint8c' : 1, |
| 'float32' : 4, |
| 'float64' : 8, |
| // Consider array stride is 1 |
| 'number' : 1 |
| }; |
| |
| var E_ADD = 0; |
| var E_SUB = 1; |
| var E_MUL = 2; |
| var E_DIV = 3; |
| var E_MOD = 4; |
| var E_AND = 5; |
| var E_OR = 6; |
| var E_XOR = 7; |
| var E_EQL = 8; |
| |
| function guessDataType(arr) { |
| if (typeof(arr) === 'undefined') { |
| return 'number'; |
| } |
| switch(Object.prototype.toString.call(arr)) { |
| case '[object Int32Array]': |
| return 'int32'; |
| case '[object Int16Array]': |
| return 'int16'; |
| case '[object Int8Array]': |
| return 'int8'; |
| case '[object Uint32Array]': |
| return 'uint32'; |
| case '[object Uint16Array]': |
| return 'uint16'; |
| case '[object Uint8Array]': |
| return 'uint8'; |
| case '[object Uint8ClampedArray]': |
| return 'uint8c'; |
| case '[object Float32Array]': |
| return 'float32'; |
| case '[object Float64Array]': |
| return 'float64'; |
| default: |
| return 'number'; |
| } |
| } |
| |
| /** |
| * NDArray |
| * @param {Array|NDArray} array |
| * @param {String} dtype |
| */ |
| var NDArray = function (array) { |
| // Last argument describe the data type of ndarray |
| var dtype = arguments[arguments.length-1]; |
| if (typeof(dtype) == 'string') { |
| this._dtype = dtype; |
| } else { |
| // Normal array |
| this._dtype = guessDataType(array); |
| } |
| |
| if (array && typeof(array) !== 'string') { |
| if (array instanceof NDArray) { |
| array._dtype = this._dtype; |
| return array; |
| } else if (typeof(array.length) !== 'undefined') { |
| // Init from array |
| this.initFromArray(array); |
| } else if(typeof(array) === 'number') { |
| // Init from shape |
| this.initFromShape.apply(this, arguments); |
| } |
| } else { |
| /** |
| * _array |
| * Initialized with an empty array |
| * Data is continuous one-dimensional array, row-major |
| * A [2, 2] dim empty array is stored like |
| * [0,0, 0,0] |
| * TODO : Consider column majors ? |
| * @type {ArrayConstructor} |
| */ |
| this._array = new ArrayConstructor[this._dtype](); |
| /** |
| * _shape |
| * a tuple array describe the dimension and size of each dimension |
| * [10, 10] means a 10x10 array |
| * @type {Array} |
| */ |
| this._shape = [0]; |
| /** |
| * _size |
| * size of the storage array length |
| * @type {Number} |
| */ |
| this._size = 0; |
| } |
| }; |
| |
| NDArray.prototype = { |
| /** |
| * Initialize from a normal js array. |
| * |
| * @param {Array} input |
| * @return {NDArray} this |
| */ |
| initFromArray : function (input) { |
| var dim = getDimension(input); |
| var cursor = 0; |
| function flatten(axis, _out, _in) { |
| var len = _in.length; |
| for (var i = 0; i < len; i++) { |
| if (axis < dim-1) { |
| flatten(axis+1, _out, _in[i]); |
| } else { |
| _out[cursor++] = _in[i]; |
| } |
| } |
| } |
| var shape = getShape(input); |
| var size = getSize(shape); |
| this._array = new ArrayConstructor[this._dtype](size); |
| |
| flatten(0, this._array, input); |
| this._shape = shape; |
| this._size = size; |
| |
| return this; |
| }, |
| |
| /** |
| * Initialize from the given shape description. |
| * @param {Array} shape |
| * @return {NDArray} this |
| */ |
| initFromShape : function (shape) { |
| if (typeof(shape) == 'number') { |
| shape = Array.prototype.slice.call(arguments); |
| } |
| if(shape) { |
| var size = getSize(shape); |
| if (this._dtype === 'number') { |
| this._array = []; |
| var data = this._array; |
| for (var i = 0; i < size; i++) { |
| data[i] = 0; |
| } |
| } else { |
| this._array = new ArrayConstructor[this._dtype](size); |
| } |
| } |
| this._shape = shape; |
| this._size = getSize(shape); |
| |
| return this; |
| }, |
| /** |
| * Fill the array with the given value. |
| * @param {Number} value |
| * @return {NDArray} this |
| */ |
| fill : function (value) { |
| var data = this._array; |
| for (var i = 0; i < data.length; i++) { |
| data[i] = value; |
| } |
| return this; |
| }, |
| |
| /** |
| * Get ndarray shape copy. |
| * @return {Array} |
| */ |
| shape : function () { |
| // Create a copy |
| return this._shape.slice(); |
| }, |
| |
| /** |
| * Get array size |
| * @return {Number} |
| */ |
| size : function () { |
| return this._size; |
| }, |
| |
| /** |
| * Get array data type. |
| * 'int32' |
| * 'int16' |
| * 'int8' |
| * 'uint32' |
| * 'uint16' |
| * 'uint8' |
| * 'float32' |
| * 'float64' |
| * @return {String} |
| */ |
| dtype : function () { |
| return this._dtype; |
| }, |
| |
| /** |
| * Get array dimension. |
| * @return {[type]} [description] |
| */ |
| dimension : function () { |
| return this._shape.length; |
| }, |
| |
| /** |
| * Tuple of bytes to step in each dimension when traversing an array. |
| * @return {Array} |
| */ |
| strides : function () { |
| var strides = calculateDimStrides(this._shape); |
| var dTypeStride = dTypeStrideMap[this._dtype]; |
| for (var i = 0; i < strides.length; i++) { |
| strides[i] *= dTypeStride; |
| } |
| return strides; |
| }, |
| /** |
| * Gives a new shape to an array without changing its data. |
| * @param {Array} shape |
| * @return {NDArray} |
| */ |
| reshape : function (shape) { |
| if (typeof(shape) == 'number') { |
| shape = Array.prototype.slice.call(arguments); |
| } |
| if (this._isShapeValid(shape)) { |
| this._shape = shape; |
| } else { |
| throw new Error('Total size of new array must be unchanged'); |
| } |
| return this; |
| }, |
| |
| _isShapeValid : function (shape) { |
| return getSize(shape) === this._size; |
| }, |
| |
| /** |
| * Change shape and size of array in-place. |
| * @param {Array} shape |
| * @return {NDArray} |
| */ |
| resize : function (shape) { |
| if (typeof(shape) == 'number') { |
| shape = Array.prototype.slice.call(arguments); |
| } |
| |
| var len = getSize(shape); |
| if (len < this._size) { |
| if (this._dtype === 'number') { |
| this._array.length = len; |
| } |
| } else { |
| if (this._dtype === 'number') { |
| for (var i = this._array.length; i < len; i++) { |
| // Fill the rest with zero |
| this._array[i] = 0; |
| } |
| } else { |
| // Reallocate new buffer |
| var newArr = new ArrayConstructor[this._dtype](len); |
| var originArr = this._array; |
| |
| // Copy data |
| for (var i = 0; i < originArr.length; i++) { |
| newArr[i] = originArr[i]; |
| } |
| this._array = newArr; |
| } |
| } |
| this._shape = shape; |
| this._size = len; |
| |
| return this; |
| |
| }, |
| |
| /** |
| * Returns a new array with axes transposed. |
| * @param {Array} [axes] |
| * @param {NDArray} [out] |
| * @return {NDArray} |
| */ |
| transpose : kwargs(function (axes, out) { |
| var originAxes = []; |
| for (var i = 0; i < this._shape.length; i++) { |
| originAxes.push(i); |
| } |
| if (typeof(axes) === 'undefined') { |
| axes = originAxes.slice(); |
| } |
| // Check if any axis is out of bounds |
| for (var i = 0; i < axes.length; i++) { |
| if (axes[i] >= this._shape.length) { |
| throw new Error(axisOutofBoundsErrorMsg(axes[i])); |
| } |
| } |
| // Has no effect on 1-D transpose |
| if (axes.length <= 1) { |
| return this; |
| } |
| |
| var targetAxes = originAxes.slice(); |
| for (var i = 0; i < Math.floor(axes.length / 2); i++) { |
| for (var j = axes.length-1; j >= Math.ceil(axes.length / 2) ; j--) { |
| // Swap axes |
| targetAxes[axes[i]] = axes[j]; |
| targetAxes[axes[j]] = axes[i]; |
| } |
| } |
| |
| return this._transposelike(targetAxes, out); |
| |
| }), |
| |
| /** |
| * Return a new array with axis1 and axis2 interchanged. |
| * @param {Number} axis1 |
| * @param {Number} axis2 |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| swapaxes : kwargs(function (axis1, axis2, out) { |
| return this.transpose([axis1, axis2], out); |
| }), |
| |
| /** |
| * Roll the specified axis backwards, until it lies in a given position. |
| * @param {Number} axis |
| * @param {Number} [start=0] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| rollaxis : kwargs(function (axis, start, out) { |
| if (axis >= this._shape.length) { |
| throw new Error(axisOutofBoundsErrorMsg(axis)); |
| } |
| |
| var axes = []; |
| for (var i = 0; i < this._shape.length; i++) { |
| axes.push(i); |
| } |
| axes.splice(axis, 1); |
| axes.splice(start, 0, axis); |
| |
| return this._transposelike(axes, out); |
| |
| }, { start : 0}), |
| |
| // Base function for transpose-like operations |
| _transposelike : function (axes, out) { |
| var source = this._array; |
| var shape = this._shape.slice(); |
| var strides = calculateDimStrides(this._shape); |
| var dim = shape.length; |
| |
| // Swap |
| var tmpStrides = []; |
| var tmpShape = []; |
| for (var i = 0; i < axes.length; i++) { |
| var axis = axes[i]; |
| // swap to target axis |
| tmpShape[i] = shape[axis]; |
| tmpStrides[i] = strides[axis]; |
| } |
| strides = tmpStrides; |
| shape = tmpShape; |
| |
| this._shape = shape; |
| var transposedStrides = calculateDimStrides(this._shape); |
| |
| if (!out) { |
| out = new NDArray(); |
| out._shape = this._shape.slice(); |
| out._dtype = this._dtype; |
| out._size = this._size; |
| } |
| // FIXME in-place transpose? |
| var transposedData = new ArrayConstructor[this._dtype](this._size); |
| out._array = transposedData; |
| // @param Item offset in current axis offset of the original array |
| // @param Item offset in current axis offset of the transposed array |
| function transpose(axis, offset, transposedOffset) { |
| var size = shape[axis]; |
| // strides in orginal array |
| var stride = strides[axis]; |
| // strides in transposed array |
| var transposedStride = transposedStrides[axis]; |
| |
| if (axis < dim-1) { |
| for (var i = 0; i < size; i++) { |
| transpose( |
| axis+1, |
| offset + stride * i, |
| transposedOffset + transposedStride * i |
| ); |
| } |
| } else { |
| for (var i = 0; i < size; i++) { |
| // offset + stride * i is the index of the original array |
| // transposedOffset + i is the index of the transposed array |
| transposedData[transposedOffset + i] |
| = source[offset + stride * i]; |
| } |
| } |
| } |
| |
| transpose(0, 0, 0); |
| |
| return out; |
| }, |
| |
| /** |
| * Repeat elements of an array along axis |
| * @param {Number} repeats |
| * The number of repetitions for each element. |
| * repeats is broadcasted to fit the shape of the given axis. |
| * @param {Number} [axis] |
| * The axis along which to repeat values. |
| * By default, use the flattened input array, |
| * and return a flat output array. |
| * @param {NDArray} [out] |
| * @return {NDArray} |
| */ |
| repeat : kwargs(function (repeats, axis, out) { |
| var shape; |
| // flattened input array |
| if (typeof(axis) === 'undefined') { |
| shape = [this._size]; |
| axis = 0; |
| } else { |
| shape = this._shape.slice(); |
| } |
| var originShape = shape.slice(); |
| |
| shape[axis] *= repeats; |
| if (!out) { |
| out = new NDArray(this._dtype); |
| out.initFromShape(shape); |
| } else { |
| if (!arrayEqual(shape, out._shape)) { |
| throw new Error(broadcastErrorMsg(shape, out._shape)); |
| } |
| } |
| var data = out._array; |
| |
| var stride = calculateDimStride(originShape, axis); |
| var axisSize = originShape[axis]; |
| var source = this._array; |
| |
| var offsetStride = stride * axisSize; |
| |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var k = 0; k < stride; k++) { |
| var idx = offset + k; |
| var idxRepeated = offset * repeats + k; |
| for (var i = 0; i < axisSize; i++) { |
| for (var j = 0; j < repeats; j++) { |
| data[idxRepeated] = source[idx]; |
| idxRepeated += stride; |
| } |
| idx += stride; |
| } |
| } |
| } |
| |
| return out; |
| }), |
| |
| choose : function () { |
| console.warn('TODO'); |
| }, |
| |
| take : function () { |
| console.warn('TODO'); |
| }, |
| |
| tile : function () { |
| console.warn('TODO'); |
| }, |
| |
| /** |
| * Preprocess for array calculation |
| * max, min, argmax, argmin, sum, ptp, val, mean |
| * Which will reduce one axis if the axis is given |
| * |
| * @param {Number} axis |
| * @param {NDArray} out |
| * @param {Function} funcWithAxis |
| * @param {Function} funcFlatten |
| * @return {Number|NDArray} |
| */ |
| _withPreprocess1 : function (axis, out, funcWithAxis, funcFlatten) { |
| var source = this._array; |
| if (!this._size) { |
| return; |
| } |
| |
| if (typeof(axis)!=='undefined') { |
| if (axis < 0) { |
| axis = this._shape.length + axis; |
| } |
| if (axis >= this._shape.length || axis < 0) { |
| throw new Error(axisOutofBoundsErrorMsg(axis)); |
| } |
| |
| var shape = this._shape.slice(); |
| shape.splice(axis, 1); |
| if (out && !arrayEqual(shape, out._shape)) { |
| throw new Error(broadcastErrorMsg(shape, out._shape)); |
| } |
| |
| if (!out) { |
| out = new NDArray(this._dtype); |
| out.initFromShape(shape); |
| } |
| var data = out._array; |
| |
| var stride = calculateDimStride(this._shape, axis); |
| var axisSize = this._shape[axis]; |
| var offsetStride = stride * axisSize; |
| |
| funcWithAxis.call( |
| this, data, source, offsetStride, axisSize, stride |
| ); |
| |
| return out; |
| } else { |
| return funcFlatten.call(this, source); |
| } |
| }, |
| |
| /** |
| * Preprocess for array calculation cumsum, cumprod |
| * Which will keep the shape if axis is given |
| * and flatten if axis is undefined |
| * @param {Number} axis |
| * @param {NDArray} out |
| * @param {Function} funcWithAxis |
| * @param {Function} funcFlatten |
| * @return {NDArray} |
| */ |
| _withPreprocess2 : function (axis, out, funcWithAxis, funcFlatten) { |
| var source = this._array; |
| if (!this._size) { |
| return; |
| } |
| if (out && !arrayEqual(this._shape, out._shape)) { |
| throw new Error(broadcastErrorMsg(this._shape, out._shape)); |
| } |
| if (!out) { |
| out = new NDArray(this._dtype); |
| out.initFromShape(this._shape); |
| } |
| |
| var data = out._array; |
| |
| if (typeof(axis)!=='undefined') { |
| if (axis < 0) { |
| axis = this._shape.length + axis; |
| } |
| if (axis >= this._shape.length || axis < 0) { |
| throw new Error(axisOutofBoundsErrorMsg(axis)); |
| } |
| |
| if (axis >= this._shape.length) { |
| throw new Error(axisOutofBoundsErrorMsg(axis)); |
| } |
| |
| var stride = calculateDimStride(this._shape, axis); |
| var axisSize = this._shape[axis]; |
| var offsetStride = stride * axisSize; |
| |
| funcWithAxis.call( |
| this, data, source, offsetStride, axisSize, stride |
| ); |
| } else { |
| out.reshape([this._size]); |
| funcFlatten.call(this, data, source); |
| } |
| |
| return out; |
| }, |
| |
| /** |
| * Get the max value of ndarray |
| * If the axis is given, the max is only calculate in this dimension |
| * Example, for the given ndarray |
| * [[3, 9], |
| * [4, 8]] |
| * >>> max(0) |
| * [4, 9] |
| * >>> max(1) |
| * [9, 8] |
| * |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| max : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var idx = i + offset; |
| var max = source[idx]; |
| for (var j = 0; j < axisSize; j++) { |
| var d = source[idx]; |
| if (d > max) { |
| max = d; |
| } |
| idx += stride; |
| } |
| data[cursor++] = max; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var max = source[0]; |
| for (var i = 1; i < this._size; i++) { |
| if (source[i] > max) { |
| max = source[i]; |
| } |
| } |
| return max; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| |
| /** |
| * Return the minimum of an array or minimum along an axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| min : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var idx = i + offset; |
| var min = source[idx]; |
| for (var j = 0; j < axisSize; j++) { |
| var d = source[idx]; |
| if (d < min) { |
| min = d; |
| } |
| idx += stride; |
| } |
| data[cursor++] = min; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var min = source[0]; |
| for (var i = 1; i < this._size; i++) { |
| if (source[i] < min) { |
| min = source[i]; |
| } |
| } |
| return min; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Return indices of the maximum values along an axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| argmax : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var dataIdx = 0; |
| var idx = i + offset; |
| var max = source[idx]; |
| for (var j = 0; j < axisSize; j++) { |
| var d = source[idx]; |
| if (d > max) { |
| max = d; |
| dataIdx = j; |
| } |
| idx += stride; |
| } |
| data[cursor++] = dataIdx; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var max = source[0]; |
| var idx = 0; |
| for (var i = 1; i < this._size; i++) { |
| if (source[i] > max) { |
| idx = i; |
| max = source[i]; |
| } |
| } |
| return idx; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Indices of the minimum values along an axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| argmin : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var dataIdx = 0; |
| var idx = i + offset; |
| var min = source[idx]; |
| for (var j = 0; j < axisSize; j++) { |
| var d = source[idx]; |
| if (d < min) { |
| min = d; |
| dataIdx = j; |
| } |
| idx += stride; |
| } |
| data[cursor++] = dataIdx; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var min = source[0]; |
| var idx = 0; |
| for (var i = 1; i < this._size; i++) { |
| if (source[i] < min) { |
| idx = i; |
| min = source[i]; |
| } |
| } |
| return idx; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Return the sum of the array elements over the given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| sum : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var sum = 0; |
| var idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| sum += source[idx]; |
| idx += stride; |
| } |
| data[cursor++] = sum; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var sum = 0; |
| for (var i = 0; i < this._size; i++) { |
| sum += source[i]; |
| } |
| return sum; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Return the product of the array elements over the given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| prod : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var prod = 1; |
| var idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| prod *= source[idx]; |
| idx += stride; |
| } |
| data[cursor++] = prod; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var prod = 1; |
| for (var i = 0; i < this._size; i++) { |
| prod *= source[i]; |
| } |
| return prod; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Returns the average of the array elements along given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| mean : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var sum = 0; |
| var idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| sum += source[idx]; |
| idx += stride; |
| } |
| var mean = sum / axisSize; |
| data[cursor++] = mean; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var sum = 0; |
| var len = source.length; |
| for (var i = 0; i < len; i++) { |
| sum += source[i]; |
| } |
| var mean = sum / len; |
| return mean; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Return the variance of the array elements over the given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| 'var' : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var sum = 0; |
| var idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| sum += source[idx]; |
| idx += stride; |
| } |
| var mean = sum / axisSize; |
| var moments = 0; |
| idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| var diff = source[idx] - mean; |
| moments += diff * diff; |
| idx += stride; |
| } |
| data[cursor++] = moments / axisSize; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var sum = 0; |
| var len = source.length; |
| for (var i = 0; i < len; i++) { |
| sum += source[i]; |
| } |
| var mean = sum / len; |
| var moments = 0; |
| for (var i = 0; i < len; i++) { |
| var diff = source[i] - mean; |
| moments += diff * diff; |
| } |
| return moments / len; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Return the standard derivatione of the array elements |
| * over the given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| std : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var sum = 0; |
| var idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| sum += source[idx]; |
| idx += stride; |
| } |
| var mean = sum / axisSize; |
| var moments = 0; |
| idx = i + offset; |
| for (var j = 0; j < axisSize; j++) { |
| var diff = source[idx] - mean; |
| moments += diff * diff; |
| idx += stride; |
| } |
| data[cursor++] = Math.sqrt(moments / axisSize); |
| } |
| } |
| } |
| function withFlatten(source) { |
| var sum = 0; |
| var len = source.length; |
| for (var i = 0; i < len; i++) { |
| sum += source[i]; |
| } |
| var mean = sum / len; |
| var moments = 0; |
| for (var i = 0; i < len; i++) { |
| var diff = source[i] - mean; |
| moments += diff * diff; |
| } |
| return Math.sqrt(moments / len); |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Peak to peak (maximum - minimum) value along a given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| ptp : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var idx = offset + i; |
| var min = source[idx]; |
| var max = source[idx]; |
| for (var j = 0; j < axisSize; j++) { |
| var d = source[idx]; |
| if (d < min) { |
| min = d; |
| } |
| if (d > max) { |
| max = d; |
| } |
| idx += stride; |
| } |
| data[cursor++] = max - min; |
| } |
| } |
| } |
| function withFlatten(source) { |
| var min = source[0]; |
| var max = source[0]; |
| for (var i = 1; i < this._size; i++) { |
| if (source[i] < min) { |
| min = source[i]; |
| } |
| if (source[i] > max) { |
| max = source[i]; |
| } |
| } |
| return max - min; |
| } |
| return function (axis, out) { |
| return this._withPreprocess1( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * |
| * @param {Number} [axis=-1] |
| * @param {string} [order='ascending'] |
| * 'ascending' | 'descending' |
| * @return {NDArray} |
| */ |
| // FIXME : V8 is quick sort, firefox and safari is merge sort |
| // order : ascending or desc |
| sort : kwargs(function (axis, order) { |
| if (axis < 0) { |
| axis = this._shape.length + axis; |
| } |
| var compareFunc; |
| if (order === 'ascending') { |
| compareFunc = function (a, b) { |
| return a - b; |
| }; |
| } else if( order === 'descending') { |
| compareFunc = function (a, b) { |
| return b - a; |
| }; |
| } |
| |
| var source = this._array; |
| var stride = calculateDimStride(this._shape, axis); |
| var axisSize = this._shape[axis]; |
| |
| var offsetStride = stride * axisSize; |
| |
| var tmp = new Array(axisSize); |
| |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| |
| for (var i = 0; i < stride; i++) { |
| var idx = offset + i; |
| for (var j = 0; j < axisSize; j++) { |
| tmp[j] = source[idx]; |
| idx += stride; |
| } |
| tmp.sort(compareFunc); |
| var idx = offset + i; |
| // Copy back |
| for (var j = 0; j < axisSize; j++) { |
| source[idx] = tmp[j]; |
| idx += stride; |
| } |
| } |
| } |
| |
| return this; |
| |
| }, {axis : -1, order : 'ascending'}), |
| |
| /** |
| * |
| * @param {Number} [axis=-1] |
| * @param {string} [order='ascending'] |
| * 'ascending' | 'descending' |
| * @param {NDArray} [out] |
| * @return {NDArray} |
| */ |
| argsort : kwargs(function (axis, order, out) { |
| if (axis < 0) { |
| axis = this._shape.length + axis; |
| } |
| if (!this._size) { |
| return; |
| } |
| if (out && !arrayEqual(this._shape, out._shape)) { |
| throw new Error(broadcastErrorMsg(this._shape, out._shape)); |
| } |
| if (!out) { |
| out = new NDArray(this._dtype); |
| out.initFromShape(this._shape); |
| } |
| var data = out._array; |
| |
| var compareFunc; |
| if (order === 'ascending') { |
| compareFunc = function (a, b) { |
| return tmp[a] - tmp[b]; |
| }; |
| } else if( order === 'descending') { |
| compareFunc = function (a, b) { |
| return tmp[b] - tmp[a]; |
| }; |
| } |
| |
| var source = this._array; |
| var stride = calculateDimStride(this._shape, axis); |
| var axisSize = this._shape[axis]; |
| var offsetStride = stride * axisSize; |
| |
| var tmp = new Array(axisSize); |
| var indexList = new Array(axisSize); |
| |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var idx = offset + i; |
| for (var j = 0; j < axisSize; j++) { |
| tmp[j] = source[idx]; |
| indexList[j] = j; |
| idx += stride; |
| } |
| indexList.sort(compareFunc); |
| // Copy back |
| var idx = offset + i; |
| for (var j = 0; j < axisSize; j++) { |
| data[idx] = indexList[j]; |
| idx += stride; |
| } |
| } |
| } |
| |
| return out; |
| |
| }, {axis : -1, order : 'ascending'}), |
| |
| /** |
| * Return the cumulative sum of the elements along the given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| cumsum : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var idx = offset + i; |
| var prevIdx = idx; |
| data[idx] = source[idx]; |
| for (var j = 1; j < axisSize; j++) { |
| prevIdx = idx; |
| idx += stride; |
| data[idx] = data[prevIdx] + source[idx]; |
| } |
| |
| } |
| } |
| } |
| function withFlatten(data, source) { |
| data[0] = source[0]; |
| for (var i = 1; i < data.length; i++) { |
| data[i] = data[i-1] + source[i]; |
| } |
| } |
| return function (axis, out) { |
| return this._withPreprocess2( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Return the cumulative product of the elements along the given axis. |
| * @param {Number} [axis] |
| * @param {NDArray} out |
| * @return {NDArray} |
| */ |
| cumprod : kwargs((function () { |
| function withAxis(data, source, offsetStride, axisSize, stride) { |
| for (var offset = 0; offset < this._size; offset+=offsetStride) { |
| for (var i = 0; i < stride; i++) { |
| var idx = offset + i; |
| var prevIdx = idx; |
| data[idx] = source[idx]; |
| for (var j = 1; j < axisSize; j++) { |
| prevIdx = idx; |
| idx += stride; |
| data[idx] = data[prevIdx] * source[idx]; |
| } |
| |
| } |
| } |
| } |
| function withFlatten(data, source) { |
| data[0] = source[0]; |
| for (var i = 1; i < data.length; i++) { |
| data[i] = data[i-1] * source[i]; |
| } |
| } |
| return function (axis, out) { |
| return this._withPreprocess2( |
| axis, out, |
| withAxis, withFlatten |
| ); |
| }; |
| })()), |
| |
| /** |
| * Dot product of two arrays. |
| * |
| * @param {NDArray|Number} b |
| * @param {NDArray} [out] |
| * @return {NDArray|Number} |
| */ |
| dot : function () { |
| console.warn('TODO'); |
| }, |
| |
| /** |
| * Mapped to region [min, max] |
| * @param {Number} mappedMin |
| * @param {Number} mappedMax |
| */ |
| map : function (mappedMin, mappedMax) { |
| var input = this._array; |
| var output = this._array; |
| |
| var min = input[0]; |
| var max = input[0]; |
| var l = this._size; |
| for (var i = 1; i < l; i++) { |
| var val = input[i]; |
| if (val < min) { |
| min = val; |
| } |
| if (val > max) { |
| max = val; |
| } |
| } |
| var range = max - min; |
| var mappedRange = mappedMax - mappedMin; |
| for (var i = 0; i < l; i++) { |
| if (range === 0) { |
| output[i] = mappedMin; |
| } else { |
| var val = input[i]; |
| var percent = (val - min) / range; |
| output[i] = mappedRange * percent + mappedMin; |
| } |
| } |
| return this; |
| }, |
| |
| /** |
| * Add |
| */ |
| add : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_ADD, out |
| ); |
| }, |
| |
| /** |
| * Substract |
| */ |
| sub : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_SUB, out |
| ); |
| }, |
| |
| /** |
| * Multiply |
| */ |
| mul : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_MUL, out |
| ); |
| }, |
| |
| /** |
| * Divide |
| */ |
| div : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_DIV, out |
| ); |
| }, |
| /** |
| * mod |
| */ |
| mod : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_MOD, out |
| ); |
| }, |
| /** |
| * and |
| */ |
| and : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_AND, out |
| ); |
| }, |
| /** |
| * or |
| */ |
| or : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_OR, out |
| ); |
| }, |
| /** |
| * xor |
| */ |
| xor : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_XOR, out |
| ); |
| }, |
| /** |
| * equal |
| */ |
| equal : function (rightOperand, out) { |
| return this.binaryOperation( |
| this, rightOperand, E_EQL, out |
| ); |
| }, |
| |
| binaryOperation : function (lo, ro, op, out) { |
| // Broadcasting |
| // http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html |
| var shape = []; |
| var isLoScalar = typeof(lo) === 'number'; |
| var isRoScalar = typeof(ro) === 'number'; |
| if (isLoScalar) { |
| shape = ro._shape.slice(); |
| } else if (isRoScalar) { |
| shape = lo._shape.slice(); |
| } else { |
| // Starts with the trailing dimensions |
| var cl = lo._shape.length-1; |
| var cr = ro._shape.length-1; |
| var loBroadCasted = lo; |
| var roBroadCasted = ro; |
| while (cl >= 0 && cr >= 0) { |
| if (lo._shape[cl] == 1) { |
| shape.unshift(ro._shape[cr]); |
| loBroadCasted = lo.repeat(ro._shape[cr], cl); |
| } else if(ro._shape[cr] == 1) { |
| shape.unshift(lo._shape[cl]); |
| roBroadCasted = ro.repeat(lo._shape[cl], cr); |
| } else if(ro._shape[cr] == lo._shape[cl]) { |
| shape.unshift(lo._shape[cl]); |
| } else { |
| throw new Error(broadcastErrorMsg(lo._shape, ro._shape)); |
| } |
| cl --; |
| cr --; |
| } |
| for (var i = cl; i >= 0; i--) { |
| shape.unshift(lo._shape[i]); |
| } |
| for (var i = cr; i >= 0; i--) { |
| shape.unshift(ro._shape[i]); |
| } |
| lo = loBroadCasted; |
| ro = roBroadCasted; |
| } |
| if (!out) { |
| out = new NDArray(this._dtype); |
| out.initFromShape(shape); |
| } else { |
| if (! arrayEqual(shape, out._shape)) { |
| throw new Error(broadcastErrorMsg(shape, out._shape)); |
| } |
| } |
| var outData = out._array; |
| |
| var diffAxis; |
| var isLoLarger; |
| var loData; |
| var roData; |
| if (isLoScalar) { |
| diffAxis = ro._shape.length-1; |
| isLoLarger = false; |
| loData = lo; |
| roData = ro._array; |
| } else if(isRoScalar) { |
| diffAxis = lo._shape.length-1; |
| isLoLarger = true; |
| roData = ro; |
| loData = lo._array; |
| } else { |
| diffAxis = Math.abs(lo._shape.length - ro._shape.length); |
| isLoLarger = lo._shape.length >= ro._shape.length; |
| loData = lo._array; |
| roData = ro._array; |
| } |
| var stride = calculateDimStride(shape, diffAxis); |
| var axisSize = shape[diffAxis]; |
| |
| var offsetStride = stride * axisSize; |
| var offsetRepeats = out._size / offsetStride; |
| |
| var _a, _b, res; |
| var idx = 0; |
| if (isLoLarger) { |
| if(isRoScalar) { |
| for (var c = 0; c < offsetRepeats; c++) { |
| for (var i = 0; i < offsetStride; i++) { |
| _a = loData[idx]; _b = roData; |
| switch (op) { |
| case E_ADD: res = _a + _b; break; |
| case E_SUB: res = _a - _b; break; |
| case E_MUL: res = _a * _b; break; |
| case E_DIV: res = _a / _b; break; |
| case E_MOD: res = _a % _b; break; |
| case E_AND: res = _a & _b; break; |
| case E_OR: res = _a | _b; break; |
| case E_XOR: res = _a ^ _b; break; |
| case E_EQL: res = _a == _b; break; |
| default: throw new Error('Unkown operation ' + op); |
| } |
| outData[idx] = res; |
| idx ++; |
| } |
| } |
| } else { |
| for (var c = 0; c < offsetRepeats; c++) { |
| for (var i = 0; i < offsetStride; i++) { |
| _a = loData[idx]; _b = roData[i]; |
| switch (op) { |
| case E_ADD: res = _a + _b; break; |
| case E_SUB: res = _a - _b; break; |
| case E_MUL: res = _a * _b; break; |
| case E_DIV: res = _a / _b; break; |
| case E_MOD: res = _a % _b; break; |
| case E_AND: res = _a & _b; break; |
| case E_OR: res = _a | _b; break; |
| case E_XOR: res = _a ^ _b; break; |
| case E_EQL: res = _a == _b; break; |
| default: throw new Error('Unkown operation ' + op); |
| } |
| outData[idx] = res; |
| idx ++; |
| } |
| } |
| } |
| } else { |
| if (isLoScalar) { |
| for (var c = 0; c < offsetRepeats; c++) { |
| for (var i = 0; i < offsetStride; i++) { |
| _a = loData; _b = roData[idx]; |
| switch (op) { |
| case E_ADD: res = _a + _b; break; |
| case E_SUB: res = _a - _b; break; |
| case E_MUL: res = _a * _b; break; |
| case E_DIV: res = _a / _b; break; |
| case E_MOD: res = _a % _b; break; |
| case E_AND: res = _a & _b; break; |
| case E_OR: res = _a | _b; break; |
| case E_XOR: res = _a ^ _b; break; |
| case E_EQL: res = _a == _b; break; |
| default: throw new Error('Unkown operation ' + op); |
| } |
| outData[idx] = res; |
| idx ++; |
| } |
| } |
| } else { |
| for (var c = 0; c < offsetRepeats; c++) { |
| for (var i = 0; i < offsetStride; i++) { |
| _a = loData[idx]; _b = roData[i]; |
| switch (op) { |
| case E_ADD: res = _a + _b; break; |
| case E_SUB: res = _a - _b; break; |
| case E_MUL: res = _a * _b; break; |
| case E_DIV: res = _a / _b; break; |
| case E_MOD: res = _a % _b; break; |
| case E_AND: res = _a & _b; break; |
| case E_OR: res = _a | _b; break; |
| case E_XOR: res = _a ^ _b; break; |
| case E_EQL: res = _a == _b; break; |
| default: throw new Error('Unkown operation ' + op); |
| } |
| outData[idx] = res; |
| idx ++; |
| } |
| } |
| } |
| } |
| return out; |
| }, |
| |
| /** |
| * negtive |
| */ |
| neg : function () { |
| var data = this._array; |
| for (var i = 0; i < this._size; i++) { |
| data[i] = -data[i]; |
| } |
| return this; |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| sin : function () { |
| return this._mathAdapter(Math.sin); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| cos : function () { |
| return this._mathAdapter(Math.cos); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| tan : function () { |
| return this._mathAdapter(Math.tan); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| abs : function () { |
| return this._mathAdapter(Math.abs); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| log : function () { |
| return this._mathAdapter(Math.log); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| sqrt : function () { |
| return this._mathAdapter(Math.sqrt); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| ceil : function () { |
| return this._mathAdapter(Math.ceil); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| floor : function () { |
| return this._mathAdapter(Math.floor); |
| }, |
| |
| /** |
| * @return {NDArray} this |
| */ |
| pow : function (exp) { |
| var data = this._array; |
| for (var i = 0; i < this._size; i++) { |
| data[i] = Math.pow(data[i], exp); |
| } |
| return this; |
| }, |
| |
| _mathAdapter : function (mathFunc) { |
| var data = this._array; |
| for (var i = 0; i < this._size; i++) { |
| data[i] = mathFunc(data[i]); |
| } |
| return this; |
| }, |
| |
| /** |
| * @param {Number} decimals |
| * @return {NDArray} this |
| */ |
| round : function (decimals) { |
| decimals = Math.floor(decimals || 0); |
| var offset = Math.pow(10, decimals); |
| var data = this._array; |
| if (decimals === 0) { |
| for (var i = 0; i < this._size; i++) { |
| data[i] = Math.round(data[i]); |
| } |
| } else { |
| for (var i = 0; i < this._size; i++) { |
| data[i] = Math.round(data[i] * offset) / offset; |
| } |
| } |
| return this; |
| }, |
| /** |
| * @param {Number} min |
| * @param {Number} max |
| * Clip to [min, max] |
| */ |
| clip : function (min, max) { |
| // TODO : Support array_like param |
| var data = this._array; |
| for (var i = 0; i < this._size; i++) { |
| data[i] = Math.max(Math.min(data[i], max), min); |
| } |
| return this; |
| }, |
| |
| /** |
| * Indexing array, support range indexing |
| * @param {string} index |
| * Index syntax can be an integer 1, 2, 3 |
| * Or more complex range indexing |
| * '1:2' |
| * '1:2, 1:2' |
| * '1:2, :' |
| * More about the indexing syntax can check the doc of numpy ndarray |
| * @param {NDArray} [out] |
| * @return {NDArray} New created sub array, or out if given |
| */ |
| get : function (index, out) { |
| if (typeof(index) == 'number') { |
| index = index.toString(); |
| } |
| var strides = calculateDimStrides(this._shape); |
| var res = this._parseRanges(index); |
| var ranges = res[0]; |
| var shape = res[1]; |
| |
| if (ranges.length > this._shape.length) { |
| throw new Error('Too many indices'); |
| } |
| // Get data |
| var len = ranges.length; |
| var data; |
| if (shape.length) { |
| out = new NDArray(this._dtype); |
| out.initFromShape(shape); |
| data = out._array; |
| } else { |
| data = []; |
| } |
| |
| var source = this._array; |
| var cursor = 0; |
| function getPiece(axis, offset) { |
| var range = ranges[axis]; |
| var stride = strides[axis]; |
| if (axis < len-1) { |
| if (range[2] > 0) { |
| for (var i = range[0]; i < range[1]; i += range[2]) { |
| getPiece(axis+1, offset + stride * i); |
| } |
| } else { |
| for (var i = range[0]; i > range[1]; i += range[2]) { |
| getPiece(axis+1, offset + stride * i); |
| } |
| } |
| } else { |
| if (range[2] > 0) { |
| for (var i = range[0]; i < range[1]; i += range[2]) { |
| for (var j = 0; j < stride; j++) { |
| data[cursor++] = source[i*stride + j + offset]; |
| } |
| } |
| } else { |
| for (var i = range[0]; i > range[1]; i += range[2]) { |
| for (var j = 0; j < stride; j++) { |
| data[cursor++] = source[i*stride + j + offset]; |
| } |
| } |
| } |
| } |
| } |
| |
| getPiece(0, 0); |
| |
| if (shape.length) { |
| // Return scalar |
| return out; |
| } else { |
| return data[0]; |
| } |
| |
| }, |
| |
| /** |
| * |
| * @param {string} index |
| * index syntax can be an integer 1, 2, 3 |
| * Or more complex range indexing |
| * '1:2' |
| * '1:2, 1:2' |
| * '1:2, :' |
| * More about the indexing syntax can check the doc of numpy ndarray |
| * @param {NDArray} ndarray Ndarray data source |
| * @return {NDArray} this |
| */ |
| set : function (index, narray) { |
| if (typeof(index) == 'number') { |
| index = index.toString(); |
| } |
| var strides = calculateDimStrides(this._shape); |
| var res = this._parseRanges(index); |
| var ranges = res[0]; |
| var shape = res[1]; |
| |
| if (ranges.length > this._shape.length) { |
| throw new Error('Too many indices'); |
| } |
| var isScalar = typeof(narray) == 'number'; |
| var len = ranges.length; |
| var data = this._array; |
| if (isScalar) { |
| // Set with a single scalar |
| var source = narray; |
| } else { |
| if (!arrayEqual(shape, narray.shape())) { |
| throw new Error(broadcastErrorMsg(shape, narray.shape())); |
| } |
| var source = narray._array; |
| } |
| var cursor = 0; |
| var setPiece = function (axis, offset) { |
| var range = ranges[axis]; |
| var stride = strides[axis]; |
| if (axis < len-1) { |
| if (range[2] > 0) { |
| for (var i = range[0]; i < range[1]; i += range[2]) { |
| setPiece(axis+1, offset + stride * i); |
| } |
| } else { |
| for (var i = range[0]; i > range[1]; i += range[2]) { |
| setPiece(axis+1, offset + stride * i); |
| } |
| } |
| } else { |
| if (range[2] > 0) { |
| for (var i = range[0]; i < range[1]; i += range[2]) { |
| for (var j = 0; j < stride; j++) { |
| if (isScalar) { |
| data[i*stride + j + offset] = source; |
| } else { |
| data[i*stride + j + offset] = source[cursor++]; |
| } |
| } |
| } |
| } else { |
| for (var i = range[0]; i > range[1]; i += range[2]) { |
| for (var j = 0; j < stride; j++) { |
| if (isScalar) { |
| data[i*stride + j + offset] = source; |
| } else { |
| data[i*stride + j + offset] = source[cursor++]; |
| } |
| } |
| } |
| } |
| } |
| }; |
| |
| setPiece(0, 0); |
| |
| return this; |
| }, |
| |
| /** |
| * Insert values along the given axis before the given indices. |
| * @param {Number|Array} obj |
| * Object that defines the index or indices before |
| * which values is inserted. |
| * @param {Number|Array|NDArray} values |
| * Values to insert |
| * @param {Number} [axis] |
| * @return {NDArray} this |
| */ |
| insert : kwargs(function (obj, values, axis) { |
| var data = this._array; |
| var isObjScalar = false; |
| if (typeof(obj) === 'number') { |
| obj = [obj]; |
| isObjScalar = true; |
| } |
| if (typeof(values) === 'number') { |
| values = new NDArray([values]); |
| } else if (values instanceof Array) { |
| values = new NDArray(values); |
| } |
| |
| if (typeof(axis) === 'undefined') { |
| this._shape = [this._size]; |
| axis = 0; |
| } |
| // Checking if indices is valid |
| var prev = obj[0]; |
| var axisSize = this._shape[axis]; |
| for (var i = 0; i < obj.length; i++) { |
| if (obj[i] < 0) { |
| obj[i] = axisSize + obj[i]; |
| } |
| if (obj[i] > axisSize) { |
| throw new Error(indexOutofBoundsErrorMsg(obj[i])); |
| } |
| if (obj[i] < prev) { |
| throw new Error('Index must be in ascending order'); |
| } |
| prev = obj[i]; |
| } |
| // Broadcasting |
| var targetShape = this._shape.slice(); |
| if (isObjScalar) { |
| targetShape.splice(axis, 1); |
| } else { |
| targetShape[axis] = obj.length; |
| } |
| |
| var sourceShape = values._shape; |
| var cs = sourceShape.length - 1; |
| var ct = targetShape.length - 1; |
| |
| var valueBroadcasted = values; |
| while (cs >= 0 && ct >= 0) { |
| if (sourceShape[cs] === 1) { |
| valueBroadcasted = values.repeat(targetShape[ct], cs); |
| } else if(sourceShape[cs] !== targetShape[ct]) { |
| throw new Error(broadcastErrorMsg(sourceShape, targetShape)); |
| } |
| cs --; |
| ct --; |
| } |
| values = valueBroadcasted; |
| |
| // Calculate indices to insert |
| var stride = calculateDimStride(this._shape, axis); |
| var axisSize = this._shape[axis]; |
| var offsetStride = axisSize * stride; |
| var offsetRepeats = this._size / offsetStride; |
| |
| var objLen = obj.length; |
| var indices = new Uint32Array(offsetRepeats * objLen); |
| |
| var cursor = 0; |
| for (var offset = 0; offset < this._size; offset += offsetStride) { |
| for (var i = 0; i < objLen; i++) { |
| var objIdx = obj[i]; |
| indices[cursor++] = offset + objIdx * stride; |
| } |
| } |
| |
| var resShape = this._shape.slice(); |
| resShape[axis] += obj.length; |
| var resSize = getSize(resShape); |
| if (this._array.length < resSize) { |
| var data = new ArrayConstructor[this._dtype](resSize); |
| } else { |
| var data = this._array; |
| } |
| var source = this._array; |
| var valuesArr = values._array; |
| |
| var idxCursor = indices.length - 1; |
| var end = this._size; |
| var start = indices[idxCursor]; |
| var dataCursor = resSize - 1; |
| var valueCursor = values._size - 1; |
| while (idxCursor >= 0) { |
| // Copy source data; |
| for (var i = end - 1; i >= start; i--) { |
| data[dataCursor--] = source[i]; |
| } |
| end = start; |
| start = indices[--idxCursor]; |
| // Copy inserted data; |
| for (var i = 0; i < stride; i++) { |
| if (valueCursor < 0) { |
| valueCursor = values._size - 1; |
| } |
| data[dataCursor--] = valuesArr[valueCursor--]; |
| } |
| } |
| // Copy the rest |
| for (var i = end - 1; i >= 0; i--) { |
| data[dataCursor--] = source[i]; |
| } |
| |
| this._array = data; |
| this._shape = resShape; |
| this._size = resSize; |
| |
| return this; |
| }), |
| |
| append : function () { |
| console.warn('TODO'); |
| }, |
| |
| /** |
| * Delete values along the axis |
| * @param {Array|Number} obj |
| * @param {Number} [axis] |
| * @return {NDArray} this |
| */ |
| 'delete' : kwargs(function (obj, axis) { |
| var data = this._array; |
| if (typeof(obj) === 'number') { |
| obj = [obj]; |
| } |
| var size = this._size; |
| |
| if (typeof(axis) === 'undefined') { |
| this._shape = [size]; |
| axis = 0; |
| } |
| |
| var stride = calculateDimStride(this._shape, axis); |
| var axisSize = this._shape[axis]; |
| |
| var offsetStride = stride * axisSize; |
| var cursor = 0; |
| for (var offset = 0; offset < size; offset += offsetStride) { |
| var start = 0; |
| var end = obj[0]; |
| var objCursor = 0; |
| while(objCursor < obj.length) { |
| if (end < 0) { |
| end = end + axisSize; |
| } |
| if (end > axisSize) { |
| throw new Error(indexOutofBoundsErrorMsg(end)); |
| } |
| if (end < start) { |
| throw new Error('Index must be in ascending order'); |
| } |
| for (var i = start; i < end; i++) { |
| for (var j = 0; j < stride; j++) { |
| data[cursor++] = data[i * stride + j + offset]; |
| } |
| } |
| start = end + 1; |
| end = obj[++objCursor]; |
| } |
| // Copy the rest |
| for (var i = start; i < axisSize; i++) { |
| for (var j = 0; j < stride; j++) { |
| data[cursor++] = data[i * stride + j + offset]; |
| } |
| } |
| } |
| this._shape[axis] -= obj.length; |
| this._size = getSize(this._shape); |
| |
| return this; |
| }), |
| |
| _parseRanges : function (index) { |
| var rangesStr = index.split(/\s*,\s*/); |
| |
| // Parse range of each axis |
| var ranges = []; |
| var shape = []; |
| var j = 0; |
| for (var i = 0; i < rangesStr.length; i++) { |
| if (rangesStr[i] === '...') { |
| var end = this._shape.length - (rangesStr.length - i); |
| while (j <= end) { |
| ranges.push([0, this._shape[j], 1]); |
| shape.push(this._shape[j]); |
| j++; |
| } |
| } else { |
| var range = parseRange(rangesStr[i], this._shape[j]); |
| ranges.push(range); |
| if(rangesStr[i].indexOf(':') >= 0) { |
| var size = Math.floor((range[1] - range[0]) / range[2]); |
| size = size < 0 ? 0 : size; |
| // Get a range not a item |
| shape.push(size); |
| } |
| j++; |
| } |
| } |
| // Copy the lower dimension size |
| for (; j < this._shape.length; j++) { |
| shape.push(this._shape[j]); |
| } |
| |
| return [ranges, shape]; |
| }, |
| |
| /** |
| * Export normal js array |
| * @return {Array} |
| */ |
| toArray : function () { |
| var data = this._array; |
| var cursor = 0; |
| |
| var shape = this._shape; |
| var dim = shape.length; |
| |
| function create(axis, out) { |
| var len = shape[axis]; |
| for (var i = 0; i < len; i++) { |
| if (axis < dim-1) { |
| create(axis+1, out[i] = []); |
| } else { |
| out[i] = data[cursor++]; |
| } |
| } |
| } |
| |
| var output = []; |
| create(0, output); |
| |
| return output; |
| }, |
| |
| /** |
| * Create a copy of self |
| * @return {NDArray} |
| */ |
| copy : function () { |
| var numArr = new NDArray(); |
| numArr._array = ArraySlice.call(this._array); |
| numArr._shape = this._shape.slice(); |
| numArr._dtype = this._dtype; |
| numArr._size = this._size; |
| |
| return numArr; |
| }, |
| |
| constructor : NDArray |
| }; |
| |
| /** |
| * |
| * @param {Number} [min=0] |
| * @param {Number} max |
| * @param {Number} [step=1] |
| * @param {string} [dtype] |
| * @return {NDArray} |
| */ |
| NDArray.range = kwargs(function (min, max, step, dtype) { |
| var args = ArraySlice.call(arguments); |
| // Last argument describe the data type of ndarray |
| var lastArg = args[args.length-1]; |
| if (typeof(lastArg) == 'string') { |
| var dtype = lastArg; |
| args.pop(); |
| } |
| if (args.length === 1) { |
| max = args[0]; |
| step = 1; |
| min = 0; |
| } else if(args.length == 2) { |
| step = 1; |
| } |
| dtype = dtype || 'number'; |
| |
| var array = new ArrayConstructor[dtype](Math.ceil((max - min)/step)); |
| var cursor = 0; |
| for (var i = min; i < max; i+=step) { |
| array[cursor++] = i; |
| } |
| var ndarray = new NDArray(); |
| ndarray._array = array; |
| ndarray._shape = [array.length]; |
| ndarray._dtype = dtype; |
| ndarray._size = array.length; |
| |
| return ndarray; |
| |
| }); |
| |
| /** |
| * |
| * @param {Array} shape |
| * @param {String} [dtype] |
| * @return {NDArray} |
| */ |
| NDArray.zeros = kwargs(function (shape, dtype) { |
| var ret = new NDArray(dtype); |
| ret.initFromShape(shape); |
| return ret; |
| }); |
| |
| /** |
| * Python like array indexing |
| * http://www.python.org/dev/peps/pep-0204/ |
| * |
| * @param {string} index |
| * index can be a simple integer 1,2,3, |
| * or a range 2:10, 2:10:1 |
| * example : |
| * 2:10 => [2, 10, 1], |
| * 10:2:-2 => [10, 2, -2], |
| * : => [0, dimSize, 1], |
| * ::-1 => [dimSize-1, -1, -1], |
| * @param {number} dimSize |
| * @return {Array} a tuple array [startOffset, endOffset, sliceStep] |
| */ |
| function parseRange(index, dimSize) { |
| if (index.indexOf(':') >= 0) { |
| // Range indexing; |
| var res = index.split(/\s*:\s*/); |
| |
| var step = parseInt(res[2] || 1, 10); |
| var start, end; |
| if (step === 0) { |
| throw new Error('Slice step cannot be zero'); |
| } |
| else if (step > 0) { |
| start = parseInt(res[0] || 0, 10); |
| end = parseInt(res[1] || dimSize, 10); |
| } |
| else { |
| start = parseInt(res[0] || dimSize - 1, 10); |
| end = parseInt(res[1] || -1, 10); |
| } |
| // Negtive offset |
| if (start < 0) { |
| start = dimSize + start; |
| } |
| // Negtive offset |
| if (end < 0 && res[1]) { |
| end = dimSize + end; |
| } |
| if (step > 0) { |
| // Clamp to [0-dimSize] |
| start = Math.max(Math.min(dimSize, start), 0); |
| // Clamp to [0-dimSize] |
| end = Math.max(Math.min(dimSize, end), 0); |
| } else { |
| // Clamp to [0-dimSize) |
| start = Math.max(Math.min(dimSize-1, start), -1); |
| // Clamp to [0-dimSize) |
| end = Math.max(Math.min(dimSize-1, end), -1); |
| } |
| return [start, end, step]; |
| } else { |
| var start = parseInt(index, 10); |
| // Negtive offset |
| if (start < 0) { |
| start = dimSize + start; |
| } |
| if (start < 0 || start > dimSize) { |
| throw new Error(indexOutofBoundsErrorMsg(index)); |
| } |
| // Clamp to [0-dimSize) |
| start = Math.max(Math.min(dimSize-1, start), 0); |
| return [start, start+1, 1]; |
| } |
| } |
| |
| function getSize(shape) { |
| var size = shape[0]; |
| for (var i = 1; i < shape.length; i++) { |
| size *= shape[i]; |
| } |
| return size; |
| } |
| |
| function getDimension(array) { |
| var dim = 1; |
| var el = array[0]; |
| while (el instanceof Array) { |
| el = el[0]; |
| dim ++; |
| } |
| return dim; |
| } |
| |
| function getShape(array) { |
| var shape = [array.length]; |
| var el = array[0]; |
| while (el instanceof Array) { |
| shape.push(el.length); |
| el = el[0]; |
| } |
| return shape; |
| } |
| |
| function calculateDimStride(shape, axis) { |
| if (axis == shape.length-1) { |
| return 1; |
| } |
| var stride = shape[axis+1]; |
| for (var i = axis+2; i < shape.length; i++) { |
| stride *= shape[i]; |
| } |
| return stride; |
| } |
| |
| function calculateDimStrides(shape) { |
| // Calculate stride of each axis |
| var strides = []; |
| var tmp = 1; |
| var len = getSize(shape); |
| for (var i = 0; i < shape.length; i++) { |
| tmp *= shape[i]; |
| strides.push(len / tmp); |
| } |
| |
| return strides; |
| } |
| |
| function arrayEqual(arr1, arr2) { |
| if (arr1.length !== arr2.length) { |
| return false; |
| } |
| for (var i = 0; i <arr1.length; i++) { |
| if (arr1[i] !== arr2[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| function broadcastErrorMsg(shape1, shape2) { |
| return 'Shape (' |
| + shape1.toString() + ') (' + shape2.toString() |
| +') could not be broadcast together'; |
| } |
| |
| function axisOutofBoundsErrorMsg(axis) { |
| return 'Axis ' + axis + ' out of bounds'; |
| } |
| |
| function indexOutofBoundsErrorMsg(idx) { |
| return 'Index ' + idx + ' out of bounds'; |
| } |
| |
| return NDArray; |
| |
| }); |
| /** |
| * echarts图表类:chord diagram |
| * |
| * @author pissang (https://github.com/pissang/) |
| * |
| */ |
| |
| define('echarts/chart/chord',['require','../component/base','./base','zrender/shape/Text','zrender/shape/Line','zrender/shape/Sector','../util/shape/Chord','../config','../util/ecData','zrender/tool/util','zrender/tool/vector','../util/ndarray','../chart'],function (require) { |
| |
| |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var SectorShape = require('zrender/shape/Sector'); |
| var ChordShape = require('../util/shape/Chord'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var vec2 = require('zrender/tool/vector'); |
| var NDArray = require('../util/ndarray'); |
| |
| var _devicePixelRatio = window.devicePixelRatio || 1; |
| |
| function Chord(ecTheme, messageCenter, zr, option, myChart) { |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this.refresh(option); |
| } |
| |
| Chord.prototype = { |
| type : ecConfig.CHART_TYPE_CHORD, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| this.selectedMap = {}; |
| this.chordSeries = []; |
| this.chordSerieSample = null; |
| var matrix = []; |
| var serieNumber = 0; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type === this.type) { |
| // Use the config of first chord serie |
| if (!this.chordSerieSample) { |
| this.chordSerieSample = series[i]; |
| this.reformOption(this.chordSerieSample); |
| } |
| |
| var _isSelected = this.isSelected(series[i].name); |
| // Filter by selected serie |
| this.selectedMap[series[i].name] = _isSelected; |
| if (!_isSelected) { |
| continue; |
| } |
| this.chordSeries.push(series[i]); |
| this.buildMark(i); |
| matrix.push(series[i].matrix); |
| serieNumber++; |
| } |
| } |
| if (!this.chordSerieSample) { |
| return; |
| } |
| if (!this.chordSeries.length) { |
| this.addShapeList(); |
| return; |
| } |
| |
| var zrWidth = this.zr.getWidth(); |
| var zrHeight = this.zr.getHeight(); |
| var zrSize = Math.min(zrWidth, zrHeight); |
| |
| this.groups = this.chordSerieSample.data; |
| this.startAngle = this.chordSerieSample.startAngle; |
| // Constrain to [0, 360] |
| this.startAngle = this.startAngle % 360; |
| if (this.startAngle < 0) { |
| this.startAngle = this.startAngle + 360; |
| } |
| this.clockWise = this.chordSerieSample.clockWise; |
| this.innerRadius = this.parsePercent( |
| this.chordSerieSample.radius[0], |
| zrSize / 2 |
| ); |
| this.outerRadius = this.parsePercent( |
| this.chordSerieSample.radius[1], |
| zrSize / 2 |
| ); |
| this.padding = this.chordSerieSample.padding; |
| this.sortGroups = this.chordSerieSample.sort; |
| this.sortSubGroups = this.chordSerieSample.sortSub; |
| this.showScale = this.chordSerieSample.showScale; |
| this.showScaleText = this.chordSerieSample.showScaleText; |
| this.center = [ |
| this.parsePercent(this.chordSerieSample.center[0], zrWidth), |
| this.parsePercent(this.chordSerieSample.center[1], zrHeight) |
| ]; |
| var fixSize = |
| this.chordSerieSample.itemStyle.normal.chordStyle.lineStyle.width - |
| this.chordSerieSample.itemStyle.normal.lineStyle.width; |
| this.strokeFix = |
| (fixSize / _devicePixelRatio) / this.innerRadius / Math.PI * 180; |
| |
| |
| this.dataMat = new NDArray(matrix); |
| this.dataMat = this.dataMat._transposelike([1, 2, 0]); |
| |
| // Filter the data by selected legend |
| var res = this._filterData(this.dataMat, this.groups); |
| this.dataMat = res[0]; |
| this.groups = res[1]; |
| |
| // Check if data is valid |
| var shape = this.dataMat.shape(); |
| if (shape[0] !== shape[1] || shape[0] !== this.groups.length) { |
| throw new Error('Data not valid'); |
| } |
| if (shape[0] === 0 || shape[2] === 0) { |
| this.addShapeList(); |
| return; |
| } |
| |
| // Down to 2 dimension |
| // More convenient for angle calculating and sort |
| this.dataMat.reshape(shape[0], shape[1] * shape[2]); |
| |
| // Processing data |
| var sumOut = this.dataMat.sum(1); |
| var percents = sumOut.mul(1 / sumOut.sum()); |
| |
| var groupNumber = shape[0]; |
| var subGroupNumber = shape[1] * shape[2]; |
| |
| var groupAngles = percents.mul(360 - this.padding * groupNumber); |
| var subGroupAngles = this.dataMat.div( |
| this.dataMat.sum(1).reshape(groupNumber, 1) |
| ); |
| subGroupAngles = subGroupAngles.mul( |
| groupAngles.sub(this.strokeFix * 2).reshape(groupNumber, 1) |
| ); |
| |
| switch (this.sortGroups) { |
| case 'ascending': |
| case 'descending': |
| var groupIndices = groupAngles |
| .argsort(0, this.sortGroups); |
| groupAngles['sort'](0, this.sortGroups); |
| sumOut['sort'](0, this.sortGroups); |
| break; |
| default: |
| var groupIndices = NDArray.range(shape[0]); |
| } |
| |
| switch (this.sortSubGroups) { |
| case 'ascending': |
| case 'descending': |
| var subGroupIndices = subGroupAngles |
| .argsort(1, this.sortSubGroups); |
| subGroupAngles['sort'](1, this.sortSubGroups); |
| break; |
| default: |
| var subGroupIndices = NDArray |
| .range(subGroupNumber) |
| .reshape(1, subGroupNumber) |
| .repeat(groupNumber, 0); |
| } |
| |
| var groupIndicesArr = groupIndices.toArray(); |
| var groupAnglesArr = groupAngles.toArray(); |
| var subGroupIndicesArr = subGroupIndices.toArray(); |
| var subGroupAnglesArr = subGroupAngles.toArray(); |
| var sumOutArray = sumOut.toArray(); |
| |
| var sectorAngles = []; |
| var chordAngles = new NDArray( |
| groupNumber, subGroupNumber |
| ).toArray(); |
| var values = []; |
| var start = 0; |
| var end = 0; |
| for (var i = 0; i < groupNumber; i++) { |
| var sortedIdx = groupIndicesArr[i]; |
| values[sortedIdx] = sumOutArray[i]; |
| |
| end = start + groupAnglesArr[i]; |
| sectorAngles[sortedIdx] = [start, end]; |
| |
| // Sub Group |
| var subStart = start + this.strokeFix; |
| var subEnd = subStart; |
| for (var j = 0; j < subGroupNumber; j++) { |
| subEnd = subStart + subGroupAnglesArr[sortedIdx][j]; |
| var subSortedIndex = subGroupIndicesArr[sortedIdx][j]; |
| /*jshint maxlen : 200*/ |
| chordAngles[sortedIdx][subSortedIndex] |
| = [subStart, subEnd]; |
| subStart = subEnd; |
| } |
| |
| start = end + this.padding; |
| } |
| |
| // reset data |
| this.chordShapes = new NDArray(groupNumber, groupNumber, serieNumber) |
| .toArray(); |
| this.sectorShapes = []; |
| |
| this._buildSectors(sectorAngles, values); |
| |
| chordAngles = new NDArray(chordAngles).reshape( |
| groupNumber, groupNumber, serieNumber, 2 |
| ).toArray(); |
| this._buildChords(chordAngles, this.dataMat.reshape(shape).toArray()); |
| |
| var res = this.normalizeValue(values); |
| if (this.showScale) { |
| this._buildScales( |
| res[0], |
| res[1], |
| sectorAngles, |
| new NDArray(res[0]).sum() / (360 - this.padding * groupNumber) |
| ); |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| _filterData : function (dataMat, groups) { |
| var indices = []; |
| var groupsFilted = []; |
| // Filter by selected group |
| for (var i = 0; i < groups.length; i++) { |
| var name = groups[i].name; |
| this.selectedMap[name] = this.isSelected(name); |
| if (!this.selectedMap[name]) { |
| indices.push(i); |
| } else { |
| groupsFilted.push(groups[i]); |
| } |
| } |
| if (indices.length) { |
| dataMat = dataMat['delete'](indices, 0); |
| dataMat = dataMat['delete'](indices, 1); |
| } |
| if (!dataMat.size()) { |
| return [dataMat, groupsFilted]; |
| } |
| // Empty data also need to be removed |
| indices = []; |
| var groupsFilted2 = []; |
| var shape = dataMat.shape(); |
| dataMat.reshape(shape[0], shape[1] * shape[2]); |
| var sumOutArray = dataMat.sum(1).toArray(); |
| dataMat.reshape(shape); |
| for (var i = 0; i < groupsFilted.length; i++) { |
| if (sumOutArray[i] === 0) { |
| indices.push(i); |
| } else { |
| groupsFilted2.push(groupsFilted[i]); |
| } |
| } |
| if (indices.length) { |
| dataMat = dataMat['delete'](indices, 0); |
| dataMat = dataMat['delete'](indices, 1); |
| } |
| |
| return [dataMat, groupsFilted2]; |
| }, |
| |
| _buildSectors : function (angles, data) { |
| var len = this.groups.length; |
| var len2 = this.chordSeries.length; |
| |
| var timeout; |
| |
| var showLabel = this.query( |
| this.chordSerieSample, 'itemStyle.normal.label.show' |
| ); |
| var labelColor = this.query( |
| this.chordSerieSample, 'itemStyle.normal.label.color' |
| ); |
| var rotateLabel = this.query( |
| this.chordSerieSample, 'itemStyle.normal.label.rotate' |
| ); |
| var labelDistance = this.query( |
| this.chordSerieSample, 'itemStyle.normal.label.distance' |
| ); |
| |
| var self = this; |
| function createMouseOver(idx) { |
| return function () { |
| if (timeout) { |
| clearTimeout(timeout); |
| } |
| timeout = setTimeout(function (){ |
| for (var i = 0; i < len; i++) { |
| self.sectorShapes[i].style.opacity |
| = i === idx ? 1 : 0.1; |
| self.zr.modShape(self.sectorShapes[i].id); |
| |
| for (var j = 0; j < len; j++) { |
| for (var k = 0; k < len2; k++) { |
| var chordShape = self.chordShapes[i][j][k]; |
| if (chordShape) { |
| chordShape.style.opacity |
| = (i === idx || j === idx) |
| ? 0.5 : 0.03; |
| self.zr.modShape(chordShape.id); |
| } |
| } |
| } |
| } |
| self.zr.refresh(); |
| }, 50); |
| }; |
| } |
| |
| function createMouseOut() { |
| return function () { |
| if (timeout) { |
| clearTimeout(timeout); |
| } |
| timeout = setTimeout(function (){ |
| for (var i = 0; i < len; i++) { |
| self.sectorShapes[i].style.opacity = 1.0; |
| self.zr.modShape(self.sectorShapes[i].id); |
| |
| for (var j = 0; j < len; j++) { |
| for (var k = 0; k < len2; k++) { |
| var chordShape = self.chordShapes[i][j][k]; |
| if (chordShape) { |
| chordShape.style.opacity = 0.5; |
| self.zr.modShape(chordShape.id); |
| } |
| } |
| } |
| } |
| self.zr.refresh(); |
| }, 50); |
| }; |
| } |
| |
| for (var i = 0; i < len; i++) { |
| var group = this.groups[i]; |
| var angle = angles[i]; |
| var _start = (this.clockWise ? (360 - angle[1]) : angle[0]) + this.startAngle; |
| var _end = (this.clockWise ? (360 - angle[0]) : angle[1]) + this.startAngle; |
| |
| var sector = { |
| zlevel : this._zlevelBase, |
| style : { |
| x : this.center[0], |
| y : this.center[1], |
| r0 : this.innerRadius, |
| r : this.outerRadius, |
| startAngle : _start, |
| endAngle : _end, |
| brushType : 'fill', |
| opacity: 1, |
| color : this.getColor(group.name) |
| }, |
| clickable: true, |
| highlightStyle : { |
| brushType : 'fill' |
| } |
| }; |
| sector.style.lineWidth = this.deepQuery( |
| [group, this.chordSerieSample], |
| 'itemStyle.normal.lineStyle.width' |
| ); |
| sector.highlightStyle.lineWidth = this.deepQuery( |
| [group, this.chordSerieSample], |
| 'itemStyle.emphasis.lineStyle.width' |
| ); |
| sector.style.strokeColor = this.deepQuery( |
| [group, this.chordSerieSample], |
| 'itemStyle.normal.lineStyle.color' |
| ); |
| sector.highlightStyle.strokeColor = this.deepQuery( |
| [group, this.chordSerieSample], |
| 'itemStyle.emphasis.lineStyle.color' |
| ); |
| if (sector.style.lineWidth > 0) { |
| sector.style.brushType = 'both'; |
| } |
| if (sector.highlightStyle.lineWidth > 0) { |
| sector.highlightStyle.brushType = 'both'; |
| } |
| ecData.pack( |
| sector, |
| this.chordSeries[0], |
| 0, |
| data[i], i, |
| group.name |
| ); |
| if (showLabel) { |
| var halfAngle = [_start + _end] / 2; |
| halfAngle %= 360; // Constrain to [0,360] |
| var isRightSide = halfAngle <= 90 |
| || halfAngle >= 270; |
| halfAngle = halfAngle * Math.PI / 180; |
| var v = [Math.cos(halfAngle), -Math.sin(halfAngle)]; |
| |
| var distance = this.showScaleText ? 35 + labelDistance : labelDistance; |
| var start = vec2.scale([], v, this.outerRadius + distance); |
| vec2.add(start, start, this.center); |
| |
| var labelShape = { |
| zlevel : this._zlevelBase - 1, |
| hoverable : false, |
| style : { |
| text : group.name, |
| textAlign : isRightSide ? 'left' : 'right', |
| color : labelColor |
| } |
| }; |
| if (rotateLabel) { |
| labelShape.rotation = isRightSide ? halfAngle : Math.PI + halfAngle; |
| if (isRightSide) { |
| labelShape.style.x = this.outerRadius + distance; |
| } else { |
| labelShape.style.x = -this.outerRadius - distance; |
| } |
| labelShape.style.y = 0; |
| labelShape.position = this.center; |
| } else { |
| labelShape.style.x = start[0]; |
| labelShape.style.y = start[1]; |
| } |
| labelShape.style.textColor = this.deepQuery( |
| [group, this.chordSerieSample], |
| 'itemStyle.normal.label.textStyle.color' |
| ) || '#fff'; |
| labelShape.style.textFont = this.getFont(this.deepQuery( |
| [group, this.chordSerieSample], |
| 'itemStyle.normal.label.textStyle' |
| )); |
| labelShape = new TextShape(labelShape); |
| this.shapeList.push(labelShape); |
| } |
| |
| sector.onmouseover = createMouseOver(i); |
| sector.onmouseout = createMouseOut(); |
| |
| sector = new SectorShape(sector); |
| this.shapeList.push(sector); |
| this.sectorShapes.push(sector); |
| } |
| }, |
| |
| _buildChords : function (angles, dataArr) { |
| var len = angles.length; |
| if (!len) { |
| return; |
| } |
| var len2 = angles[0][0].length; |
| |
| var chordLineStyle |
| = this.chordSerieSample.itemStyle.normal.chordStyle.lineStyle; |
| var chordLineStyleEmphsis |
| = this.chordSerieSample.itemStyle.emphasis.chordStyle.lineStyle; |
| |
| for (var i = 0; i < len; i++) { |
| for (var j = 0; j < len; j++) { |
| for (var k = 0; k < len2; k++) { |
| if (this.chordShapes[j][i][k]) { |
| continue; |
| } |
| |
| var angleIJ0 = angles[i][j][k][0]; |
| var angleJI0 = angles[j][i][k][0]; |
| |
| var angleIJ1 = angles[i][j][k][1]; |
| var angleJI1 = angles[j][i][k][1]; |
| |
| if (angleIJ0 - angleJI1 === 0 || |
| angleJI0 - angleJI1 === 0) { |
| this.chordShapes[i][j][k] = null; |
| continue; |
| } |
| |
| var color; |
| if (len2 === 1) { |
| if (angleIJ1 - angleIJ0 <= angleJI1 - angleJI0) { |
| color = this.getColor(this.groups[i].name); |
| } else { |
| color = this.getColor(this.groups[j].name); |
| } |
| } else { |
| color = this.getColor(this.chordSeries[k].name); |
| } |
| var s0 = !this.clockWise ? (360 - angleIJ1) : angleIJ0; |
| var s1 = !this.clockWise ? (360 - angleIJ0) : angleIJ1; |
| var t0 = !this.clockWise ? (360 - angleJI1) : angleJI0; |
| var t1 = !this.clockWise ? (360 - angleJI0) : angleJI1; |
| var chord = { |
| zlevel : this._zlevelBase, |
| style : { |
| center : this.center, |
| r : this.innerRadius, |
| source0 : s0 - this.startAngle, |
| source1 : s1 - this.startAngle, |
| target0 : t0 - this.startAngle, |
| target1 : t1 - this.startAngle, |
| brushType : 'both', |
| opacity : 0.5, |
| color : color, |
| lineWidth : chordLineStyle.width, |
| strokeColor : chordLineStyle.color |
| }, |
| clickable: true, |
| highlightStyle : { |
| brushType : 'both', |
| lineWidth : chordLineStyleEmphsis.width, |
| strokeColor : chordLineStyleEmphsis.color |
| } |
| }; |
| |
| ecData.pack( |
| chord, |
| this.chordSeries[k], |
| k, |
| dataArr[i][j][k], i + '-' +j, |
| this.groups[i].name, |
| this.groups[j].name, |
| dataArr[j][i][k] |
| ); |
| |
| chord = new ChordShape(chord); |
| this.chordShapes[i][j][k] = chord; |
| this.shapeList.push(chord); |
| } |
| } |
| } |
| }, |
| |
| _buildScales : function ( |
| values, |
| unitPostfix, |
| angles, |
| unitValue |
| ) { |
| for (var i = 0; i < angles.length; i++) { |
| var subStartAngle = angles[i][0]; |
| var subEndAngle = angles[i][1]; |
| |
| var scaleAngle = subStartAngle; |
| while (scaleAngle < subEndAngle) { |
| var thelta = ((this.clockWise ? (360 - scaleAngle) : scaleAngle) |
| + this.startAngle) / 180 * Math.PI; |
| var v = [ |
| Math.cos(thelta), |
| -Math.sin(thelta) |
| ]; |
| var start = vec2.scale([], v, this.outerRadius + 1); |
| vec2.add(start, start, this.center); |
| var end = vec2.scale([], v, this.outerRadius + this.scaleLineLength); |
| vec2.add(end, end, this.center); |
| var scaleShape = { |
| zlevel : this._zlevelBase - 1, |
| hoverable : false, |
| style : { |
| xStart : start[0], |
| yStart : start[1], |
| xEnd : end[0], |
| yEnd : end[1], |
| lineCap : 'round', |
| brushType : 'stroke', |
| strokeColor : '#666', |
| lineWidth: 1 |
| } |
| }; |
| |
| scaleShape = new LineShape(scaleShape); |
| this.shapeList.push(scaleShape); |
| |
| scaleAngle += this.scaleUnitAngle; |
| } |
| if (!this.showScaleText) { |
| continue; |
| } |
| |
| var scaleTextAngle = subStartAngle; |
| var step = unitValue * 5 * this.scaleUnitAngle; |
| var scaleValues = NDArray.range(0, values[i], step).toArray(); |
| while (scaleTextAngle < subEndAngle) { |
| var thelta = this.clockWise |
| ? (360 - scaleTextAngle) : scaleTextAngle; |
| thelta = (thelta + this.startAngle) % 360; |
| var isRightSide = thelta <= 90 |
| || thelta >= 270; |
| var textShape = { |
| zlevel : this._zlevelBase - 1, |
| hoverable : false, |
| style : { |
| x : isRightSide |
| ? this.outerRadius + this.scaleLineLength + 4 |
| : -this.outerRadius - this.scaleLineLength - 4, |
| y : 0, |
| text : Math.round(scaleValues.shift()*10)/10 |
| + unitPostfix, |
| textAlign : isRightSide ? 'left' : 'right' |
| }, |
| position : this.center.slice(), |
| rotation : isRightSide |
| ? [thelta / 180 * Math.PI, 0, 0] |
| : [ |
| (thelta + 180) / 180 * Math.PI, |
| 0, 0 |
| ] |
| }; |
| |
| textShape = new TextShape(textShape); |
| this.shapeList.push(textShape); |
| scaleTextAngle += this.scaleUnitAngle * 5; |
| } |
| } |
| }, |
| |
| normalizeValue : function (values) { |
| var result = []; |
| var max = new NDArray(values).max(); |
| var unitPostfix, unitScale; |
| if (max > 10000) { |
| unitPostfix = 'k'; |
| unitScale = 1 / 1000; |
| } else if (max > 10000000) { |
| unitPostfix = 'm'; |
| unitScale = 1 / 1000000; |
| } else if (max > 10000000000) { |
| unitPostfix = 'b'; |
| unitScale = 1 / 1000000000; |
| } else { |
| unitPostfix = ''; |
| unitScale = 1; |
| } |
| |
| for (var i = 0; i < values.length; i++) { |
| result[i] = values[i] * unitScale; |
| } |
| return [result, unitPostfix]; |
| }, |
| |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| /* |
| this.legend; |
| this.getColor; |
| this.isSelected; |
| this.chordSerieSample; |
| */ |
| // Config |
| this.chordSeries = []; |
| /* |
| this.groups; |
| this.startAngle; |
| this.clockWise; |
| this.innerRadius; |
| this.outerRadius; |
| this.padding; |
| this.sortGroups; |
| this.sortSubGroups; |
| this.center; |
| this.showScale; |
| this.showScaleText; |
| this.dataMat; |
| */ |
| this.strokeFix = 0; |
| // Adjacency matrix |
| this.sectorShapes = []; |
| this.chordShapes = []; |
| |
| this.scaleLineLength = 4; |
| this.scaleUnitAngle = 4; |
| |
| this.legend = this.component.legend; |
| if (this.legend) { |
| this.getColor = function(param) { |
| return this.legend.getColor(param); |
| }; |
| this.isSelected = function(param) { |
| return this.legend.isSelected(param); |
| }; |
| } else { |
| var colorIndices = {}; |
| var colorMap = {}; |
| var count = 0; |
| this.getColor = function (key) { |
| if (colorMap[key]) { |
| return colorMap[key]; |
| } |
| if (colorIndices[key] === undefined) { |
| colorIndices[key] = count++; |
| } |
| // key is serie name |
| for (var i = 0; i < this.chordSeries.length; i++) { |
| if (this.chordSeries[i].name === key) { |
| colorMap[key] = this.query( |
| this.chordSeries[i], |
| 'itemStyle.normal.color' |
| ); |
| break; |
| } |
| } |
| if (!colorMap[key]) { |
| var len = this.groups.length; |
| // key is group name |
| for (var i = 0; i < len; i++) { |
| if (this.groups[i].name === key) { |
| colorMap[key] = this.query( |
| this.groups[i], |
| 'itemStyle.normal.color' |
| ); |
| break; |
| } |
| } |
| } |
| if (!colorMap[key]) { |
| colorMap[key] = this.zr.getColor(colorIndices[key]); |
| } |
| |
| return colorMap[key]; |
| }; |
| this.isSelected = function () { |
| return true; |
| }; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| }, |
| |
| reformOption : function (opt) { |
| var _merge = zrUtil.merge; |
| opt = _merge( |
| opt || {}, |
| this.ecTheme.chord |
| ); |
| opt.itemStyle.normal.label.textStyle = _merge( |
| opt.itemStyle.normal.label.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| } |
| }; |
| |
| zrUtil.inherits(Chord, ChartBase); |
| zrUtil.inherits(Chord, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('chord', Chord); |
| |
| return Chord; |
| }); |
| (function __echartsForceLayoutWorker(self) { |
| |
| // In web worker |
| if (typeof(window) === 'undefined' || window !== self) { |
| // Simple TMD implementation |
| self.tmd = {}; |
| self.tmd.modules = {}; |
| |
| self.tmd.require = function(id) { |
| return self.tmd.modules[id]; |
| }; |
| |
| self.define = function(id, deps, constructor) { |
| if (arguments.length === 0) { |
| return; |
| } else if (arguments.length == 1) { |
| constructor = id; |
| // TODO |
| id = 'ForceLayout'; |
| } else if (arguments.length == 2) { |
| constructor = deps; |
| } |
| // In release environment |
| // Ugliy polyfill |
| if (id.indexOf('ForceLayout') >= 0) { |
| id = 'ForceLayout'; |
| } |
| self.tmd.modules[id] = constructor(self.tmd.require); |
| }; |
| |
| // Vector2 math functions |
| define('zrender/tool/vector', ['require'],function(require) { |
| return { |
| create: function(x, y) { |
| var out = new Float32Array(2); |
| out[0] = x || 0; |
| out[1] = y || 0; |
| return out; |
| }, |
| dist: function(a, b) { |
| var x = b[0] - a[0]; |
| var y = b[1] - a[1]; |
| return Math.sqrt(x*x + y*y); |
| }, |
| len: function(a) { |
| var x = a[0]; |
| var y = a[1]; |
| return Math.sqrt(x*x + y*y); |
| }, |
| scaleAndAdd: function(out, a, b, scale) { |
| out[0] = a[0] + b[0] * scale; |
| out[1] = a[1] + b[1] * scale; |
| return out; |
| }, |
| scale: function(out, a, b) { |
| out[0] = a[0] * b; |
| out[1] = a[1] * b; |
| return out; |
| }, |
| add: function(out, a, b) { |
| out[0] = a[0] + b[0]; |
| out[1] = a[1] + b[1]; |
| return out; |
| }, |
| sub: function(out, a, b) { |
| out[0] = a[0] - b[0]; |
| out[1] = a[1] - b[1]; |
| return out; |
| }, |
| normalize: function(out, a) { |
| var x = a[0]; |
| var y = a[1]; |
| var len = x*x + y*y; |
| if (len > 0) { |
| //TODO: evaluate use of glm_invsqrt here? |
| len = 1 / Math.sqrt(len); |
| out[0] = a[0] * len; |
| out[1] = a[1] * len; |
| } |
| return out; |
| }, |
| negate: function(out, a) { |
| out[0] = -a[0]; |
| out[1] = -a[1]; |
| return out; |
| }, |
| copy: function(out, a) { |
| out[0] = a[0]; |
| out[1] = a[1]; |
| return out; |
| }, |
| set: function(out, x, y) { |
| out[0] = x; |
| out[1] = y; |
| return out; |
| } |
| }; |
| }); |
| |
| /**************************** |
| * Main process |
| ***************************/ |
| |
| var forceLayout = null; |
| |
| self.onmessage = function(e) { |
| // Position read back |
| if (e.data instanceof ArrayBuffer) { |
| if (!forceLayout) { |
| return; |
| } |
| var positionArr = new Float32Array(e.data); |
| var nNodes = (positionArr.length - 1) / 2; |
| for (var i = 0; i < nNodes; i++) { |
| var node = forceLayout.nodes[i]; |
| node.position[0] = positionArr[i * 2 + 1]; |
| node.position[1] = positionArr[i * 2 + 2]; |
| } |
| return; |
| } |
| |
| var ForceLayout = self.tmd.modules.ForceLayout; |
| |
| switch(e.data.cmd) { |
| case 'init': |
| if (!forceLayout) { |
| forceLayout = new ForceLayout(); |
| } |
| forceLayout.initNodes(e.data.nodesPosition, e.data.nodesMass, e.data.nodesSize); |
| forceLayout.initEdges(e.data.edges, e.data.edgesWeight); |
| forceLayout._token = e.data.token; |
| break; |
| case 'updateConfig': |
| if (forceLayout) { |
| for (var name in e.data.config) { |
| forceLayout[name] = e.data.config[name]; |
| } |
| } |
| break; |
| case 'update': |
| var steps = e.data.steps; |
| |
| if (forceLayout) { |
| var nNodes = forceLayout.nodes.length; |
| var positionArr = new Float32Array(nNodes * 2 + 1); |
| |
| forceLayout.temperature = e.data.temperature; |
| |
| if (e.data.temperature > 0.01) { |
| for (var i = 0; i < steps; i++) { |
| forceLayout.update(); |
| forceLayout.temperature *= e.data.coolDown; |
| } |
| // Callback |
| for (var i = 0; i < nNodes; i++) { |
| var node = forceLayout.nodes[i]; |
| positionArr[i * 2 + 1] = node.position[0]; |
| positionArr[i * 2 + 2] = node.position[1]; |
| } |
| |
| positionArr[0] = forceLayout._token; |
| } |
| |
| self.postMessage(positionArr.buffer, [positionArr.buffer]); |
| } else { |
| // Not initialzied yet |
| var emptyArr = new Float32Array(); |
| // Post transfer object |
| self.postMessage(emptyArr.buffer, [emptyArr.buffer]); |
| } |
| break; |
| } |
| }; |
| } |
| // 1. Graph Drawing by Force-directed Placement |
| // 2. http://webatlas.fr/tempshare/ForceAtlas2_Paper.pdf |
| define('echarts/chart/ForceLayoutWorker',['require','zrender/tool/vector'],function(require) { |
| |
| |
| |
| var vec2 = require('zrender/tool/vector'); |
| var ArrayCtor = typeof(Float32Array) == 'undefined' ? Array : Float32Array; |
| |
| /**************************** |
| * Class: Region |
| ***************************/ |
| |
| function Region() { |
| |
| this.subRegions = []; |
| |
| this.nSubRegions = 0; |
| |
| this.node = null; |
| |
| this.mass = 0; |
| |
| this.centerOfMass = null; |
| |
| this.bbox = new ArrayCtor(4); |
| |
| this.size = 0; |
| } |
| |
| // Reset before update |
| Region.prototype.beforeUpdate = function() { |
| for (var i = 0; i < this.nSubRegions; i++) { |
| this.subRegions[i].beforeUpdate(); |
| } |
| this.mass = 0; |
| if (this.centerOfMass) { |
| this.centerOfMass[0] = 0; |
| this.centerOfMass[1] = 0; |
| } |
| this.nSubRegions = 0; |
| this.node = null; |
| }; |
| // Clear after update |
| Region.prototype.afterUpdate = function() { |
| this.subRegions.length = this.nSubRegions; |
| for (var i = 0; i < this.nSubRegions; i++) { |
| this.subRegions[i].afterUpdate(); |
| } |
| }; |
| |
| Region.prototype.addNode = function(node) { |
| if (this.nSubRegions === 0) { |
| if (this.node == null) { |
| this.node = node; |
| return; |
| } else { |
| this._addNodeToSubRegion(this.node); |
| this.node = null; |
| } |
| } |
| this._addNodeToSubRegion(node); |
| |
| this._updateCenterOfMass(node); |
| }; |
| |
| Region.prototype.findSubRegion = function(x, y) { |
| for (var i = 0; i < this.nSubRegions; i++) { |
| var region = this.subRegions[i]; |
| if (region.contain(x, y)) { |
| return region; |
| } |
| } |
| }; |
| |
| Region.prototype.contain = function(x, y) { |
| return this.bbox[0] <= x |
| && this.bbox[2] >= x |
| && this.bbox[1] <= y |
| && this.bbox[3] >= y; |
| }; |
| |
| Region.prototype.setBBox = function(minX, minY, maxX, maxY) { |
| // Min |
| this.bbox[0] = minX; |
| this.bbox[1] = minY; |
| // Max |
| this.bbox[2] = maxX; |
| this.bbox[3] = maxY; |
| |
| this.size = (maxX - minX + maxY - minY) / 2; |
| }; |
| |
| Region.prototype._newSubRegion = function() { |
| var subRegion = this.subRegions[this.nSubRegions]; |
| if (!subRegion) { |
| subRegion = new Region(); |
| this.subRegions[this.nSubRegions] = subRegion; |
| } |
| this.nSubRegions++; |
| return subRegion; |
| }; |
| |
| Region.prototype._addNodeToSubRegion = function(node) { |
| var subRegion = this.findSubRegion(node.position[0], node.position[1]); |
| var bbox = this.bbox; |
| if (!subRegion) { |
| var cx = (bbox[0] + bbox[2]) / 2; |
| var cy = (bbox[1] + bbox[3]) / 2; |
| var w = (bbox[2] - bbox[0]) / 2; |
| var h = (bbox[3] - bbox[1]) / 2; |
| |
| var xi = node.position[0] >= cx ? 1 : 0; |
| var yi = node.position[1] >= cy ? 1 : 0; |
| |
| var subRegion = this._newSubRegion(); |
| // Min |
| subRegion.setBBox( |
| // Min |
| xi * w + bbox[0], |
| yi * h + bbox[1], |
| // Max |
| (xi + 1) * w + bbox[0], |
| (yi + 1) * h + bbox[1] |
| ); |
| } |
| |
| subRegion.addNode(node); |
| }; |
| |
| Region.prototype._updateCenterOfMass = function(node) { |
| // Incrementally update |
| if (this.centerOfMass == null) { |
| this.centerOfMass = vec2.create(); |
| } |
| var x = this.centerOfMass[0] * this.mass; |
| var y = this.centerOfMass[1] * this.mass; |
| x += node.position[0] * node.mass; |
| y += node.position[1] * node.mass; |
| this.mass += node.mass; |
| this.centerOfMass[0] = x / this.mass; |
| this.centerOfMass[1] = y / this.mass; |
| }; |
| |
| /**************************** |
| * Class: Graph Node |
| ***************************/ |
| function GraphNode() { |
| this.position = vec2.create(); |
| |
| this.force = vec2.create(); |
| this.forcePrev = vec2.create(); |
| |
| this.speed = vec2.create(); |
| this.speedPrev = vec2.create(); |
| |
| // If repulsionByDegree is true |
| // mass = inDegree + outDegree + 1 |
| // Else |
| // mass is manually set |
| this.mass = 1; |
| |
| this.inDegree = 0; |
| this.outDegree = 0; |
| } |
| |
| /**************************** |
| * Class: Graph Edge |
| ***************************/ |
| function GraphEdge(source, target) { |
| this.source = source; |
| this.target = target; |
| |
| this.weight = 1; |
| } |
| |
| /**************************** |
| * Class: ForceLayout |
| ***************************/ |
| function ForceLayout() { |
| |
| this.barnesHutOptimize = false; |
| this.barnesHutTheta = 1.5; |
| |
| this.repulsionByDegree = false; |
| |
| this.preventOverlap = false; |
| this.strongGravity = true; |
| |
| this.gravity = 1.0; |
| this.scaling = 1.0; |
| |
| this.edgeWeightInfluence = 1.0; |
| |
| this.center = [0, 0]; |
| this.width = 500; |
| this.height = 500; |
| |
| this.nodes = []; |
| this.edges = []; |
| |
| this.bbox = new ArrayCtor(4); |
| |
| this._rootRegion = new Region(); |
| this._rootRegion.centerOfMass = vec2.create(); |
| |
| this._massArr = null; |
| |
| this._k = 0; |
| } |
| |
| ForceLayout.prototype.initNodes = function(positionArr, massArr, sizeArr) { |
| |
| this.temperature = 1.0; |
| |
| var nNodes = positionArr.length / 2; |
| this.nodes.length = 0; |
| var haveSize = typeof(sizeArr) !== 'undefined'; |
| |
| for (var i = 0; i < nNodes; i++) { |
| var node = new GraphNode(); |
| node.position[0] = positionArr[i * 2]; |
| node.position[1] = positionArr[i * 2 + 1]; |
| node.mass = massArr[i]; |
| if (haveSize) { |
| node.size = sizeArr[i]; |
| } |
| this.nodes.push(node); |
| } |
| |
| this._massArr = massArr; |
| if (haveSize) { |
| this._sizeArr = sizeArr; |
| } |
| }; |
| |
| ForceLayout.prototype.initEdges = function(edgeArr, edgeWeightArr) { |
| var nEdges = edgeArr.length / 2; |
| this.edges.length = 0; |
| var edgeHaveWeight = typeof(edgeWeightArr) !== 'undefined'; |
| |
| for (var i = 0; i < nEdges; i++) { |
| var sIdx = edgeArr[i * 2]; |
| var tIdx = edgeArr[i * 2 + 1]; |
| var sNode = this.nodes[sIdx]; |
| var tNode = this.nodes[tIdx]; |
| |
| if (!sNode || !tNode) { |
| continue; |
| } |
| sNode.outDegree++; |
| tNode.inDegree++; |
| var edge = new GraphEdge(sNode, tNode); |
| |
| if (edgeHaveWeight) { |
| edge.weight = edgeWeightArr[i]; |
| } |
| |
| this.edges.push(edge); |
| } |
| }; |
| |
| ForceLayout.prototype.update = function() { |
| |
| var nNodes = this.nodes.length; |
| |
| this.updateBBox(); |
| |
| this._k = 0.4 * this.scaling * Math.sqrt(this.width * this.height / nNodes); |
| |
| if (this.barnesHutOptimize) { |
| this._rootRegion.setBBox( |
| this.bbox[0], this.bbox[1], |
| this.bbox[2], this.bbox[3] |
| ); |
| this._rootRegion.beforeUpdate(); |
| for (var i = 0; i < nNodes; i++) { |
| this._rootRegion.addNode(this.nodes[i]); |
| } |
| this._rootRegion.afterUpdate(); |
| } else { |
| // Update center of mass of whole graph |
| var mass = 0; |
| var centerOfMass = this._rootRegion.centerOfMass; |
| vec2.set(centerOfMass, 0, 0); |
| for (var i = 0; i < nNodes; i++) { |
| var node = this.nodes[i]; |
| mass += node.mass; |
| vec2.scaleAndAdd(centerOfMass, centerOfMass, node.position, node.mass); |
| } |
| vec2.scale(centerOfMass, centerOfMass, 1 / mass); |
| } |
| |
| // Reset forces |
| for (var i = 0; i < nNodes; i++) { |
| var node = this.nodes[i]; |
| vec2.copy(node.forcePrev, node.force); |
| vec2.copy(node.speedPrev, node.speed); |
| vec2.set(node.force, 0, 0); |
| } |
| |
| // Compute forces |
| // Repulsion |
| for (var i = 0; i < nNodes; i++) { |
| var na = this.nodes[i]; |
| if (this.barnesHutOptimize) { |
| this.applyRegionToNodeRepulsion(this._rootRegion, na); |
| } else { |
| for (var j = i + 1; j < nNodes; j++) { |
| var nb = this.nodes[j]; |
| this.applyNodeToNodeRepulsion(na, nb, false); |
| } |
| } |
| |
| // Gravity |
| if (this.gravity > 0) { |
| if (this.strongGravity) { |
| this.applyNodeStrongGravity(na); |
| } else { |
| this.applyNodeGravity(na); |
| } |
| } |
| } |
| |
| // Attraction |
| for (var i = 0; i < this.edges.length; i++) { |
| this.applyEdgeAttraction(this.edges[i]); |
| } |
| |
| // Apply forces |
| // var speed = vec2.create(); |
| var v = vec2.create(); |
| for (var i = 0; i < nNodes; i++) { |
| var node = this.nodes[i]; |
| var speed = node.speed; |
| |
| // var swing = vec2.dist(node.force, node.forcePrev); |
| // // var swing = 30; |
| // vec2.scale(node.force, node.force, 1 / (1 + Math.sqrt(swing))); |
| vec2.scale(node.force, node.force, 1 / 30); |
| |
| // contraint force |
| var df = vec2.len(node.force) + 0.1; |
| var scale = Math.min(df, 500.0) / df; |
| vec2.scale(node.force, node.force, scale); |
| |
| vec2.add(speed, speed, node.force); |
| |
| vec2.scale(speed, speed, this.temperature); |
| |
| // Prevent swinging |
| // Limited the increase of speed up to 100% each step |
| // TODO adjust by nodes number |
| vec2.sub(v, speed, node.speedPrev); |
| var swing = vec2.len(v); |
| if (swing > 0) { |
| vec2.scale(v, v, 1 / swing); |
| var base = vec2.len(node.speedPrev); |
| if (base > 0) { |
| swing = Math.min(swing / base, 1) * base; |
| vec2.scaleAndAdd(speed, node.speedPrev, v, swing); |
| } |
| } |
| |
| // constraint speed |
| var ds = vec2.len(speed); |
| var scale = Math.min(ds, 100.0) / (ds + 0.1); |
| vec2.scale(speed, speed, scale); |
| |
| vec2.add(node.position, node.position, speed); |
| } |
| }; |
| |
| ForceLayout.prototype.applyRegionToNodeRepulsion = (function() { |
| var v = vec2.create(); |
| return function applyRegionToNodeRepulsion(region, node) { |
| if (region.node) { // Region is a leaf |
| this.applyNodeToNodeRepulsion(region.node, node, true); |
| } else { |
| vec2.sub(v, node.position, region.centerOfMass); |
| var d2 = v[0] * v[0] + v[1] * v[1]; |
| if (d2 > this.barnesHutTheta * region.size * region.size) { |
| var factor = this._k * this._k * (node.mass + region.mass) / (d2 + 1); |
| vec2.scaleAndAdd(node.force, node.force, v, factor * 2); |
| } else { |
| for (var i = 0; i < region.nSubRegions; i++) { |
| this.applyRegionToNodeRepulsion(region.subRegions[i], node); |
| } |
| } |
| } |
| }; |
| })(); |
| |
| ForceLayout.prototype.applyNodeToNodeRepulsion = (function() { |
| var v = vec2.create(); |
| return function applyNodeToNodeRepulsion(na, nb, oneWay) { |
| if (na == nb) { |
| return; |
| } |
| vec2.sub(v, na.position, nb.position); |
| var d2 = v[0] * v[0] + v[1] * v[1]; |
| |
| // PENDING |
| if (d2 === 0) { |
| return; |
| } |
| |
| var factor; |
| var k2 = this._k * this._k; |
| var mass = na.mass + nb.mass; |
| |
| if (this.preventOverlap) { |
| var d = Math.sqrt(d2); |
| d = d - na.size - nb.size; |
| if (d > 0) { |
| factor = k2 * mass / (d * d); |
| } else if (d <= 0) { |
| // A stronger repulsion if overlap |
| factor = k2 * 10 * mass; |
| } |
| } else { |
| // Divide factor by an extra `d` to normalize the `v` |
| factor = k2 * mass / d2; |
| } |
| |
| if (!oneWay) { |
| vec2.scaleAndAdd(na.force, na.force, v, factor * 2); |
| } |
| vec2.scaleAndAdd(nb.force, nb.force, v, -factor * 2); |
| }; |
| })(); |
| |
| ForceLayout.prototype.applyEdgeAttraction = (function() { |
| var v = vec2.create(); |
| return function applyEdgeAttraction(edge) { |
| var na = edge.source; |
| var nb = edge.target; |
| |
| vec2.sub(v, na.position, nb.position); |
| var d = vec2.len(v); |
| |
| var w; |
| if (this.edgeWeightInfluence === 0) { |
| w = 1; |
| } else if (this.edgeWeightInfluence == 1) { |
| w = edge.weight; |
| } else { |
| w = Math.pow(edge.weight, this.edgeWeightInfluence); |
| } |
| |
| var factor; |
| |
| if (this.preventOverlap) { |
| d = d - na.size - nb.size; |
| if (d <= 0) { |
| // No attraction |
| return; |
| } |
| } |
| |
| var factor = -w * d / this._k; |
| |
| vec2.scaleAndAdd(na.force, na.force, v, factor); |
| vec2.scaleAndAdd(nb.force, nb.force, v, -factor); |
| }; |
| })(); |
| |
| ForceLayout.prototype.applyNodeGravity = (function() { |
| var v = vec2.create(); |
| return function(node) { |
| // PENDING Move to centerOfMass or [0, 0] ? |
| // vec2.sub(v, this._rootRegion.centerOfMass, node.position); |
| // vec2.negate(v, node.position); |
| vec2.sub(v, this.center, node.position); |
| var d = vec2.len(v); |
| vec2.scaleAndAdd(node.force, node.force, v, this.gravity * node.mass / (d + 1)); |
| }; |
| })(); |
| |
| ForceLayout.prototype.applyNodeStrongGravity = (function() { |
| var v = vec2.create(); |
| return function(node) { |
| // vec2.negate(v, node.position); |
| vec2.sub(v, this.center, node.position); |
| var d = vec2.len(v) / 100; |
| vec2.scaleAndAdd(node.force, node.force, v, d * this.gravity * node.mass); |
| }; |
| })(); |
| |
| ForceLayout.prototype.updateBBox = function() { |
| var minX = Infinity; |
| var minY = Infinity; |
| var maxX = -Infinity; |
| var maxY = -Infinity; |
| for (var i = 0; i < this.nodes.length; i++) { |
| var pos = this.nodes[i].position; |
| minX = Math.min(minX, pos[0]); |
| minY = Math.min(minY, pos[1]); |
| maxX = Math.max(maxX, pos[0]); |
| maxY = Math.max(maxY, pos[1]); |
| } |
| this.bbox[0] = minX; |
| this.bbox[1] = minY; |
| this.bbox[2] = maxX; |
| this.bbox[3] = maxY; |
| }; |
| |
| ForceLayout.getWorkerCode = function() { |
| var str = __echartsForceLayoutWorker.toString(); |
| return str.slice(str.indexOf('{') + 1, str.lastIndexOf('}')); |
| }; |
| |
| return ForceLayout; |
| }); |
| |
| })(window); |
| /** |
| * echarts图表类:力导向图 |
| * |
| * @author pissang (https://github.com/pissang/) |
| * |
| */ |
| |
| define('echarts/chart/force',['require','../component/base','./base','./ForceLayoutWorker','zrender/shape/Line','../util/shape/Icon','../config','../util/ecData','zrender/tool/util','zrender/config','zrender/tool/vector','../util/ndarray','../chart'],function (require) { |
| |
| |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| var ForceLayout = require('./ForceLayoutWorker'); |
| |
| // 图形依赖 |
| var LineShape = require('zrender/shape/Line'); |
| var IconShape = require('../util/shape/Icon'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrConfig = require('zrender/config'); |
| var vec2 = require('zrender/tool/vector'); |
| |
| var NDArray = require('../util/ndarray'); |
| var ArrayCtor = typeof(Float32Array) == 'undefined' ? Array : Float32Array; |
| |
| var requestAnimationFrame = window.requestAnimationFrame |
| || window.msRequestAnimationFrame |
| || window.mozRequestAnimationFrame |
| || window.webkitRequestAnimationFrame |
| || function (func){setTimeout(func, 16);}; |
| |
| // Use inline web worker |
| var workerUrl; |
| if ( |
| typeof(Worker) !== 'undefined' && |
| typeof(Blob) !== 'undefined' |
| ) { |
| var blob = new Blob([ForceLayout.getWorkerCode()]); |
| workerUrl = window.URL.createObjectURL(blob); |
| } |
| |
| function getToken() { |
| return Math.round(new Date().getTime() / 100) % 10000000; |
| } |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Force(ecTheme, messageCenter, zr, option, myChart) { |
| var self = this; |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| // 保存节点的位置,改变数据时能够有更好的动画效果 |
| // TODO |
| this.__nodePositionMap = {}; |
| |
| this._nodeShapes = []; |
| this._linkShapes = []; |
| |
| this._updating = true; |
| |
| this._filteredNodes = null; |
| this._filteredLinks = null; |
| this._rawNodes = null; |
| this._rawLinks = null; |
| |
| this._steps = 1; |
| this._coolDown = 0.99; |
| |
| // 关闭可拖拽属性 |
| this.ondragstart = function() { |
| ondragstart.apply(self, arguments); |
| }; |
| this.ondragend = function() { |
| ondragend.apply(self, arguments); |
| }; |
| this.ondrop = function() {}; |
| this.shapeHandler.ondragstart = function() { |
| self.isDragstart = true; |
| }; |
| this.onmousemove = function() { |
| onmousemove.apply(self, arguments); |
| }; |
| this._init(); |
| } |
| |
| /** |
| * 绘制图形 |
| */ |
| Force.prototype = { |
| |
| constructor: Force, |
| |
| type : ecConfig.CHART_TYPE_FORCE, |
| |
| _init: function() { |
| var self = this; |
| |
| this.clear(); |
| |
| this._updating = true; |
| |
| this._buildShape(); |
| |
| if (this._layoutWorker) { |
| this._layoutWorker.onmessage = function(e) { |
| if (self._temperature < 0.01) { |
| requestAnimationFrame(function() { |
| self._step.call(self, e); |
| }); |
| } else { |
| self._step.call(self, e); |
| } |
| }; |
| |
| this._layoutWorker.postMessage({ |
| cmd: 'update', |
| steps: this._steps, |
| temperature: this._temperature, |
| coolDown: this._coolDown |
| }); |
| } |
| else { |
| var cb = function() { |
| if (self._updating) { |
| self._step(); |
| requestAnimationFrame(cb); |
| } |
| }; |
| |
| requestAnimationFrame(cb); |
| } |
| }, |
| |
| _buildShape: function() { |
| var legend = this.component.legend; |
| var series = this.series; |
| var serieName; |
| |
| this._temperature = 1; |
| |
| this.shapeList.length = 0; |
| |
| for (var i = 0, l = series.length; i < l; i++) { |
| var serie = series[i]; |
| if (serie.type === ecConfig.CHART_TYPE_FORCE) { |
| series[i] = this.reformOption(series[i]); |
| serieName = series[i].name || ''; |
| |
| if (workerUrl && serie.useWorker) { |
| try { |
| if (!this._layoutWorker) { |
| this._layoutWorker = new Worker(workerUrl); |
| } |
| this._layout = null; |
| } catch(e) { // IE10-11 will throw security error when using blog url |
| this._layoutWorker = null; |
| if (!this._layout) { |
| this._layout = new ForceLayout(); |
| } |
| } |
| } else { |
| if (!this._layout) { |
| this._layout = new ForceLayout(); |
| } |
| if (this._layoutWorker) { |
| this._layoutWorker.terminate(); |
| this._layoutWorker = null; |
| } |
| } |
| |
| // 系列图例开关 |
| this.selectedMap[serieName] = |
| legend ? legend.isSelected(serieName) : true; |
| if (!this.selectedMap[serieName]) { |
| continue; |
| } |
| |
| this.buildMark(i); |
| |
| // 同步selected状态 |
| var categories = serie.categories; |
| for (var j = 0, len = categories.length; j < len; j++) { |
| if (categories[j].name) { |
| if (legend){ |
| this.selectedMap[j] = |
| legend.isSelected(categories[j].name); |
| } else { |
| this.selectedMap[j] = true; |
| } |
| } |
| } |
| |
| this._preProcessData(serie); |
| |
| this._nodeShapes.length = 0; |
| this._linkShapes.length = 0; |
| |
| this._buildLinkShapes(serie); |
| this._buildNodeShapes(serie); |
| |
| this._initLayout(serie); |
| |
| this._updateLinkShapes(); |
| |
| // TODO 多个 force |
| this._forceSerie = serie; |
| break; |
| } |
| } |
| }, |
| |
| _preProcessData: function(serie) { |
| this._rawNodes = this.query(serie, 'nodes'); |
| this._rawLinks = zrUtil.clone(this.query(serie, 'links')); |
| |
| var filteredNodeList = []; |
| var filteredNodeMap = {}; |
| var cursor = 0; |
| var self = this; |
| this._filteredNodes = _filter(this._rawNodes, function (node, i) { |
| if (!node) { |
| return; |
| } |
| if (node.ignore) { |
| return; |
| } |
| var idx = -1; |
| if ( |
| typeof(node.category) == 'undefined' |
| || self.selectedMap[node.category] |
| ) { |
| idx = cursor++; |
| } |
| if (node.name) { |
| filteredNodeMap[node.name] = idx; |
| } |
| filteredNodeList[i] = idx; |
| |
| return idx >= 0; |
| }); |
| var source; |
| var target; |
| this._filteredLinks = _filter(this._rawLinks, function (link, i){ |
| source = link.source; |
| target = link.target; |
| var ret = true; |
| var idx = typeof(source) === 'string' |
| ? filteredNodeMap[source] // source 用 node id 表示 |
| : filteredNodeList[source]; // source 用 node index 表示 |
| if (typeof(idx) == 'undefined') { |
| idx = -1; |
| } |
| |
| if (idx >= 0) { |
| link.source = idx; |
| } else { |
| ret = false; |
| } |
| |
| var idx = typeof(target) === 'string' |
| ? filteredNodeMap[target] // target 用 node id 表示 |
| : filteredNodeList[target]; // target 用 node index 表示 |
| if (typeof(idx) == 'undefined') { |
| idx = -1; |
| } |
| |
| if (idx >= 0) { |
| link.target = idx; |
| } else { |
| ret = false; |
| } |
| // 保存原始链接中的index |
| link.rawIndex = i; |
| |
| return ret; |
| }); |
| }, |
| |
| _initLayout: function(serie) { |
| |
| var nodes = this._filteredNodes; |
| var links = this._filteredLinks; |
| var shapes = this._nodeShapes; |
| var len = nodes.length; |
| |
| var minRadius = this.query(serie, 'minRadius'); |
| var maxRadius = this.query(serie, 'maxRadius'); |
| this._steps = serie.steps || 1; |
| this._coolDown = serie.coolDown || 0.99; |
| |
| var center = this.parseCenter(this.zr, serie.center); |
| var size = this.parseRadius(this.zr, serie.size); |
| size = size[1] * 2; |
| |
| // 将值映射到minRadius-maxRadius的范围上 |
| var radius = []; |
| for (var i = 0; i < len; i++) { |
| var node = nodes[i]; |
| radius.push(node.value || 1); |
| } |
| |
| var arr = new NDArray(radius); |
| radius = arr.map(minRadius, maxRadius).toArray(); |
| var max = arr.max(); |
| if (max === 0) { |
| return; |
| } |
| var massArr = arr.mul(1/max, arr).toArray(); |
| var positionArr = new ArrayCtor(len * 2); |
| |
| for (var i = 0; i < len; i++) { |
| var initPos; |
| var node = nodes[i]; |
| if (typeof(this.__nodePositionMap[node.name]) !== 'undefined') { |
| initPos = vec2.create(); |
| vec2.copy(initPos, this.__nodePositionMap[node.name]); |
| } else if (typeof(node.initial) !== 'undefined') { |
| initPos = Array.prototype.slice.call(node.initial); |
| } else { |
| initPos = _randomInSquare( |
| center[0], center[1], size * 0.8 |
| ); |
| } |
| var style = shapes[i].style; |
| style.width = style.width || (radius[i] * 2); |
| style.height = style.height || (radius[i] * 2); |
| style.x = -style.width / 2; |
| style.y = -style.height / 2; |
| shapes[i].position = initPos; |
| |
| positionArr[i * 2] = initPos[0]; |
| positionArr[i * 2 + 1] = initPos[1]; |
| } |
| |
| len = links.length; |
| var edgeArr = new ArrayCtor(len * 2); |
| var edgeWeightArr = new ArrayCtor(len); |
| for (var i = 0; i < len; i++) { |
| var link = links[i]; |
| edgeArr[i * 2] = link.source; |
| edgeArr[i * 2 + 1] = link.target; |
| edgeWeightArr[i] = link.weight || 1; |
| } |
| |
| arr = new NDArray(edgeWeightArr); |
| var max = arr.max(); |
| if (max === 0) { |
| return; |
| } |
| var edgeWeightArr = arr.mul(1 / max, arr)._array; |
| |
| var config = { |
| center: center, |
| width: size, |
| height: size, |
| scaling: serie.scaling || 1.0, |
| gravity: serie.gravity || 1.0, |
| barnesHutOptimize: serie.large |
| }; |
| |
| if (this._layoutWorker) { |
| |
| this._token = getToken(); |
| |
| this._layoutWorker.postMessage({ |
| cmd: 'init', |
| nodesPosition: positionArr, |
| nodesMass: massArr, |
| nodesSize: radius, |
| edges: edgeArr, |
| edgesWeight: edgeWeightArr, |
| token: this._token |
| }); |
| |
| this._layoutWorker.postMessage({ |
| cmd: 'updateConfig', |
| config: config |
| }); |
| |
| } else { |
| |
| zrUtil.merge(this._layout, config, true); |
| this._layout.initNodes(positionArr, massArr, radius); |
| this._layout.initEdges(edgeArr, edgeWeightArr); |
| } |
| }, |
| |
| _buildNodeShapes: function(serie) { |
| var categories = this.query(serie, 'categories'); |
| var nodes = this._filteredNodes; |
| var len = nodes.length; |
| var legend = this.component.legend; |
| |
| for (var i = 0; i < len; i++) { |
| var node = nodes[i]; |
| |
| var shape = new IconShape({ |
| style : { |
| x : 0, |
| y : 0 |
| }, |
| clickable : true, |
| highlightStyle : {} |
| }); |
| |
| var queryTarget = []; |
| var shapeNormalStyle = []; |
| var shapeEmphasisStyle = []; |
| |
| queryTarget.push(node); |
| if (node.itemStyle) { |
| shapeNormalStyle.push(node.itemStyle.normal); |
| shapeEmphasisStyle.push(node.itemStyle.emphasis); |
| } |
| if (typeof(node.category) !== 'undefined') { |
| var category = categories[node.category]; |
| if (category) { |
| // 使用 Legend.getColor 配置默认 category 的默认颜色 |
| category.itemStyle = category.itemStyle || {}; |
| category.itemStyle.normal = category.itemStyle.normal || {}; |
| category.itemStyle.normal.color = category.itemStyle.normal.color |
| || legend.getColor(category.name); |
| |
| queryTarget.push(category); |
| shapeNormalStyle.unshift(category.itemStyle.normal); |
| shapeEmphasisStyle.unshift(category.itemStyle.emphasis); |
| } |
| } |
| queryTarget.push(serie); |
| shapeNormalStyle.unshift(serie.itemStyle.normal.nodeStyle); |
| shapeEmphasisStyle.unshift(serie.itemStyle.emphasis.nodeStyle); |
| |
| shape.style.iconType = this.deepQuery(queryTarget, 'symbol'); |
| // 强制设定节点大小,否则默认映射到 minRadius 到 maxRadius 后的值 |
| shape.style.width = shape.style.height |
| = (this.deepQuery(queryTarget, 'symbolSize') || 0) * 2; |
| |
| // 节点样式 |
| for (var k = 0; k < shapeNormalStyle.length; k++) { |
| if (shapeNormalStyle[k]) { |
| zrUtil.merge(shape.style, shapeNormalStyle[k], true); |
| } |
| } |
| // 节点高亮样式 |
| for (var k = 0; k < shapeEmphasisStyle.length; k++) { |
| if (shapeEmphasisStyle[k]) { |
| zrUtil.merge(shape.highlightStyle, shapeEmphasisStyle[k], true); |
| } |
| } |
| |
| // 节点标签样式 |
| if (this.deepQuery(queryTarget, 'itemStyle.normal.label.show')) { |
| shape.style.text = node.name; |
| shape.style.textPosition = 'inside'; |
| var labelStyle = this.deepQuery( |
| queryTarget, 'itemStyle.normal.label.textStyle' |
| ) || {}; |
| shape.style.textColor = labelStyle.color || '#fff'; |
| shape.style.textAlign = labelStyle.align || 'center'; |
| shape.style.textBaseline = labelStyle.baseline || 'middle'; |
| shape.style.textFont = this.getFont(labelStyle); |
| } |
| |
| if (this.deepQuery(queryTarget, 'itemStyle.emphasis.label.show')) { |
| shape.highlightStyle.text = node.name; |
| shape.highlightStyle.textPosition = 'inside'; |
| var labelStyle = this.deepQuery( |
| queryTarget, 'itemStyle.emphasis.label.textStyle' |
| ) || {}; |
| shape.highlightStyle.textColor = labelStyle.color || '#fff'; |
| shape.highlightStyle.textAlign = labelStyle.align || 'center'; |
| shape.highlightStyle.textBaseline = labelStyle.baseline || 'middle'; |
| shape.highlightStyle.textFont = this.getFont(labelStyle); |
| } |
| |
| // 拖拽特性 |
| if (this.deepQuery(queryTarget, 'draggable')) { |
| this.setCalculable(shape); |
| shape.dragEnableTime = 0; |
| shape.draggable = true; |
| shape.ondragstart = this.shapeHandler.ondragstart; |
| shape.ondragover = null; |
| } |
| |
| var categoryName = ''; |
| if (typeof(node.category) !== 'undefined') { |
| var category = categories[node.category]; |
| categoryName = (category && category.name) || ''; |
| } |
| // !!Pack data before addShape |
| ecData.pack( |
| shape, |
| // category |
| { |
| name : categoryName |
| }, |
| // series index |
| 0, |
| // data |
| node, |
| // data index |
| zrUtil.indexOf(this._rawNodes, node), |
| // name |
| node.name || '', |
| // value |
| node.value |
| ); |
| |
| this._nodeShapes.push(shape); |
| this.shapeList.push(shape); |
| this.zr.addShape(shape); |
| } |
| }, |
| |
| _buildLinkShapes: function(serie) { |
| |
| var nodes = this._filteredNodes; |
| var links = this._filteredLinks; |
| var len = links.length; |
| |
| for (var i = 0; i < len; i++) { |
| var link = links[i]; |
| var source = nodes[link.source]; |
| var target = nodes[link.target]; |
| |
| var linkShape = new LineShape({ |
| style : { |
| xStart : 0, |
| yStart : 0, |
| xEnd : 0, |
| yEnd : 0, |
| lineWidth : 1 |
| }, |
| clickable : true, |
| highlightStyle : {} |
| }); |
| |
| zrUtil.merge( |
| linkShape.style, |
| this.query(serie, 'itemStyle.normal.linkStyle'), |
| true |
| ); |
| zrUtil.merge( |
| linkShape.highlightStyle, |
| this.query(serie, 'itemStyle.emphasis.linkStyle'), |
| true |
| ); |
| if (typeof(link.itemStyle) !== 'undefined') { |
| if(link.itemStyle.normal){ |
| zrUtil.merge(linkShape.style, link.itemStyle.normal, true); |
| } |
| if(link.itemStyle.emphasis){ |
| zrUtil.merge( |
| linkShape.highlightStyle, |
| link.itemStyle.emphasis, |
| true |
| ); |
| } |
| } |
| |
| var link = this._rawLinks[link.rawIndex]; |
| ecData.pack( |
| linkShape, |
| // serie |
| serie, |
| // serie index |
| 0, |
| // link data |
| { |
| source : link.source, |
| target : link.target, |
| weight : link.weight || 0 |
| }, |
| // link data index |
| link.rawIndex, |
| // source name - target name |
| source.name + ' - ' + target.name, |
| // link weight |
| link.weight || 0, |
| // special |
| // 这一项只是为了表明这是条边 |
| true |
| ); |
| |
| this._linkShapes.push(linkShape); |
| this.shapeList.push(linkShape); |
| this.zr.addShape(linkShape); |
| |
| // Arrow shape |
| if (serie.linkSymbol && serie.linkSymbol !== 'none') { |
| var symbolShape = new IconShape({ |
| style: { |
| x: -5, |
| y: 0, |
| width: serie.linkSymbolSize[0], |
| height: serie.linkSymbolSize[1], |
| iconType: serie.linkSymbol, |
| brushType: 'fill', |
| // Use same style with link shape |
| color: linkShape.style.strokeColor, |
| opacity: linkShape.style.opacity, |
| shadowBlur: linkShape.style.shadowBlur, |
| shadowColor: linkShape.style.shadowColor, |
| shadowOffsetX: linkShape.style.shadowOffsetX, |
| shadowOffsetY: linkShape.style.shadowOffsetY |
| }, |
| highlightStyle: { |
| brushType: 'fill' |
| }, |
| position: [0, 0], |
| rotation: 0 |
| }); |
| linkShape._symbolShape = symbolShape; |
| this.shapeList.push(symbolShape); |
| this.zr.addShape(symbolShape); |
| } |
| } |
| }, |
| |
| _updateLinkShapes: function() { |
| var v = vec2.create(); |
| var links = this._filteredLinks; |
| for (var i = 0, len = links.length; i < len; i++) { |
| var link = links[i]; |
| var linkShape = this._linkShapes[i]; |
| var sourceShape = this._nodeShapes[link.source]; |
| var targetShape = this._nodeShapes[link.target]; |
| |
| linkShape.style.xStart = sourceShape.position[0]; |
| linkShape.style.yStart = sourceShape.position[1]; |
| linkShape.style.xEnd = targetShape.position[0]; |
| linkShape.style.yEnd = targetShape.position[1]; |
| |
| this.zr.modShape(linkShape.id); |
| |
| if (linkShape._symbolShape) { |
| var symbolShape = linkShape._symbolShape; |
| vec2.copy(symbolShape.position, targetShape.position); |
| |
| vec2.sub(v, sourceShape.position, targetShape.position); |
| vec2.normalize(v, v); |
| |
| vec2.scaleAndAdd( |
| symbolShape.position, symbolShape.position, |
| v, targetShape.style.width / 2 + 2 |
| ); |
| |
| var angle; |
| if (v[1] < 0) { |
| angle = 2 * Math.PI - Math.acos(-v[0]); |
| } else { |
| angle = Math.acos(-v[0]); |
| } |
| symbolShape.rotation = angle - Math.PI / 2; |
| |
| this.zr.modShape(symbolShape.id); |
| } |
| } |
| }, |
| |
| _update: function(e) { |
| |
| this._layout.temperature = this._temperature; |
| this._layout.update(); |
| |
| for (var i = 0; i < this._layout.nodes.length; i++) { |
| var position = this._layout.nodes[i].position; |
| var shape = this._nodeShapes[i]; |
| var node = this._filteredNodes[i]; |
| if (shape.fixed || (node.fixX && node.fixY)) { |
| vec2.copy(position, shape.position); |
| } else if (node.fixX) { |
| position[0] = shape.position[0]; |
| shape.position[1] = position[1]; |
| } else if (node.fixY) { |
| position[1] = shape.position[1]; |
| shape.position[0] = position[0]; |
| } else { |
| vec2.copy(shape.position, position); |
| } |
| |
| var nodeName = node.name; |
| if (nodeName) { |
| var gPos = this.__nodePositionMap[nodeName]; |
| if (!gPos) { |
| gPos = this.__nodePositionMap[nodeName] = vec2.create(); |
| } |
| vec2.copy(gPos, position); |
| } |
| } |
| |
| this._temperature *= this._coolDown; |
| }, |
| |
| _updateWorker: function(e) { |
| if (!this._updating) { |
| return; |
| } |
| |
| var positionArr = new Float32Array(e.data); |
| var token = positionArr[0]; |
| var ret = token === this._token; |
| // If token is from current layout instance |
| if (ret) { |
| var nNodes = (positionArr.length - 1) / 2; |
| |
| for (var i = 0; i < nNodes; i++) { |
| var shape = this._nodeShapes[i]; |
| var node = this._filteredNodes[i]; |
| |
| var x = positionArr[i * 2 + 1]; |
| var y = positionArr[i * 2 + 2]; |
| |
| if (shape.fixed || (node.fixX && node.fixY)) { |
| positionArr[i * 2 + 1] = shape.position[0]; |
| positionArr[i * 2 + 2] = shape.position[1]; |
| } else if (node.fixX) { |
| positionArr[i * 2 + 1] = shape.position[0]; |
| shape.position[1] = y; |
| } else if (node.fixY) { |
| positionArr[i * 2 + 2] = shape.position[1]; |
| shape.position[0] = x; |
| } else { |
| shape.position[0] = x; |
| shape.position[1] = y; |
| } |
| |
| var nodeName = node.name; |
| if (nodeName) { |
| var gPos = this.__nodePositionMap[nodeName]; |
| if (!gPos) { |
| gPos = this.__nodePositionMap[nodeName] = vec2.create(); |
| } |
| vec2.copy(gPos, shape.position); |
| } |
| } |
| |
| this._layoutWorker.postMessage(positionArr.buffer, [positionArr.buffer]); |
| } |
| |
| var self = this; |
| self._layoutWorker.postMessage({ |
| cmd: 'update', |
| steps: this._steps, |
| temperature: this._temperature, |
| coolDown: this._coolDown |
| }); |
| |
| for (var i = 0; i < this._steps; i++) { |
| this._temperature *= this._coolDown; |
| } |
| |
| return ret; |
| }, |
| |
| _step: function(e){ |
| if (this._layoutWorker) { |
| var res = this._updateWorker(e); |
| if (!res) { |
| return; |
| } |
| } else { |
| if (this._temperature < 0.01) { |
| return; |
| } |
| this._update(); |
| } |
| |
| this._updateLinkShapes(); |
| |
| for (var i = 0; i < this._nodeShapes.length; i++) { |
| this.zr.modShape(this._nodeShapes[i].id); |
| } |
| |
| this.zr.refresh(); |
| }, |
| |
| refresh: function(newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = this.option.series; |
| } |
| this.clear(); |
| this._buildShape(); |
| }, |
| |
| dispose: function(){ |
| this._updating = false; |
| this.clear(); |
| this.shapeList = null; |
| this.effectList = null; |
| |
| if (this._layoutWorker) { |
| this._layoutWorker.terminate(); |
| } |
| this._layoutWorker = null; |
| |
| this.__nodePositionMap = {}; |
| } |
| }; |
| |
| /** |
| * 拖拽开始 |
| */ |
| function ondragstart(param) { |
| if (!this.isDragstart || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| |
| var shape = param.target; |
| shape.fixed = true; |
| |
| // 处理完拖拽事件后复位 |
| this.isDragstart = false; |
| |
| this.zr.on(zrConfig.EVENT.MOUSEMOVE, this.onmousemove); |
| } |
| |
| function onmousemove() { |
| this._temperature = 0.8; |
| } |
| |
| /** |
| * 数据项被拖拽出去,重载基类方法 |
| */ |
| function ondragend(param, status) { |
| if (!this.isDragend || !param.target) { |
| // 没有在当前实例上发生拖拽行为则直接返回 |
| return; |
| } |
| var shape = param.target; |
| shape.fixed = false; |
| |
| // 别status = {}赋值啊!! |
| status.dragIn = true; |
| //你自己refresh的话把他设为false,设true就会重新调refresh接口 |
| status.needRefresh = false; |
| |
| // 处理完拖拽事件后复位 |
| this.isDragend = false; |
| |
| this.zr.un(zrConfig.EVENT.MOUSEMOVE, this.onmousemove); |
| } |
| |
| function _randomInSquare(x, y, size) { |
| return [ |
| (Math.random() - 0.5) * size + x, |
| (Math.random() - 0.5) * size + y |
| ]; |
| } |
| |
| function _filter(array, callback){ |
| var len = array.length; |
| var result = []; |
| for(var i = 0; i < len; i++){ |
| if(callback(array[i], i)){ |
| result.push(array[i]); |
| } |
| } |
| return result; |
| } |
| |
| zrUtil.inherits(Force, ChartBase); |
| zrUtil.inherits(Force, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('force', Force); |
| |
| return Force; |
| }); |
| /** |
| * zrender |
| * |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| * shape类:支持半平滑的polygon,折线面积图使用 |
| * 可配图形属性: |
| { |
| // 基础属性 |
| shape : 'halfSmoothPolygon', // 必须,shape类标识,需要显式指定 |
| id : {string}, // 必须,图形唯一标识,可通过'zrender/tool/guid'方法生成 |
| zlevel : {number}, // 默认为0,z层level,决定绘画在哪层canvas中 |
| invisible : {boolean}, // 默认为false,是否可见 |
| |
| // 样式属性,默认状态样式样式属性 |
| style : { |
| pointList : {Array}, // 必须,多边形各个顶角坐标 |
| }, |
| |
| // 样式属性,高亮样式属性,当不存在highlightStyle时使用基于默认样式扩展显示 |
| highlightStyle : { |
| // 同style |
| } |
| |
| // 交互属性,详见shape.Base |
| |
| // 事件属性,详见shape.Base |
| } |
| 例子: |
| { |
| shape : 'halfSmoothPolygon', |
| id : '123456', |
| zlevel : 1, |
| style : { |
| pointList : [[10, 10], [300, 20], [298, 400], [50, 450]] |
| color : '#eee', |
| text : 'Baidu' |
| }, |
| myName : 'kener', // 可自带任何有效自定义属性 |
| |
| clickable : true, |
| onClick : function (eventPacket) { |
| alert(eventPacket.target.myName); |
| } |
| } |
| */ |
| define('echarts/util/shape/HalfSmoothPolygon',['require','zrender/shape/Base','zrender/shape/util/smoothBezier','zrender/tool/util','zrender/shape/Polygon'],function (require) { |
| var Base = require('zrender/shape/Base'); |
| var smoothBezier = require('zrender/shape/util/smoothBezier'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| function HalfSmoothPolygon(options) { |
| Base.call(this, options); |
| } |
| |
| HalfSmoothPolygon.prototype = { |
| type : 'half-smooth-polygon', |
| /** |
| * 创建多边形路径 |
| * @param {Context2D} ctx Canvas 2D上下文 |
| * @param {Object} style 样式 |
| */ |
| buildPath : function (ctx, style) { |
| var pointList = style.pointList; |
| if (pointList.length < 2) { |
| // 少于2个点就不画了~ |
| return; |
| } |
| if (style.smooth) { |
| var controlPoints = smoothBezier( |
| pointList.slice(0, -2), style.smooth |
| ); |
| |
| ctx.moveTo(pointList[0][0], pointList[0][1]); |
| var cp1; |
| var cp2; |
| var p; |
| var l = pointList.length; |
| for (var i = 0; i < l - 3; i++) { |
| cp1 = controlPoints[i * 2]; |
| cp2 = controlPoints[i * 2 + 1]; |
| p = pointList[i + 1]; |
| ctx.bezierCurveTo( |
| cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1] |
| ); |
| } |
| ctx.lineTo(pointList[l - 2][0], pointList[l - 2][1]); |
| ctx.lineTo(pointList[l - 1][0], pointList[l - 1][1]); |
| ctx.lineTo(pointList[0][0], pointList[0][1]); |
| } |
| else { |
| require('zrender/shape/Polygon').prototype.buildPath( |
| ctx, style |
| ); |
| } |
| return; |
| } |
| }; |
| |
| zrUtil.inherits(HalfSmoothPolygon, Base); |
| |
| return HalfSmoothPolygon; |
| }); |
| /** |
| * echarts图表类:折线图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/line',['require','../component/base','./base','zrender/shape/BrokenLine','../util/shape/Icon','../util/shape/HalfSmoothPolygon','../component/axis','../component/grid','../component/dataZoom','../config','../util/ecData','zrender/tool/util','zrender/tool/color','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var BrokenLineShape = require('zrender/shape/BrokenLine'); |
| var IconShape = require('../util/shape/Icon'); |
| var HalfSmoothPolygonShape = require('../util/shape/HalfSmoothPolygon'); |
| // 组件依赖 |
| require('../component/axis'); |
| require('../component/grid'); |
| require('../component/dataZoom'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Line(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this.refresh(option); |
| } |
| |
| Line.prototype = { |
| type : ecConfig.CHART_TYPE_LINE, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| this.finalPLMap = {}; // 完成的point list(PL) |
| this._sIndex2ColorMap = {}; // series默认颜色索引,seriesIndex索引到color |
| this._symbol = this.option.symbolList; |
| this._sIndex2ShapeMap = {}; // series拐点图形类型,seriesIndex索引到shape type |
| |
| this.selectedMap = {}; |
| this.xMarkMap = {}; |
| |
| // 水平垂直双向series索引 ,position索引到seriesIndex |
| var _position2sIndexMap = { |
| top : [], |
| bottom : [], |
| left : [], |
| right : [] |
| }; |
| var xAxisIndex; |
| var yAxisIndex; |
| var xAxis; |
| var yAxis; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type == this.type) { |
| series[i] = this.reformOption(series[i]); |
| xAxisIndex = series[i].xAxisIndex; |
| yAxisIndex = series[i].yAxisIndex; |
| xAxis = this.component.xAxis.getAxis(xAxisIndex); |
| yAxis = this.component.yAxis.getAxis(yAxisIndex); |
| if (xAxis.type == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| _position2sIndexMap[xAxis.getPosition()].push(i); |
| } |
| else if (yAxis.type == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| _position2sIndexMap[yAxis.getPosition()].push(i); |
| } |
| } |
| } |
| // console.log(_position2sIndexMap); |
| for (var position in _position2sIndexMap) { |
| if (_position2sIndexMap[position].length > 0) { |
| this._buildSinglePosition( |
| position, _position2sIndexMap[position] |
| ); |
| } |
| } |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个方向上的折线图 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSinglePosition : function (position, seriesArray) { |
| var mapData = this._mapData(seriesArray); |
| var locationMap = mapData.locationMap; |
| var maxDataLength = mapData.maxDataLength; |
| |
| if (maxDataLength === 0 || locationMap.length === 0) { |
| return; |
| } |
| switch (position) { |
| case 'bottom' : |
| case 'top' : |
| this._buildHorizontal(seriesArray, maxDataLength, locationMap, this.xMarkMap); |
| break; |
| case 'left' : |
| case 'right' : |
| this._buildVertical(seriesArray, maxDataLength, locationMap, this.xMarkMap); |
| break; |
| } |
| |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| this.buildMark(seriesArray[i]); |
| } |
| }, |
| |
| /** |
| * 数据整形 |
| * 数组位置映射到系列索引 |
| */ |
| _mapData : function (seriesArray) { |
| var series = this.series; |
| var serie; // 临时映射变量 |
| var dataIndex = 0; // 堆积数据所在位置映射 |
| var stackMap = {}; // 堆积数据位置映射,堆积组在二维中的第几项 |
| var magicStackKey = '__kener__stack__'; // 堆积命名,非堆积数据安单一堆积处理 |
| var stackKey; // 临时映射变量 |
| var serieName; // 临时映射变量 |
| var legend = this.component.legend; |
| var locationMap = []; // 需要返回的东西:数组位置映射到系列索引 |
| var maxDataLength = 0; // 需要返回的东西:最大数据长度 |
| var iconShape; |
| // 计算需要显示的个数和分配位置并记在下面这个结构里 |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| serie = series[seriesArray[i]]; |
| serieName = serie.name; |
| |
| this._sIndex2ShapeMap[seriesArray[i]] |
| = this._sIndex2ShapeMap[seriesArray[i]] |
| || this.query(serie,'symbol') |
| || this._symbol[i % this._symbol.length]; |
| |
| if (legend){ |
| this.selectedMap[serieName] = legend.isSelected(serieName); |
| |
| this._sIndex2ColorMap[seriesArray[i]] |
| = legend.getColor(serieName); |
| |
| iconShape = legend.getItemShape(serieName); |
| if (iconShape) { |
| // 回调legend,换一个更形象的icon |
| iconShape.style.iconType = 'legendLineIcon'; |
| iconShape.style.symbol = |
| this._sIndex2ShapeMap[seriesArray[i]]; |
| |
| legend.setItemShape(serieName, iconShape); |
| } |
| } else { |
| this.selectedMap[serieName] = true; |
| this._sIndex2ColorMap[seriesArray[i]] |
| = this.zr.getColor(seriesArray[i]); |
| } |
| |
| if (this.selectedMap[serieName]) { |
| stackKey = serie.stack || (magicStackKey + seriesArray[i]); |
| if (typeof stackMap[stackKey] == 'undefined') { |
| stackMap[stackKey] = dataIndex; |
| locationMap[dataIndex] = [seriesArray[i]]; |
| dataIndex++; |
| } |
| else { |
| // 已经分配了位置就推进去就行 |
| locationMap[stackMap[stackKey]].push(seriesArray[i]); |
| } |
| } |
| // 兼职帮算一下最大长度 |
| maxDataLength = Math.max(maxDataLength, serie.data.length); |
| } |
| /* 调试输出 |
| var s = ''; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| s = '['; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| s +='[' |
| for (var m = 0, n = locationMap[j].length - 1; m < n; m++) { |
| s += series[locationMap[j][m]].data[i] + ',' |
| } |
| s += series[locationMap[j][locationMap[j].length - 1]] |
| .data[i]; |
| s += ']' |
| } |
| s += ']'; |
| console.log(s); |
| } |
| console.log(locationMap) |
| */ |
| |
| return { |
| locationMap : locationMap, |
| maxDataLength : maxDataLength |
| }; |
| }, |
| |
| /** |
| * 构建类目轴为水平方向的折线图系列 |
| */ |
| _buildHorizontal : function (seriesArray, maxDataLength, locationMap, xMarkMap) { |
| var series = this.series; |
| // 确定类目轴和数值轴,同一方向随便找一个即可 |
| var seriesIndex = locationMap[0][0]; |
| var serie = series[seriesIndex]; |
| var xAxisIndex = serie.xAxisIndex; |
| var categoryAxis = this.component.xAxis.getAxis(xAxisIndex); |
| var yAxisIndex; // 数值轴各异 |
| var valueAxis; // 数值轴各异 |
| |
| var x; |
| var y; |
| var lastYP; // 正向堆积处理 |
| var baseYP; |
| var lastYN; // 负向堆积处理 |
| var baseYN; |
| //var this.finalPLMap = {}; // 完成的point list(PL) |
| var curPLMap = {}; // 正在记录的point list(PL) |
| var data; |
| var value; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| if (typeof categoryAxis.getNameByIndex(i) == 'undefined') { |
| // 系列数据超出类目轴长度 |
| break; |
| } |
| x = categoryAxis.getCoordByIndex(i); |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| // 堆积数据用第一条valueAxis |
| yAxisIndex = series[locationMap[j][0]].yAxisIndex || 0; |
| valueAxis = this.component.yAxis.getAxis(yAxisIndex); |
| baseYP = lastYP = baseYN = lastYN = valueAxis.getCoord(0); |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| curPLMap[seriesIndex] = curPLMap[seriesIndex] || []; |
| xMarkMap[seriesIndex] = xMarkMap[seriesIndex] |
| || { |
| min : Number.POSITIVE_INFINITY, |
| max : Number.NEGATIVE_INFINITY, |
| sum : 0, |
| counter : 0, |
| average : 0 |
| }; |
| if (value == '-') { |
| // 空数据则把正在记录的curPLMap添加到finalPLMap中 |
| if (curPLMap[seriesIndex].length > 0) { |
| this.finalPLMap[seriesIndex] = |
| this.finalPLMap[seriesIndex] || []; |
| |
| this.finalPLMap[seriesIndex].push( |
| curPLMap[seriesIndex] |
| ); |
| |
| curPLMap[seriesIndex] = []; |
| } |
| continue; |
| } |
| //y = valueAxis.getCoord(value); |
| if (value >= 0) { |
| // 正向堆积 |
| lastYP -= m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (baseYP - valueAxis.getCoord(value)); |
| y = lastYP; |
| } |
| else if (value < 0){ |
| // 负向堆积 |
| lastYN += m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (valueAxis.getCoord(value) - baseYN); |
| y = lastYN; |
| } |
| curPLMap[seriesIndex].push( |
| [x, y, i, categoryAxis.getNameByIndex(i), x, baseYP] |
| ); |
| |
| if (xMarkMap[seriesIndex].min > value) { |
| xMarkMap[seriesIndex].min = value; |
| xMarkMap[seriesIndex].minY = y; |
| xMarkMap[seriesIndex].minX = x; |
| } |
| if (xMarkMap[seriesIndex].max < value) { |
| xMarkMap[seriesIndex].max = value; |
| xMarkMap[seriesIndex].maxY = y; |
| xMarkMap[seriesIndex].maxX = x; |
| } |
| xMarkMap[seriesIndex].sum += value; |
| xMarkMap[seriesIndex].counter++; |
| } |
| } |
| // 补充空数据的拖拽提示 |
| lastYP = this.component.grid.getY(); |
| var symbolSize; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| if (value != '-') { |
| // 只关心空数据 |
| continue; |
| } |
| if (this.deepQuery( |
| [data, serie, this.option], 'calculable' |
| ) |
| ) { |
| symbolSize = this.deepQuery( |
| [data, serie], |
| 'symbolSize' |
| ); |
| lastYP += symbolSize * 2 + 5; |
| y = lastYP; |
| this.shapeList.push(this._getCalculableItem( |
| seriesIndex, i, categoryAxis.getNameByIndex(i), |
| x, y, 'horizontal' |
| )); |
| } |
| } |
| } |
| } |
| |
| // 把剩余未完成的curPLMap全部添加到finalPLMap中 |
| for (var sId in curPLMap) { |
| if (curPLMap[sId].length > 0) { |
| this.finalPLMap[sId] = this.finalPLMap[sId] || []; |
| this.finalPLMap[sId].push(curPLMap[sId]); |
| curPLMap[sId] = []; |
| } |
| } |
| |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| if (xMarkMap[seriesIndex].counter > 0) { |
| xMarkMap[seriesIndex].average = |
| (xMarkMap[seriesIndex].sum / xMarkMap[seriesIndex].counter).toFixed(2) |
| - 0; |
| } |
| y = this.component.yAxis.getAxis(series[seriesIndex].yAxisIndex || 0) |
| .getCoord(xMarkMap[seriesIndex].average); |
| xMarkMap[seriesIndex].averageLine = [ |
| [this.component.grid.getX(), y], |
| [this.component.grid.getXend(), y] |
| ]; |
| |
| xMarkMap[seriesIndex].minLine = [ |
| [this.component.grid.getX(), xMarkMap[seriesIndex].minY], |
| [this.component.grid.getXend(), xMarkMap[seriesIndex].minY] |
| ]; |
| xMarkMap[seriesIndex].maxLine = [ |
| [this.component.grid.getX(), xMarkMap[seriesIndex].maxY], |
| [this.component.grid.getXend(), xMarkMap[seriesIndex].maxY] |
| ]; |
| } |
| } |
| |
| this._buildBorkenLine(seriesArray, this.finalPLMap, categoryAxis, 'horizontal'); |
| }, |
| |
| /** |
| * 构建类目轴为垂直方向的折线图系列 |
| */ |
| _buildVertical : function (seriesArray, maxDataLength, locationMap, xMarkMap) { |
| var series = this.series; |
| // 确定类目轴和数值轴,同一方向随便找一个即可 |
| var seriesIndex = locationMap[0][0]; |
| var serie = series[seriesIndex]; |
| var yAxisIndex = serie.yAxisIndex; |
| var categoryAxis = this.component.yAxis.getAxis(yAxisIndex); |
| var xAxisIndex; // 数值轴各异 |
| var valueAxis; // 数值轴各异 |
| |
| var x; |
| var y; |
| var lastXP; // 正向堆积处理 |
| var baseXP; |
| var lastXN; // 负向堆积处理 |
| var baseXN; |
| //var this.finalPLMap = {}; // 完成的point list(PL) |
| var curPLMap = {}; // 正在记录的point list(PL) |
| var data; |
| var value; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| if (typeof categoryAxis.getNameByIndex(i) == 'undefined') { |
| // 系列数据超出类目轴长度 |
| break; |
| } |
| y = categoryAxis.getCoordByIndex(i); |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| // 堆积数据用第一条valueAxis |
| xAxisIndex = series[locationMap[j][0]].xAxisIndex || 0; |
| valueAxis = this.component.xAxis.getAxis(xAxisIndex); |
| baseXP = lastXP = baseXN = lastXN = valueAxis.getCoord(0); |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| curPLMap[seriesIndex] = curPLMap[seriesIndex] || []; |
| xMarkMap[seriesIndex] = xMarkMap[seriesIndex] |
| || { |
| min : Number.POSITIVE_INFINITY, |
| max : Number.NEGATIVE_INFINITY, |
| sum : 0, |
| counter : 0, |
| average : 0 |
| }; |
| if (value == '-') { |
| // 空数据则把正在记录的curPLMap添加到finalPLMap中 |
| if (curPLMap[seriesIndex].length > 0) { |
| this.finalPLMap[seriesIndex] = |
| this.finalPLMap[seriesIndex] || []; |
| |
| this.finalPLMap[seriesIndex].push( |
| curPLMap[seriesIndex] |
| ); |
| |
| curPLMap[seriesIndex] = []; |
| } |
| continue; |
| } |
| //x = valueAxis.getCoord(value); |
| if (value >= 0) { |
| // 正向堆积 |
| lastXP += m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (valueAxis.getCoord(value) - baseXP); |
| x = lastXP; |
| } |
| else if (value < 0){ |
| // 负向堆积 |
| lastXN -= m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (baseXN - valueAxis.getCoord(value)); |
| x = lastXN; |
| } |
| curPLMap[seriesIndex].push( |
| [x, y, i, categoryAxis.getNameByIndex(i), baseXP, y] |
| ); |
| |
| if (xMarkMap[seriesIndex].min > value) { |
| xMarkMap[seriesIndex].min = value; |
| xMarkMap[seriesIndex].minX = x; |
| xMarkMap[seriesIndex].minY = y; |
| } |
| if (xMarkMap[seriesIndex].max < value) { |
| xMarkMap[seriesIndex].max = value; |
| xMarkMap[seriesIndex].maxX = x; |
| xMarkMap[seriesIndex].maxY = y; |
| } |
| xMarkMap[seriesIndex].sum += value; |
| xMarkMap[seriesIndex].counter++; |
| } |
| } |
| // 补充空数据的拖拽提示 |
| lastXP = this.component.grid.getXend(); |
| var symbolSize; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| if (value != '-') { |
| // 只关心空数据 |
| continue; |
| } |
| if (this.deepQuery( |
| [data, serie, this.option], 'calculable' |
| ) |
| ) { |
| symbolSize = this.deepQuery( |
| [data, serie], |
| 'symbolSize' |
| ); |
| lastXP -= symbolSize * 2 + 5; |
| x = lastXP; |
| this.shapeList.push(this._getCalculableItem( |
| seriesIndex, i, categoryAxis.getNameByIndex(i), |
| x, y, 'vertical' |
| )); |
| } |
| } |
| } |
| } |
| |
| // 把剩余未完成的curPLMap全部添加到finalPLMap中 |
| for (var sId in curPLMap) { |
| if (curPLMap[sId].length > 0) { |
| this.finalPLMap[sId] = this.finalPLMap[sId] || []; |
| this.finalPLMap[sId].push(curPLMap[sId]); |
| curPLMap[sId] = []; |
| } |
| } |
| |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| if (xMarkMap[seriesIndex].counter > 0) { |
| xMarkMap[seriesIndex].average = |
| (xMarkMap[seriesIndex].sum / xMarkMap[seriesIndex].counter).toFixed(2) |
| - 0; |
| } |
| |
| x = this.component.xAxis.getAxis(series[seriesIndex].xAxisIndex || 0) |
| .getCoord(xMarkMap[seriesIndex].average); |
| |
| xMarkMap[seriesIndex].averageLine = [ |
| [x, this.component.grid.getYend()], |
| [x, this.component.grid.getY()] |
| ]; |
| xMarkMap[seriesIndex].minLine = [ |
| [xMarkMap[seriesIndex].minX, this.component.grid.getYend()], |
| [xMarkMap[seriesIndex].minX, this.component.grid.getY()] |
| ]; |
| xMarkMap[seriesIndex].maxLine = [ |
| [xMarkMap[seriesIndex].maxX, this.component.grid.getYend()], |
| [xMarkMap[seriesIndex].maxX, this.component.grid.getY()] |
| ]; |
| } |
| } |
| |
| this._buildBorkenLine(seriesArray, this.finalPLMap, categoryAxis, 'vertical'); |
| }, |
| |
| /** |
| * 生成折线和折线上的拐点 |
| */ |
| _buildBorkenLine : function (seriesArray, pointList, categoryAxis, orient) { |
| var series = this.series; |
| var defaultColor; |
| |
| // 折线相关 |
| var lineWidth; |
| var lineType; |
| var lineColor; |
| var normalColor; |
| |
| // 填充相关 |
| var isFill; |
| var fillNormalColor; |
| |
| var serie; |
| var data; |
| var seriesPL; |
| var singlePL; |
| var brokenLineShape; |
| var halfSmoothPolygonShape; |
| |
| var isLarge; |
| |
| // 堆积层叠需求,反顺序构建 |
| var seriesIndex; |
| for (var sIdx = seriesArray.length - 1; sIdx >= 0; sIdx--) { |
| seriesIndex = seriesArray[sIdx]; |
| serie = series[seriesIndex]; |
| seriesPL = pointList[seriesIndex]; |
| if (serie.type == this.type && typeof seriesPL != 'undefined') { |
| defaultColor = this._sIndex2ColorMap[seriesIndex]; |
| // 多级控制 |
| lineWidth = this.query( |
| serie, 'itemStyle.normal.lineStyle.width' |
| ); |
| lineType = this.query( |
| serie, 'itemStyle.normal.lineStyle.type' |
| ); |
| lineColor = this.query( |
| serie, 'itemStyle.normal.lineStyle.color' |
| ); |
| normalColor = this.getItemStyleColor( |
| this.query(serie, 'itemStyle.normal.color'), seriesIndex, -1 |
| ); |
| |
| isFill = typeof this.query( |
| serie, 'itemStyle.normal.areaStyle' |
| ) != 'undefined'; |
| |
| fillNormalColor = this.query( |
| serie, 'itemStyle.normal.areaStyle.color' |
| ); |
| |
| for (var i = 0, l = seriesPL.length; i < l; i++) { |
| singlePL = seriesPL[i]; |
| isLarge = this._isLarge(orient, singlePL); |
| if (!isLarge) { // 非大数据模式才显示拐点symbol |
| for (var j = 0, k = singlePL.length; j < k; j++) { |
| data = serie.data[singlePL[j][2]]; |
| if (this.deepQuery( |
| [data, serie], 'showAllSymbol' |
| ) // 全显示 |
| || (categoryAxis.isMainAxis(singlePL[j][2]) |
| && this.deepQuery( |
| [data, serie], 'symbol' |
| ) != 'none' |
| ) // 主轴非空 |
| || this.deepQuery( |
| [data, serie, this.option], |
| 'calculable' |
| ) // 可计算 |
| ) { |
| this.shapeList.push(this._getSymbol( |
| seriesIndex, |
| singlePL[j][2], // dataIndex |
| singlePL[j][3], // name |
| singlePL[j][0], // x |
| singlePL[j][1], // y |
| orient |
| )); |
| } |
| } |
| } |
| else { |
| // 大数据模式截取pointList |
| singlePL = this._getLargePointList(orient, singlePL); |
| } |
| |
| // 折线图 |
| brokenLineShape = new BrokenLineShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| miterLimit: lineWidth, |
| pointList : singlePL, |
| strokeColor : lineColor |
| || normalColor |
| || defaultColor, |
| lineWidth : lineWidth, |
| lineType : lineType, |
| smooth : this._getSmooth(serie.smooth), |
| shadowColor : this.query( |
| serie, |
| 'itemStyle.normal.lineStyle.shadowColor' |
| ), |
| shadowBlur: this.query( |
| serie, |
| 'itemStyle.normal.lineStyle.shadowBlur' |
| ), |
| shadowOffsetX: this.query( |
| serie, |
| 'itemStyle.normal.lineStyle.shadowOffsetX' |
| ), |
| shadowOffsetY: this.query( |
| serie, |
| 'itemStyle.normal.lineStyle.shadowOffsetY' |
| ) |
| }, |
| hoverable : false, |
| _main : true, |
| _seriesIndex : seriesIndex, |
| _orient : orient |
| }); |
| |
| ecData.pack( |
| brokenLineShape, |
| series[seriesIndex], seriesIndex, |
| 0, i, series[seriesIndex].name |
| ); |
| |
| this.shapeList.push(brokenLineShape); |
| |
| if (isFill) { |
| halfSmoothPolygonShape = new HalfSmoothPolygonShape({ |
| zlevel : this._zlevelBase, |
| style : { |
| miterLimit: lineWidth, |
| pointList : zrUtil.clone(singlePL).concat([ |
| [ |
| singlePL[singlePL.length - 1][4], |
| singlePL[singlePL.length - 1][5] |
| ], |
| [ |
| singlePL[0][4], |
| singlePL[0][5] |
| ] |
| ]), |
| brushType : 'fill', |
| smooth : this._getSmooth(serie.smooth), |
| color : fillNormalColor |
| ? fillNormalColor |
| : zrColor.alpha(defaultColor,0.5) |
| }, |
| hoverable : false, |
| _main : true, |
| _seriesIndex : seriesIndex, |
| _orient : orient |
| }); |
| ecData.pack( |
| halfSmoothPolygonShape, |
| series[seriesIndex], seriesIndex, |
| 0, i, series[seriesIndex].name |
| ); |
| this.shapeList.push(halfSmoothPolygonShape); |
| } |
| } |
| } |
| } |
| }, |
| |
| _isLarge : function(orient, singlePL) { |
| if (singlePL.length < 2) { |
| return false; |
| } |
| else { |
| return orient == 'horizontal' |
| ? (Math.abs(singlePL[0][0] - singlePL[1][0]) < 0.5) |
| : (Math.abs(singlePL[0][1] - singlePL[1][1]) < 0.5); |
| } |
| }, |
| |
| /** |
| * 大规模pointList优化 |
| */ |
| _getLargePointList : function(orient, singlePL) { |
| var total; |
| if (orient == 'horizontal') { |
| total = this.component.grid.getWidth(); |
| } |
| else { |
| total = this.component.grid.getHeight(); |
| } |
| |
| var len = singlePL.length; |
| var newList = []; |
| for (var i = 0; i < total; i++) { |
| newList[i] = singlePL[Math.floor(len / total * i)]; |
| } |
| return newList; |
| }, |
| |
| _getSmooth : function (isSmooth/*, pointList, orient*/) { |
| if (isSmooth) { |
| /* 不科学啊,发现0.3通用了 |
| var delta; |
| if (orient == 'horizontal') { |
| delta = Math.abs(pointList[0][0] - pointList[1][0]); |
| } |
| else { |
| delta = Math.abs(pointList[0][1] - pointList[1][1]); |
| } |
| */ |
| return 0.3; |
| } |
| else { |
| return 0; |
| } |
| }, |
| |
| /** |
| * 生成空数据所需的可计算提示图形 |
| */ |
| _getCalculableItem : function (seriesIndex, dataIndex, name, x, y, orient) { |
| var series = this.series; |
| var color = series[seriesIndex].calculableHolderColor |
| || this.ecTheme.calculableHolderColor; |
| |
| var itemShape = this._getSymbol( |
| seriesIndex, dataIndex, name, |
| x, y, orient |
| ); |
| itemShape.style.color = color; |
| itemShape.style.strokeColor = color; |
| itemShape.rotation = [0,0]; |
| itemShape.hoverable = false; |
| itemShape.draggable = false; |
| itemShape.style.text = undefined; |
| |
| return itemShape; |
| }, |
| |
| /** |
| * 生成折线图上的拐点图形 |
| */ |
| _getSymbol : function (seriesIndex, dataIndex, name, x, y, orient) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| |
| var itemShape = this.getSymbolShape( |
| serie, seriesIndex, data, dataIndex, name, |
| x, y, |
| this._sIndex2ShapeMap[seriesIndex], |
| this._sIndex2ColorMap[seriesIndex], |
| '#fff', |
| orient == 'vertical' ? 'horizontal' : 'vertical' // 翻转 |
| ); |
| itemShape.zlevel = this._zlevelBase + 1; |
| |
| if (this.deepQuery([data, serie, this.option], 'calculable')) { |
| this.setCalculable(itemShape); |
| itemShape.draggable = true; |
| } |
| |
| return itemShape; |
| }, |
| |
| // 位置转换 |
| getMarkCoord : function (seriesIndex, mpData) { |
| var serie = this.series[seriesIndex]; |
| var xMarkMap = this.xMarkMap[seriesIndex]; |
| var xAxis = this.component.xAxis.getAxis(serie.xAxisIndex); |
| var yAxis = this.component.yAxis.getAxis(serie.yAxisIndex); |
| |
| if (mpData.type |
| && (mpData.type == 'max' || mpData.type == 'min' || mpData.type == 'average') |
| ) { |
| // 特殊值内置支持 |
| return [ |
| xMarkMap[mpData.type + 'X'], |
| xMarkMap[mpData.type + 'Y'], |
| xMarkMap[mpData.type + 'Line'], |
| xMarkMap[mpData.type] |
| ]; |
| } |
| |
| return [ |
| typeof mpData.xAxis != 'string' && xAxis.getCoordByIndex |
| ? xAxis.getCoordByIndex(mpData.xAxis || 0) |
| : xAxis.getCoord(mpData.xAxis || 0), |
| |
| typeof mpData.yAxis != 'string' && yAxis.getCoordByIndex |
| ? yAxis.getCoordByIndex(mpData.yAxis || 0) |
| : yAxis.getCoord(mpData.yAxis || 0) |
| ]; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| }, |
| |
| ontooltipHover : function (param, tipShape) { |
| var seriesIndex = param.seriesIndex; |
| var dataIndex = param.dataIndex; |
| var seriesPL; |
| var singlePL; |
| var len = seriesIndex.length; |
| while (len--) { |
| seriesPL = this.finalPLMap[seriesIndex[len]]; |
| if (seriesPL) { |
| for (var i = 0, l = seriesPL.length; i < l; i++) { |
| singlePL = seriesPL[i]; |
| for (var j = 0, k = singlePL.length; j < k; j++) { |
| if (dataIndex == singlePL[j][2]) { |
| tipShape.push(this._getSymbol( |
| seriesIndex[len], // seriesIndex |
| singlePL[j][2], // dataIndex |
| singlePL[j][3], // name |
| singlePL[j][0], // x |
| singlePL[j][1], // y |
| 'horizontal' |
| )); |
| } |
| } |
| } |
| } |
| } |
| }, |
| |
| /** |
| * 动态数据增加动画 |
| */ |
| addDataAnimation : function (params) { |
| var series = this.series; |
| var aniMap = {}; // seriesIndex索引参数 |
| for (var i = 0, l = params.length; i < l; i++) { |
| aniMap[params[i][0]] = params[i]; |
| } |
| var x; |
| var dx; |
| var y; |
| var dy; |
| var seriesIndex; |
| var pointList; |
| var isHorizontal; // 是否横向布局, isHorizontal; |
| for (var i = this.shapeList.length - 1; i >= 0; i--) { |
| seriesIndex = this.shapeList[i]._seriesIndex; |
| if (aniMap[seriesIndex] && !aniMap[seriesIndex][3]) { |
| // 有数据删除才有移动的动画 |
| if (this.shapeList[i]._main && this.shapeList[i].style.pointList.length > 1) { |
| pointList = this.shapeList[i].style.pointList; |
| // 主线动画 |
| dx = Math.abs(pointList[0][0] - pointList[1][0]); |
| dy = Math.abs(pointList[0][1] - pointList[1][1]); |
| isHorizontal = |
| this.shapeList[i]._orient == 'horizontal'; |
| |
| if (aniMap[seriesIndex][2]) { |
| // 队头加入删除末尾 |
| if (this.shapeList[i].type == 'polygon') { |
| //区域图 |
| var len = pointList.length; |
| this.shapeList[i].style.pointList[len - 3] |
| = pointList[len - 2]; |
| isHorizontal |
| ? (this.shapeList[i].style.pointList[len - 3][0] |
| = pointList[len - 4][0] |
| ) |
| : (this.shapeList[i].style.pointList[len - 3][1] |
| = pointList[len - 4][1] |
| ); |
| this.shapeList[i].style.pointList[len - 2] |
| = pointList[len - 1]; |
| } |
| this.shapeList[i].style.pointList.pop(); |
| |
| isHorizontal ? (x = dx, y = 0) : (x = 0, y = -dy); |
| } |
| else { |
| // 队尾加入删除头部 |
| this.shapeList[i].style.pointList.shift(); |
| if (this.shapeList[i].type == 'polygon') { |
| //区域图 |
| var targetPoint = |
| this.shapeList[i].style.pointList.pop(); |
| isHorizontal |
| ? (targetPoint[0] = pointList[0][0]) |
| : (targetPoint[1] = pointList[0][1]); |
| this.shapeList[i].style.pointList.push( |
| targetPoint |
| ); |
| } |
| isHorizontal ? (x = -dx, y = 0) : (x = 0, y = dy); |
| } |
| |
| this.zr.modShape( |
| this.shapeList[i].id, |
| { |
| style : { |
| pointList: this.shapeList[i].style.pointList |
| } |
| }, |
| true |
| ); |
| } |
| else { |
| // 拐点动画 |
| if (aniMap[seriesIndex][2] |
| && this.shapeList[i]._dataIndex |
| == series[seriesIndex].data.length - 1 |
| ) { |
| // 队头加入删除末尾 |
| this.zr.delShape(this.shapeList[i].id); |
| continue; |
| } |
| else if (!aniMap[seriesIndex][2] |
| && this.shapeList[i]._dataIndex === 0 |
| ) { |
| // 队尾加入删除头部 |
| this.zr.delShape(this.shapeList[i].id); |
| continue; |
| } |
| } |
| this.shapeList[i].position = [0, 0]; |
| this.zr.animate(this.shapeList[i].id, '') |
| .when( |
| 500, |
| {position : [x, y]} |
| ) |
| .start(); |
| } |
| } |
| } |
| }; |
| |
| function legendLineIcon(ctx, style) { |
| var x = style.x; |
| var y = style.y; |
| var width = style.width; |
| var height = style.height; |
| |
| var dy = height / 2; |
| |
| if (style.symbol.match('empty')) { |
| ctx.fillStyle = '#fff'; |
| } |
| style.brushType = 'both'; |
| |
| var symbol = style.symbol.replace('empty', '').toLowerCase(); |
| if (symbol.match('star')) { |
| dy = (symbol.replace('star','') - 0) || 5; |
| y -= 1; |
| symbol = 'star'; |
| } |
| else if (symbol == 'rectangle' || symbol == 'arrow') { |
| x += (width - height) / 2; |
| width = height; |
| } |
| |
| var imageLocation = ''; |
| if (symbol.match('image')) { |
| imageLocation = symbol.replace( |
| new RegExp('^image:\\/\\/'), '' |
| ); |
| symbol = 'image'; |
| x += Math.round((width - height) / 2) - 1; |
| width = height = height + 2; |
| } |
| symbol = IconShape.prototype.iconLibrary[symbol]; |
| |
| if (symbol) { |
| var x2 = style.x; |
| var y2 = style.y; |
| ctx.moveTo(x2, y2 + dy); |
| ctx.lineTo(x2 + 5, y2 + dy); |
| ctx.moveTo(x2 + style.width - 5, y2 + dy); |
| ctx.lineTo(x2 + style.width, y2 + dy); |
| |
| symbol(ctx, { |
| x : x + 4, |
| y : y + 4, |
| width : width - 8, |
| height : height - 8, |
| n : dy, |
| image : imageLocation |
| }); |
| |
| } |
| else { |
| ctx.moveTo(x, y + dy); |
| ctx.lineTo(x + width, y + dy); |
| } |
| } |
| IconShape.prototype.iconLibrary['legendLineIcon'] = legendLineIcon; |
| |
| zrUtil.inherits(Line, ChartBase); |
| zrUtil.inherits(Line, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('line', Line); |
| |
| return Line; |
| }); |
| /** |
| * echarts图表类:柱形图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/bar',['require','../component/base','./base','zrender/shape/Rectangle','../component/axis','../component/grid','../component/dataZoom','../config','../util/ecData','zrender/tool/util','zrender/tool/color','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| // 组件依赖 |
| require('../component/axis'); |
| require('../component/grid'); |
| require('../component/dataZoom'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Bar(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| this.refresh(option); |
| } |
| |
| Bar.prototype = { |
| type : ecConfig.CHART_TYPE_BAR, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| this.selectedMap = {}; |
| this.xMarkMap = {}; |
| |
| // series默认颜色索引,seriesIndex索引到color |
| this._sIndex2colorMap = {}; |
| |
| // 水平垂直双向series索引 ,position索引到seriesIndex |
| var _position2sIndexMap = { |
| top : [], |
| bottom : [], |
| left : [], |
| right : [] |
| }; |
| var xAxisIndex; |
| var yAxisIndex; |
| var xAxis; |
| var yAxis; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type == ecConfig.CHART_TYPE_BAR) { |
| series[i] = this.reformOption(series[i]); |
| xAxisIndex = series[i].xAxisIndex; |
| yAxisIndex = series[i].yAxisIndex; |
| xAxis = this.component.xAxis.getAxis(xAxisIndex); |
| yAxis = this.component.yAxis.getAxis(yAxisIndex); |
| if (xAxis.type == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| _position2sIndexMap[xAxis.getPosition()].push(i); |
| } |
| else if (yAxis.type == ecConfig.COMPONENT_TYPE_AXIS_CATEGORY |
| ) { |
| _position2sIndexMap[yAxis.getPosition()].push(i); |
| } |
| } |
| } |
| // console.log(_position2sIndexMap) |
| for (var position in _position2sIndexMap) { |
| if (_position2sIndexMap[position].length > 0) { |
| this._buildSinglePosition( |
| position, _position2sIndexMap[position], this.xMarkMap |
| ); |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个方向上的柱形图 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSinglePosition : function (position, seriesArray, xMarkMap) { |
| var mapData = this._mapData(seriesArray); |
| var locationMap = mapData.locationMap; |
| var maxDataLength = mapData.maxDataLength; |
| |
| if (maxDataLength === 0 || locationMap.length === 0) { |
| return; |
| } |
| |
| switch (position) { |
| case 'bottom' : |
| case 'top' : |
| this._buildHorizontal(maxDataLength, locationMap, seriesArray, xMarkMap); |
| break; |
| case 'left' : |
| case 'right' : |
| this._buildVertical(maxDataLength, locationMap, seriesArray, xMarkMap); |
| break; |
| } |
| }, |
| |
| /** |
| * 数据整形 |
| * 数组位置映射到系列索引 |
| */ |
| _mapData : function (seriesArray) { |
| var series = this.series; |
| var serie; // 临时映射变量 |
| var dataIndex = 0; // 堆积数据所在位置映射 |
| var stackMap = {}; // 堆积数据位置映射,堆积组在二维中的第几项 |
| var magicStackKey = '__kener__stack__'; // 堆积命名,非堆积数据安单一堆积处理 |
| var stackKey; // 临时映射变量 |
| var serieName; // 临时映射变量 |
| var legend = this.component.legend; |
| var locationMap = []; // 需要返回的东西:数组位置映射到系列索引 |
| var maxDataLength = 0; // 需要返回的东西:最大数据长度 |
| var iconShape; |
| // 计算需要显示的个数和分配位置并记在下面这个结构里 |
| for (var i = 0, l = seriesArray.length; i < l; i++) { |
| serie = series[seriesArray[i]]; |
| serieName = serie.name; |
| if (legend){ |
| this.selectedMap[serieName] = legend.isSelected(serieName); |
| this._sIndex2colorMap[seriesArray[i]] = |
| legend.getColor(serieName); |
| |
| iconShape = legend.getItemShape(serieName); |
| if (iconShape) { |
| // 回调legend,换一个更形象的icon |
| if (serie.itemStyle.normal.borderWidth > 0) { |
| iconShape.style.x += 1; |
| iconShape.style.y += 1; |
| iconShape.style.width -= 2; |
| iconShape.style.height -= 2; |
| iconShape.style.strokeColor = |
| iconShape.highlightStyle.strokeColor = |
| serie.itemStyle.normal.borderColor; |
| iconShape.highlightStyle.lineWidth = 3; |
| iconShape.style.brushType = 'both'; |
| } |
| legend.setItemShape(serieName, iconShape); |
| } |
| } else { |
| this.selectedMap[serieName] = true; |
| this._sIndex2colorMap[seriesArray[i]] = |
| this.zr.getColor(seriesArray[i]); |
| } |
| |
| if (this.selectedMap[serieName]) { |
| stackKey = serie.stack || (magicStackKey + seriesArray[i]); |
| if (typeof stackMap[stackKey] == 'undefined') { |
| stackMap[stackKey] = dataIndex; |
| locationMap[dataIndex] = [seriesArray[i]]; |
| dataIndex++; |
| } |
| else { |
| // 已经分配了位置就推进去就行 |
| locationMap[stackMap[stackKey]].push(seriesArray[i]); |
| } |
| } |
| // 兼职帮算一下最大长度 |
| maxDataLength = Math.max(maxDataLength, serie.data.length); |
| } |
| |
| /* 调试输出 |
| var s = ''; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| s = '['; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| s +='[' |
| for (var m = 0, n = locationMap[j].length - 1; m < n; m++) { |
| s += series[locationMap[j][m]].data[i] + ',' |
| } |
| s += series[locationMap[j][locationMap[j].length - 1]] |
| .data[i]; |
| s += ']' |
| } |
| s += ']'; |
| console.log(s); |
| } |
| console.log(locationMap) |
| */ |
| |
| return { |
| locationMap : locationMap, |
| maxDataLength : maxDataLength |
| }; |
| }, |
| |
| /** |
| * 构建类目轴为水平方向的柱形图系列 |
| */ |
| _buildHorizontal : function (maxDataLength, locationMap, seriesArray, xMarkMap) { |
| var series = this.series; |
| // 确定类目轴和数值轴,同一方向随便找一个即可 |
| var seriesIndex = locationMap[0][0]; |
| var serie = series[seriesIndex]; |
| var xAxisIndex = serie.xAxisIndex; |
| var categoryAxis = this.component.xAxis.getAxis(xAxisIndex); |
| var yAxisIndex; // 数值轴各异 |
| var valueAxis; // 数值轴各异 |
| |
| var size = this._mapSize(categoryAxis, locationMap); |
| var gap = size.gap; |
| var barGap = size.barGap; |
| var barWidthMap = size.barWidthMap; |
| var barWidth = size.barWidth; // 自适应宽度 |
| var barMinHeightMap = size.barMinHeightMap; |
| var barHeight; |
| var interval = size.interval; |
| |
| var x; |
| var y; |
| var lastYP; // 正向堆积处理 |
| var baseYP; |
| var lastYN; // 负向堆积处理 |
| var baseYN; |
| var barShape; |
| var data; |
| var value; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| if (typeof categoryAxis.getNameByIndex(i) == 'undefined') { |
| // 系列数据超出类目轴长度 |
| break; |
| } |
| x = categoryAxis.getCoordByIndex(i) - gap / 2; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| // 堆积数据用第一条valueAxis |
| yAxisIndex = series[locationMap[j][0]].yAxisIndex || 0; |
| valueAxis = this.component.yAxis.getAxis(yAxisIndex); |
| baseYP = lastYP = baseYN = lastYN = valueAxis.getCoord(0); |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| xMarkMap[seriesIndex] = xMarkMap[seriesIndex] |
| || { |
| min : Number.POSITIVE_INFINITY, |
| max : Number.NEGATIVE_INFINITY, |
| sum : 0, |
| counter : 0, |
| average : 0 |
| }; |
| if (value == '-') { |
| // 空数据在做完后补充拖拽提示框 |
| continue; |
| } |
| //y = valueAxis.getCoord(value); |
| if (value > 0) { |
| // 正向堆积 |
| //barHeight = baseYP - y; |
| barHeight = m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (baseYP - valueAxis.getCoord(value)); |
| // 非堆积数据最小高度有效 |
| if (n == 1 |
| && barMinHeightMap[seriesIndex] > barHeight |
| ) { |
| barHeight = barMinHeightMap[seriesIndex]; |
| } |
| lastYP -= barHeight; |
| y = lastYP; |
| } |
| else if (value < 0){ |
| // 负向堆积 |
| //barHeight = y - baseYN; |
| barHeight = m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (valueAxis.getCoord(value) - baseYN); |
| // 非堆积数据最小高度有效 |
| if (n == 1 |
| && barMinHeightMap[seriesIndex] > barHeight |
| ) { |
| barHeight = barMinHeightMap[seriesIndex]; |
| } |
| y = lastYN; |
| lastYN += barHeight; |
| } |
| else { |
| // 0值 |
| barHeight = 0;//baseYP - y; |
| // 最小高度无效 |
| lastYP -= barHeight; |
| y = lastYP; |
| } |
| xMarkMap[seriesIndex][i] = |
| x + (barWidthMap[seriesIndex] || barWidth) / 2; |
| if (xMarkMap[seriesIndex].min > value) { |
| xMarkMap[seriesIndex].min = value; |
| xMarkMap[seriesIndex].minY = y; |
| xMarkMap[seriesIndex].minX = xMarkMap[seriesIndex][i]; |
| } |
| if (xMarkMap[seriesIndex].max < value) { |
| xMarkMap[seriesIndex].max = value; |
| xMarkMap[seriesIndex].maxY = y; |
| xMarkMap[seriesIndex].maxX = xMarkMap[seriesIndex][i]; |
| } |
| xMarkMap[seriesIndex].sum += value; |
| xMarkMap[seriesIndex].counter++; |
| |
| if (i % interval === 0) { |
| barShape = this._getBarItem( |
| seriesIndex, i, |
| categoryAxis.getNameByIndex(i), |
| x, y, |
| barWidthMap[seriesIndex] || barWidth, |
| barHeight, |
| 'vertical' |
| ); |
| this.shapeList.push(new RectangleShape(barShape)); |
| } |
| } |
| |
| // 补充空数据的拖拽提示框 |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| if (value != '-') { |
| // 只关心空数据 |
| continue; |
| } |
| |
| if (this.deepQuery( |
| [data, serie, this.option], 'calculable' |
| ) |
| ) { |
| lastYP -= this.ecTheme.island.r; |
| y = lastYP; |
| |
| barShape = this._getBarItem( |
| seriesIndex, i, |
| categoryAxis.getNameByIndex(i), |
| x + 0.5, y + 0.5, |
| (barWidthMap[seriesIndex] || barWidth) - 1, |
| this.ecTheme.island.r - 1, |
| 'vertical' |
| ); |
| barShape.hoverable = false; |
| barShape.draggable = false; |
| barShape.style.lineWidth = 1; |
| barShape.style.brushType = 'stroke'; |
| barShape.style.strokeColor = |
| serie.calculableHolderColor |
| || this.ecTheme.calculableHolderColor; |
| |
| this.shapeList.push(new RectangleShape(barShape)); |
| } |
| } |
| |
| x += ((barWidthMap[seriesIndex] || barWidth) + barGap); |
| } |
| } |
| |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| if (xMarkMap[seriesIndex].counter > 0) { |
| xMarkMap[seriesIndex].average = |
| (xMarkMap[seriesIndex].sum / xMarkMap[seriesIndex].counter).toFixed(2) |
| - 0; |
| } |
| |
| y = this.component.yAxis.getAxis(series[seriesIndex].yAxisIndex || 0) |
| .getCoord(xMarkMap[seriesIndex].average); |
| |
| xMarkMap[seriesIndex].averageLine = [ |
| [this.component.grid.getX(), y], |
| [this.component.grid.getXend(), y] |
| ]; |
| xMarkMap[seriesIndex].minLine = [ |
| [this.component.grid.getX(), xMarkMap[seriesIndex].minY], |
| [this.component.grid.getXend(), xMarkMap[seriesIndex].minY] |
| ]; |
| xMarkMap[seriesIndex].maxLine = [ |
| [this.component.grid.getX(), xMarkMap[seriesIndex].maxY], |
| [this.component.grid.getXend(), xMarkMap[seriesIndex].maxY] |
| ]; |
| |
| xMarkMap[seriesIndex].isHorizontal = true; |
| this.buildMark(seriesIndex); |
| } |
| } |
| }, |
| |
| /** |
| * 构建类目轴为垂直方向的柱形图系列 |
| */ |
| _buildVertical : function (maxDataLength, locationMap, seriesArray, xMarkMap) { |
| var series = this.series; |
| // 确定类目轴和数值轴,同一方向随便找一个即可 |
| var seriesIndex = locationMap[0][0]; |
| var serie = series[seriesIndex]; |
| var yAxisIndex = serie.yAxisIndex; |
| var categoryAxis = this.component.yAxis.getAxis(yAxisIndex); |
| var xAxisIndex; // 数值轴各异 |
| var valueAxis; // 数值轴各异 |
| |
| var size = this._mapSize(categoryAxis, locationMap); |
| var gap = size.gap; |
| var barGap = size.barGap; |
| var barWidthMap = size.barWidthMap; |
| var barWidth = size.barWidth; // 自适应宽度 |
| var barMinHeightMap = size.barMinHeightMap; |
| var barHeight; |
| var interval = size.interval; |
| |
| var x; |
| var y; |
| var lastXP; // 正向堆积处理 |
| var baseXP; |
| var lastXN; // 负向堆积处理 |
| var baseXN; |
| var barShape; |
| var data; |
| var value; |
| for (var i = 0, l = maxDataLength; i < l; i++) { |
| if (typeof categoryAxis.getNameByIndex(i) == 'undefined') { |
| // 系列数据超出类目轴长度 |
| break; |
| } |
| y = categoryAxis.getCoordByIndex(i) + gap / 2; |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| // 堆积数据用第一条valueAxis |
| xAxisIndex = series[locationMap[j][0]].xAxisIndex || 0; |
| valueAxis = this.component.xAxis.getAxis(xAxisIndex); |
| baseXP = lastXP = baseXN = lastXN = valueAxis.getCoord(0); |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| xMarkMap[seriesIndex] = xMarkMap[seriesIndex] |
| || { |
| min : Number.POSITIVE_INFINITY, |
| max : Number.NEGATIVE_INFINITY, |
| sum : 0, |
| counter : 0, |
| average : 0 |
| }; |
| if (value == '-') { |
| // 空数据在做完后补充拖拽提示框 |
| continue; |
| } |
| //x = valueAxis.getCoord(value); |
| if (value > 0) { |
| // 正向堆积 |
| //barHeight = x - baseXP; |
| barHeight = m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (valueAxis.getCoord(value) - baseXP); |
| // 非堆积数据最小高度有效 |
| if (n == 1 |
| && barMinHeightMap[seriesIndex] > barHeight |
| ) { |
| barHeight = barMinHeightMap[seriesIndex]; |
| } |
| x = lastXP; |
| lastXP += barHeight; |
| } |
| else if (value < 0){ |
| // 负向堆积 |
| //barHeight = baseXN - x; |
| barHeight = m > 0 |
| ? valueAxis.getCoordSize(value) |
| : (baseXN - valueAxis.getCoord(value)); |
| // 非堆积数据最小高度有效 |
| if (n == 1 |
| && barMinHeightMap[seriesIndex] > barHeight |
| ) { |
| barHeight = barMinHeightMap[seriesIndex]; |
| } |
| lastXN -= barHeight; |
| x = lastXN; |
| } |
| else { |
| // 0值 |
| barHeight = 0;//x - baseXP; |
| // 最小高度无效 |
| x = lastXP; |
| lastXP += barHeight; |
| } |
| |
| xMarkMap[seriesIndex][i] = |
| y - (barWidthMap[seriesIndex] || barWidth) / 2; |
| if (xMarkMap[seriesIndex].min > value) { |
| xMarkMap[seriesIndex].min = value; |
| xMarkMap[seriesIndex].minX = x + barHeight; |
| xMarkMap[seriesIndex].minY = xMarkMap[seriesIndex][i]; |
| } |
| if (xMarkMap[seriesIndex].max < value) { |
| xMarkMap[seriesIndex].max = value; |
| xMarkMap[seriesIndex].maxX = x + barHeight; |
| xMarkMap[seriesIndex].maxY = xMarkMap[seriesIndex][i]; |
| } |
| xMarkMap[seriesIndex].sum += value; |
| xMarkMap[seriesIndex].counter++; |
| |
| if (i % interval === 0) { |
| barShape = this._getBarItem( |
| seriesIndex, i, |
| categoryAxis.getNameByIndex(i), |
| x, y - (barWidthMap[seriesIndex] || barWidth), |
| barHeight, |
| barWidthMap[seriesIndex] || barWidth, |
| 'horizontal' |
| ); |
| this.shapeList.push(new RectangleShape(barShape)); |
| } |
| } |
| |
| // 补充空数据的拖拽提示框 |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| serie = series[seriesIndex]; |
| data = serie.data[i]; |
| value = typeof data != 'undefined' |
| ? (typeof data.value != 'undefined' |
| ? data.value |
| : data) |
| : '-'; |
| if (value != '-') { |
| // 只关心空数据 |
| continue; |
| } |
| |
| if (this.deepQuery( |
| [data, serie, this.option], 'calculable' |
| ) |
| ) { |
| x = lastXP; |
| lastXP += this.ecTheme.island.r; |
| |
| barShape = this._getBarItem( |
| seriesIndex, |
| i, |
| categoryAxis.getNameByIndex(i), |
| x + 0.5, y + 0.5 - (barWidthMap[seriesIndex] || barWidth), |
| this.ecTheme.island.r - 1, |
| (barWidthMap[seriesIndex] || barWidth) - 1, |
| 'horizontal' |
| ); |
| barShape.hoverable = false; |
| barShape.draggable = false; |
| barShape.style.lineWidth = 1; |
| barShape.style.brushType = 'stroke'; |
| barShape.style.strokeColor = |
| serie.calculableHolderColor |
| || this.ecTheme.calculableHolderColor; |
| |
| this.shapeList.push(new RectangleShape(barShape)); |
| } |
| } |
| |
| y -= ((barWidthMap[seriesIndex] || barWidth) + barGap); |
| } |
| } |
| |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| if (xMarkMap[seriesIndex].counter > 0) { |
| xMarkMap[seriesIndex].average = |
| (xMarkMap[seriesIndex].sum / xMarkMap[seriesIndex].counter).toFixed(2) |
| - 0; |
| } |
| |
| x = this.component.xAxis.getAxis(series[seriesIndex].xAxisIndex || 0) |
| .getCoord(xMarkMap[seriesIndex].average); |
| |
| xMarkMap[seriesIndex].averageLine = [ |
| [x, this.component.grid.getYend()], |
| [x, this.component.grid.getY()] |
| ]; |
| xMarkMap[seriesIndex].minLine = [ |
| [xMarkMap[seriesIndex].minX, this.component.grid.getYend()], |
| [xMarkMap[seriesIndex].minX, this.component.grid.getY()] |
| ]; |
| xMarkMap[seriesIndex].maxLine = [ |
| [xMarkMap[seriesIndex].maxX, this.component.grid.getYend()], |
| [xMarkMap[seriesIndex].maxX, this.component.grid.getY()] |
| ]; |
| |
| xMarkMap[seriesIndex].isHorizontal = false; |
| this.buildMark(seriesIndex); |
| } |
| } |
| }, |
| |
| /** |
| * 我真是自找麻烦啊,为啥要允许系列级个性化最小宽度和高度啊!!! |
| * @param {CategoryAxis} categoryAxis 类目坐标轴,需要知道类目间隔大小 |
| * @param {Array} locationMap 整形数据的系列索引 |
| */ |
| _mapSize : function (categoryAxis, locationMap, ignoreUserDefined) { |
| var series = this.series; |
| var seriesIndex; |
| var barWidthMap = {}; |
| var barMinHeightMap = {}; |
| var sBarWidth; |
| var sBarWidthCounter = 0; |
| var sBarWidthTotal = 0; |
| var barGap; |
| var barCategoryGap; |
| var hasFound; |
| var queryTarget; |
| var interval = 1; |
| |
| for (var j = 0, k = locationMap.length; j < k; j++) { |
| hasFound = false; // 同一堆积第一个barWidth生效 |
| for (var m = 0, n = locationMap[j].length; m < n; m++) { |
| seriesIndex = locationMap[j][m]; |
| queryTarget = series[seriesIndex]; |
| if (!ignoreUserDefined) { |
| if (!hasFound) { |
| sBarWidth = this.query( |
| queryTarget, |
| 'barWidth' |
| ); |
| if (typeof sBarWidth != 'undefined') { |
| // 同一堆积第一个生效barWidth |
| barWidthMap[seriesIndex] = sBarWidth; |
| sBarWidthTotal += sBarWidth; |
| sBarWidthCounter++; |
| hasFound = true; |
| // 复位前面同一堆积但没被定义的 |
| for (var ii = 0, ll = m; ii < ll; ii++) { |
| var pSeriesIndex = locationMap[j][ii]; |
| barWidthMap[pSeriesIndex] = sBarWidth; |
| } |
| } |
| } else { |
| barWidthMap[seriesIndex] = sBarWidth; // 用找到的一个 |
| } |
| } |
| |
| barMinHeightMap[seriesIndex] = this.query( |
| queryTarget, |
| 'barMinHeight' |
| ); |
| barGap = typeof barGap != 'undefined' |
| ? barGap |
| : this.query( |
| queryTarget, |
| 'barGap' |
| ); |
| barCategoryGap = typeof barCategoryGap != 'undefined' |
| ? barCategoryGap |
| : this.query( |
| queryTarget, |
| 'barCategoryGap' |
| ); |
| } |
| } |
| |
| var gap; |
| var barWidth; |
| if (locationMap.length != sBarWidthCounter) { |
| // 至少存在一个自适应宽度的柱形图 |
| if (!ignoreUserDefined) { |
| gap = typeof barCategoryGap == 'string' |
| && barCategoryGap.match(/%$/) |
| // 百分比 |
| ? Math.floor( |
| categoryAxis.getGap() |
| * (100 - parseFloat(barCategoryGap)) |
| / 100 |
| ) |
| // 数值 |
| : (categoryAxis.getGap() - barCategoryGap); |
| if (typeof barGap == 'string' && barGap.match(/%$/)) { |
| barGap = parseFloat(barGap) / 100; |
| barWidth = Math.floor( |
| (gap - sBarWidthTotal) |
| / ((locationMap.length - 1) * barGap |
| + locationMap.length - sBarWidthCounter) |
| ); |
| barGap = Math.floor(barWidth * barGap); |
| } |
| else { |
| barGap = parseFloat(barGap); |
| barWidth = Math.floor( |
| (gap - sBarWidthTotal |
| - barGap * (locationMap.length - 1) |
| ) |
| / (locationMap.length - sBarWidthCounter) |
| ); |
| } |
| // 无法满足用户定义的宽度设计,忽略用户宽度,打回重做 |
| if (barWidth <= 0) { |
| return this._mapSize(categoryAxis, locationMap, true); |
| } |
| } |
| else { |
| // 忽略用户定义的宽度设定 |
| gap = categoryAxis.getGap(); |
| barGap = 0; |
| barWidth = Math.floor(gap / locationMap.length); |
| // 已经忽略用户定义的宽度设定依然还无法满足显示,只能硬来了; |
| if (barWidth <= 0) { |
| interval = Math.floor(locationMap.length / gap); |
| barWidth = 1; |
| } |
| } |
| } |
| else { |
| // 全是自定义宽度,barGap无效,系列间隔决定barGap |
| gap = sBarWidthCounter > 1 |
| ? (typeof barCategoryGap == 'string' |
| && barCategoryGap.match(/%$/) |
| ) |
| // 百分比 |
| ? Math.floor( |
| categoryAxis.getGap() |
| * (100 - parseFloat(barCategoryGap)) |
| / 100 |
| ) |
| // 数值 |
| : (categoryAxis.getGap() - barCategoryGap) |
| // 只有一个 |
| : sBarWidthTotal; |
| barWidth = 0; |
| barGap = sBarWidthCounter > 1 |
| ? Math.floor( |
| (gap - sBarWidthTotal) / (sBarWidthCounter - 1) |
| ) |
| : 0; |
| if (barGap < 0) { |
| // 无法满足用户定义的宽度设计,忽略用户宽度,打回重做 |
| return this._mapSize(categoryAxis, locationMap, true); |
| } |
| } |
| |
| return { |
| barWidthMap : barWidthMap, |
| barMinHeightMap : barMinHeightMap , |
| gap : gap, |
| barWidth : barWidth, |
| barGap : barGap, |
| interval : interval |
| }; |
| }, |
| |
| /** |
| * 生成最终图形数据 |
| */ |
| _getBarItem : function (seriesIndex, dataIndex, name, x, y, width, height, orient) { |
| var series = this.series; |
| var barShape; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| // 多级控制 |
| var defaultColor = this._sIndex2colorMap[seriesIndex]; |
| var queryTarget = [data, serie]; |
| var normalColor = this.deepQuery( |
| queryTarget, |
| 'itemStyle.normal.color' |
| ) || defaultColor; |
| var emphasisColor = this.deepQuery( |
| queryTarget, |
| 'itemStyle.emphasis.color' |
| ); |
| var normal = this.deepMerge( |
| queryTarget, |
| 'itemStyle.normal' |
| ); |
| var normalBorderWidth = normal.borderWidth; |
| var emphasis = this.deepMerge( |
| queryTarget, |
| 'itemStyle.emphasis' |
| ); |
| barShape = { |
| zlevel : this._zlevelBase, |
| clickable: true, |
| style : { |
| x : x, |
| y : y, |
| width : width, |
| height : height, |
| brushType : 'both', |
| color : this.getItemStyleColor(normalColor, seriesIndex, dataIndex, data), |
| radius : normal.borderRadius, |
| lineWidth : normalBorderWidth, |
| strokeColor : normal.borderColor |
| }, |
| highlightStyle : { |
| color : this.getItemStyleColor(emphasisColor, seriesIndex, dataIndex, data), |
| radius : emphasis.borderRadius, |
| lineWidth : emphasis.borderWidth, |
| strokeColor : emphasis.borderColor |
| }, |
| _orient : orient |
| }; |
| barShape.highlightStyle.color = barShape.highlightStyle.color |
| || (typeof barShape.style.color == 'string' |
| ? zrColor.lift(barShape.style.color, -0.3) |
| : barShape.style.color |
| ); |
| // 考虑线宽的显示优化 |
| if (normalBorderWidth > 0 |
| && barShape.style.height > normalBorderWidth |
| && barShape.style.width > normalBorderWidth |
| ) { |
| barShape.style.y += normalBorderWidth / 2; |
| barShape.style.height -= normalBorderWidth; |
| barShape.style.x += normalBorderWidth / 2; |
| barShape.style.width -= normalBorderWidth; |
| } |
| else { |
| // 太小了或者线宽小于0,废了边线 |
| barShape.style.brushType = 'fill'; |
| } |
| |
| barShape.highlightStyle.textColor = barShape.highlightStyle.color; |
| |
| barShape = this.addLabel(barShape, serie, data, name, orient); |
| if (barShape.style.textPosition == 'insideLeft' |
| || barShape.style.textPosition == 'insideRight' |
| || barShape.style.textPosition == 'insideTop' |
| || barShape.style.textPosition == 'insideBottom' |
| ) { |
| var gap = 5; |
| switch (barShape.style.textPosition) { |
| case 'insideLeft' : |
| barShape.style.textX = barShape.style.x + gap; |
| barShape.style.textY = barShape.style.y + barShape.style.height / 2; |
| barShape.style.textAlign = 'left'; |
| barShape.style.textBaseline = 'middle'; |
| break; |
| case 'insideRight' : |
| barShape.style.textX = barShape.style.x + barShape.style.width - gap; |
| barShape.style.textY = barShape.style.y + barShape.style.height / 2; |
| barShape.style.textAlign = 'right'; |
| barShape.style.textBaseline = 'middle'; |
| break; |
| case 'insideTop' : |
| barShape.style.textX = barShape.style.x + barShape.style.width / 2; |
| barShape.style.textY = barShape.style.y + gap / 2; |
| barShape.style.textAlign = 'center'; |
| barShape.style.textBaseline = 'top'; |
| break; |
| case 'insideBottom' : |
| barShape.style.textX = barShape.style.x + barShape.style.width / 2; |
| barShape.style.textY = barShape.style.y + barShape.style.height - gap / 2; |
| barShape.style.textAlign = 'center'; |
| barShape.style.textBaseline = 'bottom'; |
| break; |
| } |
| barShape.style.textPosition = 'specific'; |
| barShape.style.textColor = barShape.style.textColor || '#fff'; |
| } |
| |
| |
| |
| if (this.deepQuery([data, serie, this.option],'calculable')) { |
| this.setCalculable(barShape); |
| barShape.draggable = true; |
| } |
| |
| ecData.pack( |
| barShape, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| name |
| ); |
| |
| return barShape; |
| }, |
| |
| // 位置转换 |
| getMarkCoord : function (seriesIndex, mpData) { |
| var serie = this.series[seriesIndex]; |
| var xMarkMap = this.xMarkMap[seriesIndex]; |
| var xAxis = this.component.xAxis.getAxis(serie.xAxisIndex); |
| var yAxis = this.component.yAxis.getAxis(serie.yAxisIndex); |
| var dataIndex; |
| var pos; |
| if (mpData.type |
| && (mpData.type == 'max' || mpData.type == 'min' || mpData.type == 'average') |
| ) { |
| // 特殊值内置支持 |
| pos = [ |
| xMarkMap[mpData.type + 'X'], |
| xMarkMap[mpData.type + 'Y'], |
| xMarkMap[mpData.type + 'Line'], |
| xMarkMap[mpData.type] |
| ]; |
| } |
| else if (xMarkMap.isHorizontal) { |
| // 横向 |
| dataIndex = typeof mpData.xAxis == 'string' && xAxis.getIndexByName |
| ? xAxis.getIndexByName(mpData.xAxis) |
| : (mpData.xAxis || 0); |
| |
| var x = xMarkMap[dataIndex]; |
| x = typeof x != 'undefined' |
| ? x |
| : typeof mpData.xAxis != 'string' && xAxis.getCoordByIndex |
| ? xAxis.getCoordByIndex(mpData.xAxis || 0) |
| : xAxis.getCoord(mpData.xAxis || 0); |
| |
| pos = [x, yAxis.getCoord(mpData.yAxis || 0)]; |
| } |
| else { |
| // 纵向 |
| dataIndex = typeof mpData.yAxis == 'string' && yAxis.getIndexByName |
| ? yAxis.getIndexByName(mpData.yAxis) |
| : (mpData.yAxis || 0); |
| |
| var y = xMarkMap[dataIndex]; |
| y = typeof y != 'undefined' |
| ? y |
| : typeof mpData.yAxis != 'string' && yAxis.getCoordByIndex |
| ? yAxis.getCoordByIndex(mpData.yAxis || 0) |
| : yAxis.getCoord(mpData.yAxis || 0); |
| |
| pos = [xAxis.getCoord(mpData.xAxis || 0), y]; |
| } |
| |
| return pos; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| }, |
| |
| /** |
| * 动态数据增加动画 |
| */ |
| addDataAnimation : function (params) { |
| var series = this.series; |
| var aniMap = {}; // seriesIndex索引参数 |
| for (var i = 0, l = params.length; i < l; i++) { |
| aniMap[params[i][0]] = params[i]; |
| } |
| var x; |
| var dx; |
| var y; |
| var dy; |
| var serie; |
| var seriesIndex; |
| var dataIndex; |
| for (var i = this.shapeList.length - 1; i >= 0; i--) { |
| seriesIndex = ecData.get(this.shapeList[i], 'seriesIndex'); |
| if (aniMap[seriesIndex] && !aniMap[seriesIndex][3]) { |
| // 有数据删除才有移动的动画 |
| if (this.shapeList[i].type == 'rectangle') { |
| // 主动画 |
| dataIndex = ecData.get(this.shapeList[i], 'dataIndex'); |
| serie = series[seriesIndex]; |
| if (aniMap[seriesIndex][2] |
| && dataIndex == serie.data.length - 1 |
| ) { |
| // 队头加入删除末尾 |
| this.zr.delShape(this.shapeList[i].id); |
| continue; |
| } |
| else if (!aniMap[seriesIndex][2] && dataIndex === 0) { |
| // 队尾加入删除头部 |
| this.zr.delShape(this.shapeList[i].id); |
| continue; |
| } |
| if (this.shapeList[i]._orient == 'horizontal') { |
| // 条形图 |
| dy = this.component.yAxis.getAxis( |
| serie.yAxisIndex || 0 |
| ).getGap(); |
| y = aniMap[seriesIndex][2] ? -dy : dy; |
| x = 0; |
| } |
| else { |
| // 柱形图 |
| dx = this.component.xAxis.getAxis( |
| serie.xAxisIndex || 0 |
| ).getGap(); |
| x = aniMap[seriesIndex][2] ? dx : -dx; |
| y = 0; |
| } |
| this.shapeList[i].position = [0, 0]; |
| this.zr.animate(this.shapeList[i].id, '') |
| .when( |
| 500, |
| {position : [x, y]} |
| ) |
| .start(); |
| } |
| } |
| } |
| } |
| }; |
| |
| zrUtil.inherits(Bar, ChartBase); |
| zrUtil.inherits(Bar, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('bar', Bar); |
| |
| return Bar; |
| }); |
| /** |
| * echarts图表类:饼图 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, linzhifeng@baidu.com) |
| * |
| */ |
| define('echarts/chart/pie',['require','../component/base','./base','zrender/shape/Text','zrender/shape/Ring','zrender/shape/Circle','zrender/shape/Sector','zrender/shape/BrokenLine','../config','../util/ecData','zrender/tool/util','zrender/tool/math','zrender/tool/color','../chart'],function (require) { |
| var ComponentBase = require('../component/base'); |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var TextShape = require('zrender/shape/Text'); |
| var RingShape = require('zrender/shape/Ring'); |
| var CircleShape = require('zrender/shape/Circle'); |
| var SectorShape = require('zrender/shape/Sector'); |
| var BrokenLineShape = require('zrender/shape/BrokenLine'); |
| |
| var ecConfig = require('../config'); |
| var ecData = require('../util/ecData'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrMath = require('zrender/tool/math'); |
| var zrColor = require('zrender/tool/color'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Pie(ecTheme, messageCenter, zr, option, myChart){ |
| // 基类 |
| ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| // 图表基类 |
| ChartBase.call(this); |
| |
| var self = this; |
| /** |
| * 输出动态视觉引导线 |
| */ |
| self.shapeHandler.onmouseover = function (param) { |
| var shape = param.target; |
| var seriesIndex = ecData.get(shape, 'seriesIndex'); |
| var dataIndex = ecData.get(shape, 'dataIndex'); |
| var percent = ecData.get(shape, 'special'); |
| var lastAddRadius = shape._lastAddRadius; |
| |
| var startAngle = shape.style.startAngle; |
| var endAngle = shape.style.endAngle; |
| var defaultColor = shape.highlightStyle.color; |
| |
| // 文本标签,需要显示则会有返回 |
| var label = self.getLabel( |
| seriesIndex, dataIndex, percent, lastAddRadius, |
| startAngle, endAngle, defaultColor, |
| true |
| ); |
| if (label) { |
| self.zr.addHoverShape(label); |
| } |
| |
| // 文本标签视觉引导线,需要显示则会有返回 |
| var labelLine = self.getLabelLine( |
| seriesIndex, dataIndex, lastAddRadius, |
| shape.style.r0, shape.style.r, |
| startAngle, endAngle, defaultColor, |
| true |
| ); |
| if (labelLine) { |
| self.zr.addHoverShape(labelLine); |
| } |
| }; |
| |
| this.refresh(option); |
| } |
| |
| Pie.prototype = { |
| type : ecConfig.CHART_TYPE_PIE, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape : function () { |
| var series = this.series; |
| var legend = this.component.legend; |
| this.selectedMap = {}; |
| this._selected = {}; |
| var center; |
| var radius; |
| |
| var pieCase; // 饼图箱子 |
| this._selectedMode = false; |
| var serieName; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type == ecConfig.CHART_TYPE_PIE) { |
| series[i] = this.reformOption(series[i]); |
| serieName = series[i].name || ''; |
| // 系列图例开关 |
| this.selectedMap[serieName] = |
| legend ? legend.isSelected(serieName) : true; |
| if (!this.selectedMap[serieName]) { |
| continue; |
| } |
| |
| center = this.parseCenter(this.zr, series[i].center); |
| radius = this.parseRadius(this.zr, series[i].radius); |
| this._selectedMode = this._selectedMode || series[i].selectedMode; |
| this._selected[i] = []; |
| if (this.deepQuery([series[i], this.option], 'calculable')) { |
| pieCase = { |
| zlevel : this._zlevelBase, |
| hoverable : false, |
| style : { |
| x : center[0], // 圆心横坐标 |
| y : center[1], // 圆心纵坐标 |
| // 圆环内外半径 |
| r0 : radius[0] <= 10 ? 0 : radius[0] - 10, |
| r : radius[1] + 10, |
| brushType : 'stroke', |
| lineWidth: 1, |
| strokeColor : series[i].calculableHolderColor |
| || this.ecTheme.calculableHolderColor |
| } |
| }; |
| ecData.pack(pieCase, series[i], i, undefined, -1); |
| this.setCalculable(pieCase); |
| |
| pieCase = radius[0] <= 10 |
| ? new CircleShape(pieCase) |
| : new RingShape(pieCase); |
| this.shapeList.push(pieCase); |
| } |
| this._buildSinglePie(i); |
| this.buildMark(i); |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个饼图 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSinglePie : function (seriesIndex) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data; |
| var legend = this.component.legend; |
| var itemName; |
| var totalSelected = 0; // 迭代累计选中且非0个数 |
| var totalSelectedValue0 = 0; // 迭代累计选中0只个数 |
| var totalValue = 0; // 迭代累计 |
| var maxValue = Number.NEGATIVE_INFINITY; |
| |
| // 计算需要显示的个数和总值 |
| for (var i = 0, l = data.length; i < l; i++) { |
| itemName = data[i].name; |
| if (legend){ |
| this.selectedMap[itemName] = legend.isSelected(itemName); |
| } else { |
| this.selectedMap[itemName] = true; |
| } |
| if (this.selectedMap[itemName] && !isNaN(data[i].value)) { |
| if (+data[i].value !== 0) { |
| totalSelected++; |
| } |
| else { |
| totalSelectedValue0++; |
| } |
| totalValue += +data[i].value; |
| maxValue = Math.max(maxValue, +data[i].value); |
| } |
| } |
| |
| var percent = 100; |
| var lastPercent; // 相邻细角度优化 |
| var lastAddRadius = 0; |
| var clockWise = serie.clockWise; |
| var startAngle = serie.startAngle.toFixed(2) - 0; |
| var endAngle; |
| var minAngle = serie.minAngle || 0.01; // #bugfixed |
| var totalAngle = 360 - (minAngle * totalSelected) |
| - 0.01 * totalSelectedValue0; |
| var defaultColor; |
| var roseType = serie.roseType; |
| var radius; |
| var r0; // 扇形内半径 |
| var r1; // 扇形外半径 |
| |
| for (var i = 0, l = data.length; i < l; i++) { |
| itemName = data[i].name; |
| if (!this.selectedMap[itemName] || isNaN(data[i].value)) { |
| continue; |
| } |
| // 默认颜色策略 |
| if (legend) { |
| // 有图例则从图例中获取颜色定义 |
| defaultColor = legend.getColor(itemName); |
| } |
| else { |
| // 全局颜色定义 |
| defaultColor = this.zr.getColor(i); |
| } |
| |
| lastPercent = percent; |
| percent = data[i].value / totalValue; |
| if (roseType != 'area') { |
| endAngle = clockWise |
| ? (startAngle - percent * totalAngle - (percent !== 0 ? minAngle : 0.01)) |
| : (percent * totalAngle + startAngle + (percent !== 0 ? minAngle : 0.01)); |
| } |
| else { |
| endAngle = clockWise |
| ? (startAngle - 360 / l) |
| : (360 / l + startAngle); |
| } |
| endAngle = endAngle.toFixed(2) - 0; |
| percent = (percent * 100).toFixed(2); |
| |
| radius = this.parseRadius(this.zr, serie.radius); |
| r0 = +radius[0]; |
| r1 = +radius[1]; |
| |
| if (roseType == 'radius') { |
| r1 = data[i].value / maxValue * (r1 - r0) * 0.8 |
| + (r1 - r0) * 0.2 |
| + r0; |
| } |
| else if (roseType == 'area') { |
| r1 = Math.sqrt(data[i].value / maxValue) * (r1 - r0) + r0; |
| } |
| |
| if (clockWise) { |
| var temp; |
| temp = startAngle; |
| startAngle = endAngle; |
| endAngle = temp; |
| } |
| |
| // 当前小角度需要检查前一个是否也是小角度,如果是得调整长度,不能完全避免,但能大大降低覆盖概率 |
| if (i > 0 |
| && Math.abs(startAngle - endAngle) < 15 // 约15度 |
| && lastPercent < 4 |
| && this._needLabel(serie, data[i], false) |
| && this.deepQuery( |
| [data[i], serie], 'itemStyle.normal.label.position' |
| ) != 'center' |
| ) { |
| // 都小就延长,前小后大就缩短 |
| lastAddRadius += (percent < 4 ? 20 : -20); |
| } |
| else { |
| lastAddRadius = 0; |
| } |
| |
| this._buildItem( |
| seriesIndex, i, percent, lastAddRadius, // 相邻最小角度优化 |
| data[i].selected, |
| r0, r1, |
| startAngle, endAngle, defaultColor |
| ); |
| if (!clockWise) { |
| startAngle = endAngle; |
| } |
| } |
| }, |
| |
| /** |
| * 构建单个扇形及指标 |
| */ |
| _buildItem : function ( |
| seriesIndex, dataIndex, percent, lastAddRadius, |
| isSelected, |
| r0, r1, |
| startAngle, endAngle, defaultColor |
| ) { |
| var series = this.series; |
| // 扇形 |
| var sector = this.getSector( |
| seriesIndex, dataIndex, percent, isSelected, |
| r0, r1, |
| startAngle, endAngle, defaultColor |
| ); |
| // 图形需要附加的私有数据 |
| ecData.pack( |
| sector, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| series[seriesIndex].data[dataIndex].name, |
| percent |
| ); |
| sector._lastAddRadius = lastAddRadius; |
| this.shapeList.push(sector); |
| |
| // 文本标签,需要显示则会有返回 |
| var label = this.getLabel( |
| seriesIndex, dataIndex, percent, lastAddRadius, |
| startAngle, endAngle, defaultColor, |
| false |
| ); |
| if (label) { |
| ecData.pack( |
| label, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| series[seriesIndex].data[dataIndex].name, |
| percent |
| ); |
| label._dataIndex = dataIndex; |
| this.shapeList.push(label); |
| } |
| |
| // 文本标签视觉引导线,需要显示则会有返回 |
| var labelLine = this.getLabelLine( |
| seriesIndex, dataIndex, lastAddRadius, |
| r0, r1, |
| startAngle, endAngle, defaultColor, |
| false |
| ); |
| if (labelLine) { |
| ecData.pack( |
| labelLine, |
| series[seriesIndex], seriesIndex, |
| series[seriesIndex].data[dataIndex], dataIndex, |
| series[seriesIndex].data[dataIndex].name, |
| percent |
| ); |
| labelLine._dataIndex = dataIndex; |
| this.shapeList.push(labelLine); |
| } |
| }, |
| |
| /** |
| * 构建扇形 |
| */ |
| getSector : function ( |
| seriesIndex, dataIndex, percent, isSelected, |
| r0, r1, |
| startAngle, endAngle, defaultColor |
| ) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var queryTarget = [data, serie]; |
| var center = this.parseCenter(this.zr, serie.center); |
| |
| // 多级控制 |
| var normal = this.deepMerge( |
| queryTarget, |
| 'itemStyle.normal' |
| ) || {}; |
| var emphasis = this.deepMerge( |
| queryTarget, |
| 'itemStyle.emphasis' |
| ) || {}; |
| var normalColor = this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data) |
| || defaultColor; |
| |
| var emphasisColor = this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data) |
| || (typeof normalColor == 'string' |
| ? zrColor.lift(normalColor, -0.2) |
| : normalColor |
| ); |
| |
| var sector = { |
| zlevel : this._zlevelBase, |
| clickable : true, |
| style : { |
| x : center[0], // 圆心横坐标 |
| y : center[1], // 圆心纵坐标 |
| r0 : r0, // 圆环内半径 |
| r : r1, // 圆环外半径 |
| startAngle : startAngle, |
| endAngle : endAngle, |
| brushType : 'both', |
| color : normalColor, |
| lineWidth : normal.borderWidth, |
| strokeColor : normal.borderColor, |
| lineJoin: 'round' |
| }, |
| highlightStyle : { |
| color : emphasisColor, |
| lineWidth : emphasis.borderWidth, |
| strokeColor : emphasis.borderColor, |
| lineJoin: 'round' |
| }, |
| _seriesIndex : seriesIndex, |
| _dataIndex : dataIndex |
| }; |
| |
| if (isSelected) { |
| var midAngle = |
| ((sector.style.startAngle + sector.style.endAngle) / 2) |
| .toFixed(2) - 0; |
| sector.style._hasSelected = true; |
| sector.style._x = sector.style.x; |
| sector.style._y = sector.style.y; |
| var offset = this.query(serie, 'selectedOffset'); |
| sector.style.x += zrMath.cos(midAngle, true) * offset; |
| sector.style.y -= zrMath.sin(midAngle, true) * offset; |
| |
| this._selected[seriesIndex][dataIndex] = true; |
| } |
| else { |
| this._selected[seriesIndex][dataIndex] = false; |
| } |
| |
| |
| if (this._selectedMode) { |
| sector.onclick = this.shapeHandler.onclick; |
| } |
| |
| if (this.deepQuery([data, serie, this.option], 'calculable')) { |
| this.setCalculable(sector); |
| sector.draggable = true; |
| } |
| |
| // “normal下不显示,emphasis显示”添加事件响应 |
| if (this._needLabel(serie, data, true) // emphasis下显示文本 |
| || this._needLabelLine(serie, data, true) // emphasis下显示引导线 |
| ) { |
| sector.onmouseover = this.shapeHandler.onmouseover; |
| } |
| |
| sector = new SectorShape(sector); |
| return sector; |
| }, |
| |
| /** |
| * 需要显示则会有返回构建好的shape,否则返回undefined |
| */ |
| getLabel : function ( |
| seriesIndex, dataIndex, percent, lastAddRadius, |
| startAngle, endAngle, defaultColor, |
| isEmphasis |
| ) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| |
| // 特定状态下是否需要显示文本标签 |
| if (!this._needLabel(serie, data, isEmphasis)) { |
| return; |
| } |
| |
| var status = isEmphasis ? 'emphasis' : 'normal'; |
| |
| // serie里有默认配置,放心大胆的用! |
| var itemStyle = zrUtil.merge( |
| zrUtil.clone(data.itemStyle) || {}, |
| serie.itemStyle |
| ); |
| // label配置 |
| var labelControl = itemStyle[status].label; |
| var textStyle = labelControl.textStyle || {}; |
| |
| var center = this.parseCenter(this.zr, serie.center); |
| var centerX = center[0]; // 圆心横坐标 |
| var centerY = center[1]; // 圆心纵坐标 |
| var x; |
| var y; |
| var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; // 中值 |
| var radius = this.parseRadius(this.zr, serie.radius); // 标签位置半径 |
| var textAlign; |
| var textBaseline = 'middle'; |
| labelControl.position = labelControl.position |
| || itemStyle.normal.label.position; |
| if (labelControl.position == 'center') { |
| // center显示 |
| radius = radius[1]; |
| x = centerX; |
| y = centerY; |
| textAlign = 'center'; |
| } |
| else if (labelControl.position == 'inner'){ |
| // 内部显示 |
| radius = (radius[0] + radius[1]) / 2 + lastAddRadius; |
| x = Math.round( |
| centerX + radius * zrMath.cos(midAngle, true) |
| ); |
| y = Math.round( |
| centerY - radius * zrMath.sin(midAngle, true) |
| ); |
| defaultColor = '#fff'; |
| textAlign = 'center'; |
| |
| } |
| else { |
| // 外部显示,默认 labelControl.position == 'outer') |
| radius = radius[1] |
| - (-itemStyle[status].labelLine.length) |
| //- (-textStyle.fontSize) |
| + lastAddRadius; |
| x = centerX + radius * zrMath.cos(midAngle, true); |
| y = centerY - radius * zrMath.sin(midAngle, true); |
| textAlign = (midAngle >= 90 && midAngle <= 270) |
| ? 'right' : 'left'; |
| } |
| |
| if (labelControl.position != 'center' |
| && labelControl.position != 'inner' |
| ) { |
| x += textAlign == 'left' ? 20 : -20; |
| } |
| data.__labelX = x - (textAlign == 'left' ? 5 : -5); |
| data.__labelY = y; |
| |
| return new TextShape({ |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| style : { |
| x : x, |
| y : y, |
| color : textStyle.color || defaultColor, |
| text : this.getLabelText( |
| seriesIndex, dataIndex, percent, status |
| ), |
| textAlign : textStyle.align || textAlign, |
| textBaseline : textStyle.baseline || textBaseline, |
| textFont : this.getFont(textStyle) |
| }, |
| highlightStyle : { |
| brushType : 'fill' |
| }, |
| _seriesIndex : seriesIndex, |
| _dataIndex : dataIndex |
| }); |
| }, |
| |
| /** |
| * 根据lable.format计算label text |
| */ |
| getLabelText : function (seriesIndex, dataIndex, percent, status) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var formatter = this.deepQuery( |
| [data, serie], |
| 'itemStyle.' + status + '.label.formatter' |
| ); |
| |
| if (formatter) { |
| if (typeof formatter == 'function') { |
| return formatter.call( |
| this.myChart, |
| serie.name, |
| data.name, |
| data.value, |
| percent |
| ); |
| } |
| else if (typeof formatter == 'string') { |
| formatter = formatter.replace('{a}','{a0}') |
| .replace('{b}','{b0}') |
| .replace('{c}','{c0}') |
| .replace('{d}','{d0}'); |
| formatter = formatter.replace('{a0}', serie.name) |
| .replace('{b0}', data.name) |
| .replace('{c0}', data.value) |
| .replace('{d0}', percent); |
| |
| return formatter; |
| } |
| } |
| else { |
| return data.name; |
| } |
| }, |
| |
| /** |
| * 需要显示则会有返回构建好的shape,否则返回undefined |
| */ |
| getLabelLine : function ( |
| seriesIndex, dataIndex, lastAddRadius, |
| r0, r1, |
| startAngle, endAngle, defaultColor, |
| isEmphasis |
| ) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| |
| // 特定状态下是否需要显示文本标签 |
| if (this._needLabelLine(serie, data, isEmphasis)) { |
| var status = isEmphasis ? 'emphasis' : 'normal'; |
| |
| // serie里有默认配置,放心大胆的用! |
| var itemStyle = zrUtil.merge( |
| zrUtil.clone(data.itemStyle) || {}, |
| serie.itemStyle |
| ); |
| // labelLine配置 |
| var labelLineControl = itemStyle[status].labelLine; |
| var lineStyle = labelLineControl.lineStyle || {}; |
| |
| var center = this.parseCenter(this.zr, serie.center); |
| var centerX = center[0]; // 圆心横坐标 |
| var centerY = center[1]; // 圆心纵坐标 |
| // 视觉引导线起点半径 |
| var midRadius = r1; |
| // 视觉引导线终点半径 |
| var maxRadius = this.parseRadius(this.zr, serie.radius)[1] |
| - (-labelLineControl.length) |
| + lastAddRadius; |
| var midAngle = ((endAngle + startAngle) / 2) % 360; // 角度中值 |
| var cosValue = zrMath.cos(midAngle, true); |
| var sinValue = zrMath.sin(midAngle, true); |
| // 三角函数缓存已在zrender/tool/math中做了 |
| return new BrokenLineShape({ |
| // shape : 'brokenLine', |
| zlevel : this._zlevelBase + 1, |
| hoverable : false, |
| style : { |
| pointList : [ |
| [ |
| centerX + midRadius * cosValue, |
| centerY - midRadius * sinValue |
| ], |
| [ |
| centerX + maxRadius * cosValue, |
| centerY - maxRadius * sinValue |
| ], |
| [ |
| data.__labelX, |
| data.__labelY |
| ] |
| ], |
| //xStart : centerX + midRadius * cosValue, |
| //yStart : centerY - midRadius * sinValue, |
| //xEnd : centerX + maxRadius * cosValue, |
| //yEnd : centerY - maxRadius * sinValue, |
| strokeColor : lineStyle.color || defaultColor, |
| lineType : lineStyle.type, |
| lineWidth : lineStyle.width |
| }, |
| _seriesIndex : seriesIndex, |
| _dataIndex : dataIndex |
| }); |
| } |
| else { |
| return; |
| } |
| }, |
| |
| /** |
| * 返回特定状态(normal or emphasis)下是否需要显示label标签文本 |
| * @param {Object} serie |
| * @param {Object} data |
| * @param {boolean} isEmphasis true is 'emphasis' and false is 'normal' |
| */ |
| _needLabel : function (serie, data, isEmphasis) { |
| return this.deepQuery( |
| [data, serie], |
| 'itemStyle.' |
| + (isEmphasis ? 'emphasis' : 'normal') |
| + '.label.show' |
| ); |
| }, |
| |
| /** |
| * 返回特定状态(normal or emphasis)下是否需要显示labelLine标签视觉引导线 |
| * @param {Object} serie |
| * @param {Object} data |
| * @param {boolean} isEmphasis true is 'emphasis' and false is 'normal' |
| */ |
| _needLabelLine : function (serie, data, isEmphasis) { |
| return this.deepQuery( |
| [data, serie], |
| 'itemStyle.' |
| + (isEmphasis ? 'emphasis' : 'normal') |
| +'.labelLine.show' |
| ); |
| }, |
| |
| /** |
| * 参数修正&默认值赋值,重载基类方法 |
| * @param {Object} opt 参数 |
| */ |
| reformOption : function (opt) { |
| // 常用方法快捷方式 |
| var _merge = zrUtil.merge; |
| opt = _merge( |
| opt || {}, |
| this.ecTheme.pie |
| ); |
| |
| // 通用字体设置 |
| opt.itemStyle.normal.label.textStyle = _merge( |
| opt.itemStyle.normal.label.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| opt.itemStyle.emphasis.label.textStyle = _merge( |
| opt.itemStyle.emphasis.label.textStyle || {}, |
| this.ecTheme.textStyle |
| ); |
| |
| return opt; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh : function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| }, |
| |
| /** |
| * 动态数据增加动画 |
| */ |
| addDataAnimation : function (params) { |
| var series = this.series; |
| var aniMap = {}; // seriesIndex索引参数 |
| for (var i = 0, l = params.length; i < l; i++) { |
| aniMap[params[i][0]] = params[i]; |
| } |
| |
| // 构建新的饼图匹配差异做动画 |
| var sectorMap = {}; |
| var textMap = {}; |
| var lineMap = {}; |
| var backupShapeList = this.shapeList; |
| this.shapeList = []; |
| |
| var seriesIndex; |
| var isHead; |
| var dataGrow; |
| var deltaIdxMap = {}; // 修正新增数据后会对dataIndex产生错位匹配 |
| for (var i = 0, l = params.length; i < l; i++) { |
| seriesIndex = params[i][0]; |
| isHead = params[i][2]; |
| dataGrow = params[i][3]; |
| if (series[seriesIndex] |
| && series[seriesIndex].type == ecConfig.CHART_TYPE_PIE |
| ) { |
| if (isHead) { |
| if (!dataGrow) { |
| sectorMap[ |
| seriesIndex |
| + '_' |
| + series[seriesIndex].data.length |
| ] = 'delete'; |
| } |
| deltaIdxMap[seriesIndex] = 1; |
| } |
| else { |
| if (!dataGrow) { |
| sectorMap[seriesIndex + '_-1'] = 'delete'; |
| deltaIdxMap[seriesIndex] = -1; |
| } |
| else { |
| deltaIdxMap[seriesIndex] = 0; |
| } |
| } |
| this._buildSinglePie(seriesIndex); |
| } |
| } |
| var dataIndex; |
| var key; |
| for (var i = 0, l = this.shapeList.length; i < l; i++) { |
| seriesIndex = this.shapeList[i]._seriesIndex; |
| dataIndex = this.shapeList[i]._dataIndex; |
| key = seriesIndex + '_' + dataIndex; |
| // map映射让n*n变n |
| switch (this.shapeList[i].type) { |
| case 'sector' : |
| sectorMap[key] = this.shapeList[i]; |
| break; |
| case 'text' : |
| textMap[key] = this.shapeList[i]; |
| break; |
| case 'broken-line' : |
| lineMap[key] = this.shapeList[i]; |
| break; |
| } |
| } |
| this.shapeList = []; |
| var targeSector; |
| for (var i = 0, l = backupShapeList.length; i < l; i++) { |
| seriesIndex = backupShapeList[i]._seriesIndex; |
| if (aniMap[seriesIndex]) { |
| dataIndex = backupShapeList[i]._dataIndex |
| + deltaIdxMap[seriesIndex]; |
| key = seriesIndex + '_' + dataIndex; |
| targeSector = sectorMap[key]; |
| if (!targeSector) { |
| continue; |
| } |
| if (backupShapeList[i].type == 'sector') { |
| if (targeSector != 'delete') { |
| // 原有扇形 |
| this.zr.animate(backupShapeList[i].id, 'style') |
| .when( |
| 400, |
| { |
| startAngle : |
| targeSector.style.startAngle, |
| endAngle : |
| targeSector.style.endAngle |
| } |
| ) |
| .start(); |
| } |
| else { |
| // 删除的扇形 |
| this.zr.animate(backupShapeList[i].id, 'style') |
| .when( |
| 400, |
| deltaIdxMap[seriesIndex] < 0 |
| ? { |
| startAngle : |
| backupShapeList[i].style.startAngle |
| } |
| : { |
| endAngle : |
| backupShapeList[i].style.endAngle |
| } |
| ) |
| .start(); |
| } |
| } |
| else if (backupShapeList[i].type == 'text' |
| || backupShapeList[i].type == 'broken-line' |
| ) { |
| if (targeSector == 'delete') { |
| // 删除逻辑一样 |
| this.zr.delShape(backupShapeList[i].id); |
| } |
| else { |
| // 懒得新建变量了,借用一下 |
| switch (backupShapeList[i].type) { |
| case 'text': |
| targeSector = textMap[key]; |
| this.zr.animate(backupShapeList[i].id, 'style') |
| .when( |
| 400, |
| { |
| x :targeSector.style.x, |
| y :targeSector.style.y |
| } |
| ) |
| .start(); |
| break; |
| case 'broken-line': |
| targeSector = lineMap[key]; |
| this.zr.animate(backupShapeList[i].id, 'style') |
| .when( |
| 400, |
| { |
| pointList:targeSector.style.pointList |
| } |
| ) |
| .start(); |
| break; |
| } |
| |
| } |
| } |
| } |
| } |
| this.shapeList = backupShapeList; |
| }, |
| |
| onclick : function (param) { |
| var series = this.series; |
| if (!this.isClick || !param.target) { |
| // 没有在当前实例上发生点击直接返回 |
| return; |
| } |
| this.isClick = false; |
| var offset; // 偏移 |
| var target = param.target; |
| var style = target.style; |
| var seriesIndex = ecData.get(target, 'seriesIndex'); |
| var dataIndex = ecData.get(target, 'dataIndex'); |
| |
| for (var i = 0, len = this.shapeList.length; i < len; i++) { |
| if (this.shapeList[i].id == target.id) { |
| seriesIndex = ecData.get(target, 'seriesIndex'); |
| dataIndex = ecData.get(target, 'dataIndex'); |
| // 当前点击的 |
| if (!style._hasSelected) { |
| var midAngle = |
| ((style.startAngle + style.endAngle) / 2) |
| .toFixed(2) - 0; |
| target.style._hasSelected = true; |
| this._selected[seriesIndex][dataIndex] = true; |
| target.style._x = target.style.x; |
| target.style._y = target.style.y; |
| offset = this.query( |
| series[seriesIndex], |
| 'selectedOffset' |
| ); |
| target.style.x += zrMath.cos(midAngle, true) |
| * offset; |
| target.style.y -= zrMath.sin(midAngle, true) |
| * offset; |
| } |
| else { |
| // 复位 |
| target.style.x = target.style._x; |
| target.style.y = target.style._y; |
| target.style._hasSelected = false; |
| this._selected[seriesIndex][dataIndex] = false; |
| } |
| |
| this.zr.modShape(target.id, target); |
| } |
| else if (this.shapeList[i].style._hasSelected |
| && this._selectedMode == 'single' |
| ) { |
| seriesIndex = ecData.get(this.shapeList[i], 'seriesIndex'); |
| dataIndex = ecData.get(this.shapeList[i], 'dataIndex'); |
| // 单选模式下需要取消其他已经选中的 |
| this.shapeList[i].style.x = this.shapeList[i].style._x; |
| this.shapeList[i].style.y = this.shapeList[i].style._y; |
| this.shapeList[i].style._hasSelected = false; |
| this._selected[seriesIndex][dataIndex] = false; |
| this.zr.modShape( |
| this.shapeList[i].id, this.shapeList[i] |
| ); |
| } |
| } |
| |
| this.messageCenter.dispatch( |
| ecConfig.EVENT.PIE_SELECTED, |
| param.event, |
| { |
| selected : this._selected, |
| target : ecData.get(target, 'name') |
| }, |
| this.myChart |
| ); |
| this.zr.refresh(); |
| } |
| }; |
| |
| zrUtil.inherits(Pie, ChartBase); |
| zrUtil.inherits(Pie, ComponentBase); |
| |
| // 图表注册 |
| require('../chart').define('pie', Pie); |
| |
| return Pie; |
| }); |
| define('_chart',['require','echarts/chart/gauge','echarts/chart/funnel','echarts/chart/scatter','echarts/chart/k','echarts/chart/radar','echarts/chart/chord','echarts/chart/force','echarts/chart/line','echarts/chart/bar','echarts/chart/pie'],function (require) { |
| require("echarts/chart/gauge"); |
| require("echarts/chart/funnel"); |
| require("echarts/chart/scatter"); |
| require("echarts/chart/k"); |
| require("echarts/chart/radar"); |
| require("echarts/chart/chord"); |
| require("echarts/chart/force"); |
| require("echarts/chart/line"); |
| require("echarts/chart/bar"); |
| require("echarts/chart/pie"); |
| }); |
| var zrender = require('zrender'); |
| zrender.tool = { |
| color : require('zrender/tool/color'), |
| math : require('zrender/tool/math'), |
| util : require('zrender/tool/util'), |
| vector : require('zrender/tool/vector'), |
| area : require('zrender/tool/area'), |
| event : require('zrender/tool/event') |
| } |
| |
| zrender.animation = { |
| Animation : require('zrender/animation/Animation'), |
| Cip : require('zrender/animation/Clip'), |
| easing : require('zrender/animation/easing') |
| } |
| var echarts = require('echarts'); |
| echarts.config = require('echarts/config'); |
| echarts.util = {}; |
| |
| // 加载需要的图表,由build.js临时生成 |
| var chart = require('_chart'); |
| |
| _global['echarts'] = echarts; |
| _global['zrender'] = zrender; |
| |
| })(window); |