blob: 77b8eae0af0ff0ec9937145e5f051210b72ec50d [file] [log] [blame]
/**
* echarts图表基类
*
* @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
*
*/
define(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 PolylineShape = require('zrender/shape/Polyline');
var ShapeBundle = require('zrender/shape/ShapeBundle');
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 ComponentBase = require('../component/base');
var EdgeBundling = require('../layout/EdgeBundling');
var zrUtil = require('zrender/tool/util');
var zrArea = require('zrender/tool/area');
// Some utility functions
function isCoordAvailable(coord) {
return coord.x != null && coord.y != null;
}
function Base(ecTheme, messageCenter, zr, option, myChart) {
ComponentBase.call(this, ecTheme, messageCenter, zr, option, myChart);
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
|| ecConfig.calculableColor;
highlightStyle.lineWidth = calculableShape.type === 'icon' ? 30 : 10;
self.zr.addHoverShape(calculableShape);
setTimeout(function (){
// 复位
if (highlightStyle) {
highlightStyle.brushType = brushType;
highlightStyle.strokeColor = strokeColor;
highlightStyle.lineWidth = lineWidth;
}
},20);
},
ondrop: function (param) {
// 排除一些非数据的拖拽进入
if (ecData.get(param.dragged, 'data') != null) {
self.isDrop = true;
}
},
ondragend: function () {
self.isDragend = true;
}
};
}
/**
* 基类方法
*/
Base.prototype = {
/**
* 图形拖拽特性
*/
setCalculable: function (shape) {
shape.dragEnableTime = this.ecTheme.DRAG_ENABLE_TIME || ecConfig.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 || status.dragIn) {
// 没有在当前实例上发生拖拽行为或者已经被认领了则直接返回
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上,数据被拖拽进某个饼图|雷达|漏斗,增加数据
if (ecData.get(dragged, 'seriesIndex') == seriesIndex) {
// 自己拖拽到自己
status.dragOut = status.dragIn = status.needRefresh = true;
this.isDrop = false;
return;
}
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 = series[seriesIndex].data[dataIndex] || '-';
if (data.value != null) {
if (data.value != '-') {
series[seriesIndex].data[dataIndex].value =
accMath.accAdd(
series[seriesIndex].data[dataIndex].value,
ecData.get(dragged, 'value')
);
}
else {
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 != '-') {
series[seriesIndex].data[dataIndex] =
accMath.accAdd(
series[seriesIndex].data[dataIndex],
ecData.get(dragged, 'value')
);
}
else {
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 || status.dragOut) {
// 没有在当前实例上发生拖拽行为或者已经被认领了则直接返回
return;
}
var target = param.target; // 被拖拽图形元素
var seriesIndex = ecData.get(target, 'seriesIndex');
var dataIndex = ecData.get(target, 'dataIndex');
var series = this.series;
// 删除被拖拽走的数据
if (series[seriesIndex].data[dataIndex].value != null) {
series[seriesIndex].data[dataIndex].value = '-';
// 清理可能有且唯一的legend data
var name = series[seriesIndex].data[dataIndex].name;
var legend = this.component.legend;
if (legend && legend.getRelatedAmount(name) === 0) {
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;
},
/**
* 折线图、柱形图公用方法
*/
_buildPosition: function() {
this._symbol = this.option.symbolList;
this._sIndex2ShapeMap = {}; // series拐点图形类型,seriesIndex索引到shape type
this._sIndex2ColorMap = {}; // series默认颜色索引,seriesIndex索引到color
this.selectedMap = {};
this.xMarkMap = {};
var series = this.series;
// 水平垂直双向series索引 ,position索引到seriesIndex
var _position2sIndexMap = {
top: [],
bottom: [],
left: [],
right: [],
other: []
};
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]);
this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink;
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);
}
else {
_position2sIndexMap.other.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;
case 'other' :
this._buildOther(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
var style = iconShape.style;
if (this.type == ecConfig.CHART_TYPE_LINE) {
style.iconType = 'legendLineIcon';
style.symbol = this._sIndex2ShapeMap[seriesArray[i]];
}
else if (serie.itemStyle.normal.barBorderWidth > 0) {
var highlightStyle = iconShape.highlightStyle;
style.brushType = 'both';
style.x += 1;
style.y += 1;
style.width -= 2;
style.height -= 2;
style.strokeColor
= highlightStyle.strokeColor
= serie.itemStyle.normal.barBorderColor;
highlightStyle.lineWidth = 3;
}
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 (stackMap[stackKey] == null) {
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
};
},
_calculMarkMapXY : function(xMarkMap, locationMap, xy) {
var series = this.series;
for (var j = 0, k = locationMap.length; j < k; j++) {
for (var m = 0, n = locationMap[j].length; m < n; m++) {
var seriesIndex = locationMap[j][m];
var valueIndex = xy == 'xy' ? 0 : '';
var grid = this.component.grid;
var tarMark = xMarkMap[seriesIndex];
if (xy.indexOf('x') != '-1') {
if (tarMark['counter' + valueIndex] > 0) {
tarMark['average' + valueIndex] =
tarMark['sum' + valueIndex] / tarMark['counter' + valueIndex];
}
var x = this.component.xAxis.getAxis(series[seriesIndex].xAxisIndex || 0)
.getCoord(tarMark['average' + valueIndex]);
tarMark['averageLine' + valueIndex] = [
[x, grid.getYend()],
[x, grid.getY()]
];
tarMark['minLine' + valueIndex] = [
[tarMark['minX' + valueIndex], grid.getYend()],
[tarMark['minX' + valueIndex], grid.getY()]
];
tarMark['maxLine' + valueIndex] = [
[tarMark['maxX' + valueIndex], grid.getYend()],
[tarMark['maxX' + valueIndex], grid.getY()]
];
tarMark.isHorizontal = false;
}
valueIndex = xy == 'xy' ? 1 : '';
if (xy.indexOf('y') != '-1') {
if (tarMark['counter' + valueIndex] > 0) {
tarMark['average' + valueIndex] =
tarMark['sum' + valueIndex] / tarMark['counter' + valueIndex];
}
var y = this.component.yAxis.getAxis(series[seriesIndex].yAxisIndex || 0)
.getCoord(tarMark['average' + valueIndex]);
tarMark['averageLine' + valueIndex] = [
[grid.getX(), y],
[grid.getXend(), y]
];
tarMark['minLine' + valueIndex] = [
[grid.getX(), tarMark['minY' + valueIndex]],
[grid.getXend(), tarMark['minY' + valueIndex]]
];
tarMark['maxLine' + valueIndex] = [
[grid.getX(), tarMark['maxY' + valueIndex]],
[grid.getXend(), tarMark['maxY' + valueIndex]]
];
tarMark.isHorizontal = true;
}
}
}
},
/**
* 添加文本
*/
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) {
var style = tarShape.style;
style.text = this._getLabelText(
serie, data, name, 'normal'
);
style.textPosition = nLabel.position == null
? (orient === 'horizontal' ? 'right' : 'top')
: nLabel.position;
style.textColor = nTextStyle.color;
style.textFont = this.getFont(nTextStyle);
style.textAlign = nTextStyle.align;
style.textBaseline = nTextStyle.baseline;
}
if (eLabel.show) {
var highlightStyle = tarShape.highlightStyle;
highlightStyle.text = this._getLabelText(
serie, data, name, 'emphasis'
);
highlightStyle.textPosition = nLabel.show
? tarShape.style.textPosition
: (eLabel.position == null
? (orient === 'horizontal' ? 'right' : 'top')
: eLabel.position);
highlightStyle.textColor = eTextStyle.color;
highlightStyle.textFont = this.getFont(eTextStyle);
highlightStyle.textAlign = eTextStyle.align;
highlightStyle.textBaseline = eTextStyle.baseline;
}
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 = this.getDataFromOption(data, '-');
if (formatter) {
if (typeof formatter === 'function') {
return formatter.call(
this.myChart,
{
seriesName: serie.name,
series: serie,
name: name,
value: value,
data: data,
status: status
}
);
}
else if (typeof formatter === 'string') {
formatter = formatter.replace('{a}','{a0}')
.replace('{b}','{b0}')
.replace('{c}','{c0}')
.replace('{a0}', serie.name)
.replace('{b0}', name)
.replace('{c0}', this.numAddCommas(value));
return formatter;
}
}
else {
if (value instanceof Array) {
return value[2] != null
? this.numAddCommas(value[2])
: (value[0] + ' , ' + value[1]);
}
else {
return this.numAddCommas(value);
}
}
},
/**
* 标线标注
*/
buildMark: function (seriesIndex) {
var serie = this.series[seriesIndex];
if (this.selectedMap[serie.name]) {
serie.markLine && this._buildMarkLine(seriesIndex);
serie.markPoint && this._buildMarkPoint(seriesIndex);
}
},
/**
* 标注逻辑
*/
_buildMarkPoint: function (seriesIndex) {
var attachStyle = (this.markAttachStyle || {})[seriesIndex];
var serie = this.series[seriesIndex];
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);
mpData.x = mpData.x != null ? mpData.x : pos[0];
mpData.y = mpData.y != null ? mpData.y : pos[1];
if (mpData.type
&& (mpData.type === 'max' || mpData.type === 'min')
) {
// 特殊值内置支持
mpData.value = pos[3];
mpData.name = mpData.name || mpData.type;
mpData.symbolSize = mpData.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++) {
var tarShape = shapeList[i];
tarShape.zlevel = serie.zlevel;
tarShape.z = serie.z + 1;
for (var key in attachStyle) {
tarShape[key] = zrUtil.clone(attachStyle[key]);
}
this.shapeList.push(tarShape);
}
// 个别特殊图表需要自己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 pos;
var markLine = zrUtil.clone(serie.markLine);
for (var i = 0, l = markLine.data.length; i < l; i++) {
var 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 = mlData.type !== 'average'
? pos[3]
: +pos[3].toFixed(
markLine.precision != null
? markLine.precision
: this.deepQuery(
[this.ecTheme, ecConfig],
'markLine.precision'
)
);
pos = pos[2];
mlData = [{},{}];
}
else {
pos = [
this.getMarkCoord(seriesIndex, mlData[0]),
this.getMarkCoord(seriesIndex, mlData[1])
];
}
if (pos == null || pos[0] == null || pos[1] == null) {
// 不在显示区域内
continue;
}
markLine.data[i][0].x = mlData[0].x != null ? mlData[0].x : pos[0][0];
markLine.data[i][0].y = mlData[0].y != null ? mlData[0].y : pos[0][1];
markLine.data[i][1].x = mlData[1].x != null ? mlData[1].x : pos[1][0];
markLine.data[i][1].y = mlData[1].y != null ? mlData[1].y : pos[1][1];
}
var shapeList = this._markLine(seriesIndex, markLine);
var isLarge = markLine.large;
if (isLarge) {
var shapeBundle = new ShapeBundle({
style: {
shapeList: shapeList
}
});
var firstShape = shapeList[0];
if (firstShape) {
zrUtil.merge(shapeBundle.style, firstShape.style);
zrUtil.merge(shapeBundle.highlightStyle = {}, firstShape.highlightStyle);
shapeBundle.style.brushType = 'stroke';
shapeBundle.zlevel = serie.zlevel;
shapeBundle.z = serie.z + 1;
shapeBundle.hoverable = false;
for (var key in attachStyle) {
shapeBundle[key] = zrUtil.clone(attachStyle[key]);
}
}
this.shapeList.push(shapeBundle);
this.zr.addShape(shapeBundle);
shapeBundle._mark = 'largeLine';
var effect = markLine.effect;
if (effect.show) {
shapeBundle.effect = effect;
}
}
else {
for (var i = 0, l = shapeList.length; i < l; i++) {
var tarShape = shapeList[i];
tarShape.zlevel = serie.zlevel;
tarShape.z = serie.z + 1;
for (var key in attachStyle) {
tarShape[key] = zrUtil.clone(attachStyle[key]);
}
this.shapeList.push(tarShape);
}
// 个别特殊图表需要自己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(
zrUtil.merge(
mpOption,
zrUtil.clone(this.ecTheme.markPoint || {})
),
zrUtil.clone(ecConfig.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++) {
if (data[i].x == null || data[i].y == null) {
continue;
}
value = data[i].value != null ? 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;
}
}
color = color == null ? this.zr.getColor(seriesIndex) : color;
// 标准化一些参数
data[i].tooltip = data[i].tooltip
|| mpOption.tooltip
|| {trigger:'item'}; // tooltip.trigger指定为item
data[i].name = data[i].name != null ? 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.getLargeMarkPointShape(seriesIndex, mpOption);
itemShape._mark = 'largePoint';
itemShape && pList.push(itemShape);
}
return pList;
},
/**
* 标线多级控制构造
*/
_markLine: (function () {
function normalizeOptionValue(mlOption, key) {
mlOption[key] = mlOption[key] instanceof Array
? mlOption[key].length > 1
? mlOption[key]
: [mlOption[key][0], mlOption[key][0]]
: [mlOption[key], mlOption[key]];
}
return function (seriesIndex, mlOption) {
var serie = this.series[seriesIndex];
var component = this.component;
var dataRange = component.dataRange;
var legend = component.legend;
zrUtil.merge(
zrUtil.merge(
mlOption,
zrUtil.clone(this.ecTheme.markLine || {})
),
zrUtil.clone(ecConfig.markLine)
);
var defaultColor = legend ? legend.getColor(serie.name)
: this.zr.getColor(seriesIndex);
// 标准化一些同时支持Array和String的参数
normalizeOptionValue(mlOption, 'symbol');
normalizeOptionValue(mlOption, 'symbolSize');
normalizeOptionValue(mlOption, 'symbolRotate');
// Normalize and filter data
var data = mlOption.data;
var edges = [];
var zrWidth = this.zr.getWidth();
var zrHeight = this.zr.getHeight();
for (var i = 0; i < data.length; i++) {
var mlData = data[i];
if (isCoordAvailable(mlData[0])
&& isCoordAvailable(mlData[1])
) {
// 组装一个mergeData
var mergeData = this.deepMerge(mlData);
var queryTarget = [mergeData, mlOption];
var color = defaultColor;
var value = mergeData.value != null ? mergeData.value : '';
// 值域
if (dataRange) {
color = isNaN(value) ? color : dataRange.getColor(value);
var nColor = this.deepQuery(queryTarget, 'itemStyle.normal.color')
|| color;
var eColor = this.deepQuery(queryTarget, 'itemStyle.emphasis.color')
|| nColor;
// 有值域,并且值域返回null且用户没有自己定义颜色,则隐藏这个mark
if (nColor == null && eColor == null) {
continue;
}
}
// 标准化一些参数
mlData[0].tooltip = mergeData.tooltip
|| mlOption.tooltip
|| {trigger:'item'}; // tooltip.trigger指定为item
mlData[0].name = mlData[0].name || '';
mlData[1].name = mlData[1].name || '';
mlData[0].value = value;
edges.push({
points: [
[this.parsePercent(mlData[0].x, zrWidth),
this.parsePercent(mlData[0].y, zrHeight)],
[this.parsePercent(mlData[1].x, zrWidth),
this.parsePercent(mlData[1].y, zrHeight)]
],
rawData: mlData,
color: color
});
}
}
var enableBundling = this.query(mlOption, 'bundling.enable');
if (enableBundling) {
var edgeBundling = new EdgeBundling();
edgeBundling.maxTurningAngle = this.query(
mlOption, 'bundling.maxTurningAngle'
) / 180 * Math.PI;
edges = edgeBundling.run(edges);
}
mlOption.name = serie.name;
var shapeList = [];
for (var i = 0, l = edges.length; i < l; i++) {
var edge = edges[i];
var rawEdge = edge.rawEdge || edge;
var mlData = rawEdge.rawData;
var value = mlData.value != null ? mlData.value : '';
var itemShape = this.getMarkLineShape(
mlOption,
seriesIndex,
mlData,
i,
edge.points,
enableBundling,
rawEdge.color
);
itemShape._mark = 'line';
var effect = this.deepMerge(
[mlData[0], mlData[1], mlOption],
'effect'
);
if (effect.show) {
itemShape.effect = effect;
itemShape.effect.large = mlOption.large;
}
if (serie.type === ecConfig.CHART_TYPE_MAP) {
itemShape._geo = [
this.getMarkGeo(mlData[0]),
this.getMarkGeo(mlData[1])
];
}
// 重新pack一下数据
ecData.pack(
itemShape,
serie, seriesIndex,
mlData[0], i,
mlData[0].name
// 不要帮我代码规范
+ (mlData[1].name !== '' ? (' > ' + mlData[1].name) : ''),
value
);
shapeList.push(itemShape);
}
return shapeList;
};
})(),
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 = this.getDataFromOption(data, '-');
symbol = this.deepQuery(queryTarget, 'symbol') || symbol;
var symbolSize = this.deepQuery(queryTarget, 'symbolSize');
symbolSize = typeof symbolSize === 'function'
? symbolSize(value)
: symbolSize;
if (typeof symbolSize === 'number') {
symbolSize = [symbolSize, symbolSize];
}
var symbolRotate = this.deepQuery(queryTarget, 'symbolRotate');
var normal = this.deepMerge(
queryTarget,
'itemStyle.normal'
);
var emphasis = this.deepMerge(
queryTarget,
'itemStyle.emphasis'
);
var nBorderWidth = normal.borderWidth != null
? normal.borderWidth
: (normal.lineStyle && normal.lineStyle.width);
if (nBorderWidth == null) {
nBorderWidth = symbol.match('empty') ? 2 : 0;
}
var eBorderWidth = emphasis.borderWidth != null
? emphasis.borderWidth
: (emphasis.lineStyle && emphasis.lineStyle.width);
if (eBorderWidth == null) {
eBorderWidth = nBorderWidth + 2;
}
var nColor = this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data);
var eColor = this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data);
var width = symbolSize[0];
var height = symbolSize[1];
var itemShape = new IconShape({
style: {
iconType: symbol.replace('empty', '').toLowerCase(),
x: x - width,
y: y - height,
width: width * 2,
height: height * 2,
brushType: 'both',
color: symbol.match('empty')
? emptyColor
: (nColor || color),
strokeColor: normal.borderColor || nColor || color,
lineWidth: nBorderWidth
},
highlightStyle: {
color: symbol.match('empty')
? emptyColor
: (eColor || nColor || color),
strokeColor: emphasis.borderColor
|| normal.borderColor
|| eColor
|| nColor
|| color,
lineWidth: eBorderWidth
},
clickable: this.deepQuery(queryTarget, 'clickable')
});
if (symbol.match('image')) {
itemShape.style.image = symbol.replace(new RegExp('^image:\\/\\/'), '');
itemShape = new ImageShape({
style: itemShape.style,
highlightStyle: itemShape.highlightStyle,
clickable: this.deepQuery(queryTarget, 'clickable')
});
}
if (symbolRotate != null) {
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 (itemShape.style.textColor == null) {
itemShape.style.textColor = itemShape.style.strokeColor;
}
if (itemShape.highlightStyle.textColor == null) {
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;
},
/**
* 标线构造器
*/
getMarkLineShape: function (
mlOption, // 系列
seriesIndex, // 系列索引
data, // 数据
dataIndex, // 数据索引
points, // 坐标点
bundling, // 是否边捆绑过
color // 默认color,来自legend或dataRange全局分配
) {
var value0 = data[0].value != null ? data[0].value : '-';
var value1 = data[1].value != null ? data[1].value : '-';
var symbol = [
data[0].symbol || mlOption.symbol[0],
data[1].symbol || mlOption.symbol[1]
];
var symbolSize = [
data[0].symbolSize || mlOption.symbolSize[0],
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], data[1], 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 (nBorderWidth == null) {
nBorderWidth = normal.borderWidth;
}
var eBorderWidth = elineStyle.width;
if (eBorderWidth == null) {
eBorderWidth = emphasis.borderWidth != null
? emphasis.borderWidth
: (nBorderWidth + 2);
}
var smoothness = this.deepQuery(queryTarget, 'smoothness');
if (! this.deepQuery(queryTarget, 'smooth')) {
smoothness = 0;
}
var ShapeCtor = bundling ? PolylineShape : MarkLineShape;
var itemShape = new ShapeCtor({
style: {
symbol: symbol,
symbolSize: symbolSize,
symbolRotate: symbolRotate,
// data: [data[0].name,data[1].name],
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: emphasis.borderWidth == null
? (normal.borderWidth + 2)
: (emphasis.borderWidth)
},
clickable: this.deepQuery(queryTarget, 'clickable')
});
var shapeStyle = itemShape.style;
if (bundling) {
shapeStyle.pointList = points;
shapeStyle.smooth = smoothness;
}
else {
shapeStyle.xStart = points[0][0];
shapeStyle.yStart = points[0][1];
shapeStyle.xEnd = points[1][0];
shapeStyle.yEnd = points[1][1];
shapeStyle.curveness = smoothness;
itemShape.updatePoints(itemShape.style);
}
itemShape = this.addLabel(
itemShape,
mlOption,
data[0],
data[0].name + ' : ' + data[1].name
);
return itemShape;
},
/**
* 大规模标注构造器
*/
getLargeMarkPointShape: 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 = data[0].value != null ? data[0].value : '';
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: (effect.shadowBlur != null ? 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 isUpdate = lastShapeList.length > 0;
var duration = isUpdate
? this.query(this.option, 'animationDurationUpdate')
: this.query(this.option, 'animationDuration');
var easing = this.query(this.option, 'animationEasing');
var delay;
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;
// https://github.com/ecomfe/echarts/issues/1219#issuecomment-71987602
// 响应中断可能产生的重复元素
if (oldMap[key]) {
this.zr.delShape(lastShapeList[i].id);
}
else {
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, 0, isUpdate
);
}
else {
// 新有旧没有 添加并动画过渡
//this._animateAdd(newMap[key], duration, easing);
delay = (this.type == ecConfig.CHART_TYPE_LINE
|| this.type == ecConfig.CHART_TYPE_RADAR)
&& key.indexOf('icon') !== 0
? duration / 2
: 0;
this._animateMod(
false, newMap[key], duration, easing, delay, isUpdate
);
}
}
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
&& this.type != ecConfig.CHART_TYPE_TREEMAP
&& this.type != ecConfig.CHART_TYPE_VENN
&& this.type != ecConfig.CHART_TYPE_TREE
) {
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, delay, isUpdate) {
switch (newShape.type) {
case 'polyline' :
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 'image' :
case 'icon' :
ecAnimation.icon(this.zr, oldShape, newShape, duration, easing, delay);
break;
case 'candle' :
if (!isUpdate) {
ecAnimation.candle(this.zr, oldShape, newShape, duration, easing);
}
else {
this.zr.addShape(newShape);
}
break;
case 'ring' :
case 'sector' :
case 'circle' :
if (!isUpdate) {
// 进入动画,加旋转
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 (!isUpdate) {
ecAnimation.polygon(this.zr, oldShape, newShape, duration, easing);
}
else {
ecAnimation.pointList(this.zr, oldShape, newShape, duration, easing);
}
break;
case 'ribbon' :
ecAnimation.ribbon(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 'bezier-curve' :
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=} shapeList 指定特效对象,不指定默认使用this.shapeList
*/
animationMark: function (duration , easing, shapeList) {
var shapeList = shapeList || this.shapeList;
for (var i = 0, l = shapeList.length; i < l; i++) {
if (!shapeList[i]._mark) {
continue;
}
this._animateMod(false, shapeList[i], duration, easing, 0, true);
}
this.animationEffect(shapeList);
},
/**
* 特效动画
* @param {Array=} shapeList 指定特效对象,不知道默认使用this.shapeList
*/
animationEffect: function (shapeList) {
!shapeList && this.clearEffectShape();
shapeList = shapeList || this.shapeList;
if (shapeList == null) {
return;
}
var zlevel = ecConfig.EFFECT_ZLEVEL;
if (this.canvasSupported) {
this.zr.modLayer(
zlevel,
{
motionBlur: true,
lastFrameAlpha: this.option.effectBlendAlpha
|| ecConfig.effectBlendAlpha
}
);
}
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) {
var effectList = this.effectList;
if (this.zr && effectList && effectList.length > 0) {
clearMotionBlur && this.zr.modLayer(
ecConfig.EFFECT_ZLEVEL,
{ motionBlur: false }
);
this.zr.delShape(effectList);
// 手动清除不会被 zr 自动清除的动画控制器
for (var i = 0; i < effectList.length; i++) {
if (effectList[i].effectAnimator) {
effectList[i].effectAnimator.stop();
}
}
}
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 = this.query(this.option, 'animationDurationUpdate');
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);
if (this.option.animation && !this.option.renderAsImage) {
// animationMark就会addShape
this.animationMark(duration, easing, this.shapeList.slice(lastLength));
}
else {
for (var i = lastLength, l = this.shapeList.length; i < l; i++) {
this.zr.addShape(this.shapeList[i]);
}
this.zr.refreshNextFrame();
}
// 还原,复用_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.refreshNextFrame();
}
}
};
zrUtil.inherits(Base, ComponentBase);
return Base;
});