You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1142 lines
44 KiB
1142 lines
44 KiB
/**
|
|
* 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
|
|
);
|
|
this.z = opt.z;
|
|
this.zlevel = opt.zlevel;
|
|
return opt;
|
|
},
|
|
|
|
/**
|
|
* 刷新
|
|
*/
|
|
refresh: function (newOption) {
|
|
if (newOption) {
|
|
this.option = newOption;
|
|
this.series = newOption.series;
|
|
}
|
|
|
|
this.backupShapeList();
|
|
this._buildShape();
|
|
},
|
|
|
|
/**
|
|
* 动态数据增加动画
|
|
*/
|
|
addDataAnimation: function (params, done) {
|
|
var series = this.series;
|
|
var aniMap = {}; // seriesIndex索引参数
|
|
for (var i = 0, l = params.length; i < l; i++) {
|
|
aniMap[params[i][0]] = params[i];
|
|
}
|
|
|
|
var aniCount = 0;
|
|
function animationDone() {
|
|
aniCount--;
|
|
if (aniCount === 0) {
|
|
done && done();
|
|
}
|
|
}
|
|
|
|
// 构建新的饼图匹配差异做动画
|
|
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') {
|
|
aniCount++;
|
|
// 原有扇形
|
|
this.zr.animate(backupShapeList[i].id, 'style')
|
|
.when(
|
|
400,
|
|
{
|
|
startAngle: targeSector.style.startAngle,
|
|
endAngle: targeSector.style.endAngle
|
|
}
|
|
)
|
|
.done(animationDone)
|
|
.start();
|
|
}
|
|
else {
|
|
aniCount++;
|
|
// 删除的扇形
|
|
this.zr.animate(backupShapeList[i].id, 'style')
|
|
.when(
|
|
400,
|
|
deltaIdxMap[seriesIndex] < 0
|
|
? { startAngle: backupShapeList[i].style.startAngle }
|
|
: { endAngle: backupShapeList[i].style.endAngle }
|
|
)
|
|
.done(animationDone)
|
|
.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':
|
|
aniCount++;
|
|
targeSector = textMap[key];
|
|
this.zr.animate(backupShapeList[i].id, 'style')
|
|
.when(
|
|
400,
|
|
{
|
|
x :targeSector.style.x,
|
|
y :targeSector.style.y
|
|
}
|
|
)
|
|
.done(animationDone)
|
|
.start();
|
|
break;
|
|
case 'polyline':
|
|
aniCount++;
|
|
targeSector = lineMap[key];
|
|
this.zr.animate(backupShapeList[i].id, 'style')
|
|
.when(
|
|
400,
|
|
{
|
|
pointList:targeSector.style.pointList
|
|
}
|
|
)
|
|
.done(animationDone)
|
|
.start();
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.shapeList = backupShapeList;
|
|
|
|
// 没有动画
|
|
if (!aniCount) {
|
|
animationDone();
|
|
}
|
|
},
|
|
|
|
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);
|
|
}
|
|
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.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;
|
|
});
|
|
|