/** | |
* echarts图表类:饼图 | |
* | |
* @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 | |
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com) | |
* | |
*/ | |
define(function (require) { | |
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 PolylineShape = require('zrender/shape/Polyline'); | |
var ecConfig = require('../config'); | |
// 饼图默认参数 | |
ecConfig.pie = { | |
zlevel: 0, // 一级层叠 | |
z: 2, // 二级层叠 | |
clickable: true, | |
legendHoverLink: true, | |
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 | |
// distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数 | |
}, | |
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 | |
// distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数 | |
}, | |
labelLine: { | |
show: false, | |
length: 20, | |
lineStyle: { | |
// color: 各异, | |
width: 1, | |
type: 'solid' | |
} | |
} | |
} | |
} | |
}; | |
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){ | |
// 图表基类 | |
ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart); | |
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 center = [shape.style.x, shape.style.y]; | |
var startAngle = shape.style.startAngle; | |
var endAngle = shape.style.endAngle; | |
var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; // 中值 | |
var defaultColor = shape.highlightStyle.color; | |
// 文本标签,需要显示则会有返回 | |
var label = self.getLabel( | |
seriesIndex, dataIndex, percent, | |
center, midAngle, defaultColor, | |
true | |
); | |
if (label) { | |
self.zr.addHoverShape(label); | |
} | |
// 文本标签视觉引导线,需要显示则会有返回 | |
var labelLine = self.getLabelLine( | |
seriesIndex, dataIndex, | |
center, shape.style.r0, shape.style.r, | |
midAngle, 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]); | |
this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink; | |
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.getZlevelBase(), | |
z: this.getZBase(), | |
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 | |
|| ecConfig.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; | |
var singleShapeList = []; | |
// 计算需要显示的个数和总值 | |
for (var i = 0, l = data.length; i < l; i++) { | |
itemName = data[i].name; | |
this.selectedMap[itemName] = legend ? legend.isSelected(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); | |
} | |
} | |
if (totalValue === 0) { | |
return; | |
} | |
var percent = 100; | |
var clockWise = serie.clockWise; | |
var startAngle = (serie.startAngle.toFixed(2) - 0 + 360) % 360; | |
var endAngle; | |
var minAngle = serie.minAngle || 0.01; // #bugfixed | |
var totalAngle = 360 - (minAngle * totalSelected) - 0.01 * totalSelectedValue0; | |
var defaultColor; | |
var roseType = serie.roseType; | |
var center; | |
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; | |
} | |
// 默认颜色策略,有图例则从图例中获取颜色定义,没有就全局颜色定义 | |
defaultColor = legend ? legend.getColor(itemName) : this.zr.getColor(i); | |
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); | |
center = this.parseCenter(this.zr, serie.center); | |
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; | |
} | |
this._buildItem( | |
singleShapeList, | |
seriesIndex, i, percent, | |
data[i].selected, | |
center, r0, r1, | |
startAngle, endAngle, defaultColor | |
); | |
if (!clockWise) { | |
startAngle = endAngle; | |
} | |
} | |
this._autoLabelLayout(singleShapeList, center, r1); | |
for (var i = 0, l = singleShapeList.length; i < l; i++) { | |
this.shapeList.push(singleShapeList[i]); | |
} | |
singleShapeList = null; | |
}, | |
/** | |
* 构建单个扇形及指标 | |
*/ | |
_buildItem: function ( | |
singleShapeList, | |
seriesIndex, dataIndex, percent, | |
isSelected, | |
center, r0, r1, | |
startAngle, endAngle, defaultColor | |
) { | |
var series = this.series; | |
var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; // 中值 | |
// 扇形 | |
var sector = this.getSector( | |
seriesIndex, dataIndex, percent, isSelected, | |
center, r0, r1, | |
startAngle, endAngle, defaultColor | |
); | |
// 图形需要附加的私有数据 | |
ecData.pack( | |
sector, | |
series[seriesIndex], seriesIndex, | |
series[seriesIndex].data[dataIndex], dataIndex, | |
series[seriesIndex].data[dataIndex].name, | |
percent | |
); | |
singleShapeList.push(sector); | |
// 文本标签,需要显示则会有返回 | |
var label = this.getLabel( | |
seriesIndex, dataIndex, percent, | |
center, midAngle, defaultColor, | |
false | |
); | |
// 文本标签视觉引导线,需要显示则会有返回 | |
var labelLine = this.getLabelLine( | |
seriesIndex, dataIndex, | |
center, r0, r1, | |
midAngle, defaultColor, | |
false | |
); | |
if (labelLine) { | |
ecData.pack( | |
labelLine, | |
series[seriesIndex], seriesIndex, | |
series[seriesIndex].data[dataIndex], dataIndex, | |
series[seriesIndex].data[dataIndex].name, | |
percent | |
); | |
singleShapeList.push(labelLine); | |
} | |
if (label) { | |
ecData.pack( | |
label, | |
series[seriesIndex], seriesIndex, | |
series[seriesIndex].data[dataIndex], dataIndex, | |
series[seriesIndex].data[dataIndex].name, | |
percent | |
); | |
label._labelLine = labelLine; | |
singleShapeList.push(label); | |
} | |
}, | |
/** | |
* 构建扇形 | |
*/ | |
getSector: function ( | |
seriesIndex, dataIndex, percent, isSelected, | |
center, r0, r1, | |
startAngle, endAngle, defaultColor | |
) { | |
var series = this.series; | |
var serie = 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 sector = { | |
zlevel: this.getZlevelBase(), | |
z: this.getZBase(), | |
clickable: this.deepQuery(queryTarget, 'clickable'), | |
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; | |
} | |
// “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, | |
center, midAngle, 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 centerX = center[0]; // 圆心横坐标 | |
var centerY = center[1]; // 圆心纵坐标 | |
var x; | |
var y; | |
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显示 | |
x = centerX; | |
y = centerY; | |
textAlign = 'center'; | |
} | |
else if (labelControl.position === 'inner' || labelControl.position === 'inside') { | |
// 内部标签显示, 按外半径比例计算标签位置 | |
radius = (radius[0] + radius[1]) * (labelControl.distance || 0.5); | |
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); | |
x = Math.round(centerX + radius * zrMath.cos(midAngle, true)); | |
y = Math.round(centerY - radius * zrMath.sin(midAngle, true)); | |
textAlign = (midAngle >= 90 && midAngle <= 270) ? 'right' : 'left'; | |
} | |
if (labelControl.position != 'center' | |
&& labelControl.position != 'inner' | |
&& labelControl.position != 'inside' | |
) { | |
x += textAlign === 'left' ? 20 : -20; | |
} | |
data.__labelX = x - (textAlign === 'left' ? 5 : -5); | |
data.__labelY = y; | |
var ts = new TextShape({ | |
zlevel: this.getZlevelBase(), | |
z: this.getZBase() + 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' | |
} | |
}); | |
ts._radius = radius; | |
ts._labelPosition = labelControl.position || 'outer'; | |
ts._rect = ts.getRect(ts.style); | |
ts._seriesIndex = seriesIndex; | |
ts._dataIndex = dataIndex; | |
return ts; | |
}, | |
/** | |
* 根据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, | |
{ | |
seriesIndex: seriesIndex, | |
seriesName: serie.name || '', | |
series: serie, | |
dataIndex: dataIndex, | |
data: data, | |
name: data.name, | |
value: data.value, | |
percent: 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, | |
center, r0, r1, | |
midAngle, 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 centerX = center[0]; // 圆心横坐标 | |
var centerY = center[1]; // 圆心纵坐标 | |
// 视觉引导线起点半径 | |
var minRadius = r1; | |
// 视觉引导线终点半径 | |
var maxRadius = this.parseRadius(this.zr, serie.radius)[1] | |
- (-labelLineControl.length); | |
var cosValue = zrMath.cos(midAngle, true); | |
var sinValue = zrMath.sin(midAngle, true); | |
return new PolylineShape({ | |
zlevel: this.getZlevelBase(), | |
z: this.getZBase() + 1, | |
hoverable: false, | |
style: { | |
pointList: [ | |
[ | |
centerX + minRadius * cosValue, | |
centerY - minRadius * sinValue | |
], | |
[ | |
centerX + maxRadius * cosValue, | |
centerY - maxRadius * sinValue | |
], | |
[ | |
data.__labelX, | |
data.__labelY | |
] | |
], | |
//xStart: centerX + minRadius * cosValue, | |
//yStart: centerY - minRadius * 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 {Array.<Object>} sList 单系列图形集合 | |
*/ | |
_autoLabelLayout : function (sList, center, r) { | |
var leftList = []; | |
var rightList = []; | |
for (var i = 0, l = sList.length; i < l; i++) { | |
if (sList[i]._labelPosition === 'outer' || sList[i]._labelPosition === 'outside') { | |
sList[i]._rect._y = sList[i]._rect.y; | |
if (sList[i]._rect.x < center[0]) { | |
leftList.push(sList[i]); | |
} | |
else { | |
rightList.push(sList[i]); | |
} | |
} | |
} | |
this._layoutCalculate(leftList, center, r, -1); | |
this._layoutCalculate(rightList, center, r, 1); | |
}, | |
/** | |
* @param {Array.<Object>} tList 单系列文本图形集合 | |
* @param {number} direction 水平方向参数,left为-1,right为1 | |
*/ | |
_layoutCalculate : function(tList, center, r, direction) { | |
tList.sort(function(a, b){ | |
return a._rect.y - b._rect.y; | |
}); | |
// 压 | |
function _changeDown(start, end, delta, direction) { | |
for (var j = start; j < end; j++) { | |
tList[j]._rect.y += delta; | |
tList[j].style.y += delta; | |
if (tList[j]._labelLine) { | |
tList[j]._labelLine.style.pointList[1][1] += delta; | |
tList[j]._labelLine.style.pointList[2][1] += delta; | |
} | |
if (j > start | |
&& j + 1 < end | |
&& tList[j + 1]._rect.y > tList[j]._rect.y + tList[j]._rect.height | |
) { | |
_changeUp(j, delta / 2); | |
return; | |
} | |
} | |
_changeUp(end - 1, delta / 2); | |
} | |
// 弹 | |
function _changeUp(end, delta) { | |
for (var j = end; j >= 0; j--) { | |
tList[j]._rect.y -= delta; | |
tList[j].style.y -= delta; | |
if (tList[j]._labelLine) { | |
tList[j]._labelLine.style.pointList[1][1] -= delta; | |
tList[j]._labelLine.style.pointList[2][1] -= delta; | |
} | |
if (j > 0 | |
&& tList[j]._rect.y > tList[j - 1]._rect.y + tList[j - 1]._rect.height | |
) { | |
break; | |
} | |
} | |
} | |
function _changeX(sList, isDownList, center, r, direction) { | |
var x = center[0]; | |
var y = center[1]; | |
var deltaX; | |
var deltaY; | |
var length; | |
var lastDeltaX = direction > 0 | |
? isDownList // 右侧 | |
? Number.MAX_VALUE // 下 | |
: 0 // 上 | |
: isDownList // 左侧 | |
? Number.MAX_VALUE // 下 | |
: 0; // 上 | |
for (var i = 0, l = sList.length; i < l; i++) { | |
deltaY = Math.abs(sList[i]._rect.y - y); | |
length = sList[i]._radius - r; | |
deltaX = (deltaY < r + length) | |
? Math.sqrt( | |
(r + length + 20) * (r + length + 20) | |
- Math.pow(sList[i]._rect.y - y, 2) | |
) | |
: Math.abs( | |
sList[i]._rect.x + (direction > 0 ? 0 : sList[i]._rect.width) - x | |
); | |
if (isDownList && deltaX >= lastDeltaX) { | |
// 右下,左下 | |
deltaX = lastDeltaX - 10; | |
} | |
if (!isDownList && deltaX <= lastDeltaX) { | |
// 右上,左上 | |
deltaX = lastDeltaX + 10; | |
} | |
sList[i]._rect.x = sList[i].style.x = x + deltaX * direction; | |
if (sList[i]._labelLine) { | |
sList[i]._labelLine.style.pointList[2][0] = x + (deltaX - 5) * direction; | |
sList[i]._labelLine.style.pointList[1][0] = x + (deltaX - 20) *direction; | |
} | |
lastDeltaX = deltaX; | |
} | |
} | |
var lastY = 0; | |
var delta; | |
var len = tList.length; | |
var upList = []; | |
var downList = []; | |
for (var i = 0; i < len; i++) { | |
delta = tList[i]._rect.y - lastY; | |
if (delta < 0) { | |
_changeDown(i, len, -delta, direction); | |
} | |
lastY = tList[i]._rect.y + tList[i]._rect.height; | |
} | |
if (this.zr.getHeight() - lastY < 0) { | |
_changeUp(len - 1, lastY - this.zr.getHeight()); | |
} | |
for (var i = 0; i < len; i++) { | |
if (tList[i]._rect.y >= center[1]) { | |
downList.push(tList[i]); | |
} | |
else { | |
upList.push(tList[i]); | |
} | |
} | |
_changeX(downList, true, center, r, direction); | |
_changeX(upList, false, center, r, direction); | |
}, | |
/** | |
* 参数修正&默认值赋值,重载基类方法 | |
* @param {Object} opt 参数 | |
*/ | |
reformOption: function (opt) { | |
// 常用方法快捷方式 | |
var _merge = zrUtil.merge; | |
opt = _merge( | |
_merge( | |
opt || {}, zrUtil.clone(this.ecTheme.pie || {}) | |
), | |
zrUtil.clone(ecConfig.pie) | |
); | |
// 通用字体设置 | |
opt.itemStyle.normal.label.textStyle = this.getTextStyle( | |
opt.itemStyle.normal.label.textStyle | |
); | |
opt.itemStyle.emphasis.label.textStyle = this.getTextStyle( | |
opt.itemStyle.emphasis.label.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 'polyline' : | |
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 === 'polyline' | |
) { | |
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 'polyline': | |
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.refreshNextFrame(); | |
} | |
}; | |
zrUtil.inherits(Pie, ChartBase); | |
// 图表注册 | |
require('../chart').define('pie', Pie); | |
return Pie; | |
}); |