blob: 708cd4b663d8351eba85e4c7722a5454f7783937 [file] [log] [blame]
/**
* echarts组件类:极坐标
*
* @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
* @author Neil (杨骥, 511415343@qq.com)
*
*/
define(function (require) {
var Base = require('./base');
// 图形依赖
var TextShape = require('zrender/shape/Text');
var LineShape = require('zrender/shape/Line');
var PolygonShape = require('zrender/shape/Polygon');
var Circle = require('zrender/shape/Circle');
var Ring = require('zrender/shape/Ring');
var ecConfig = require('../config');
ecConfig.polar = {
zlevel: 0, // 一级层叠
z: 0, // 二级层叠
center: ['50%', '50%'], // 默认全局居中
radius: '75%',
startAngle: 90,
boundaryGap: [0, 0], // 数值起始和结束两端空白策略
splitNumber: 5,
name: {
show: true,
// formatter: null,
textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE
color: '#333'
}
},
axisLine: { // 坐标轴线
show: true, // 默认显示,属性show控制显示与否
lineStyle: { // 属性lineStyle控制线条样式
color: '#ccc',
width: 1,
type: 'solid'
}
},
axisLabel: { // 坐标轴文本标签,详见axis.axisLabel
show: false,
// formatter: null,
textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE
color: '#333'
}
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)']
}
},
splitLine: {
show: true,
lineStyle: {
width: 1,
color: '#ccc'
}
},
type: 'polygon'
// indicator: []
};
var zrUtil = require('zrender/tool/util');
var ecCoordinates = require('../util/coordinates');
function Polar(ecTheme, messageCenter, zr, option, myChart) {
Base.call(this, ecTheme, messageCenter, zr, option, myChart);
this.refresh(option);
}
Polar.prototype = {
type : ecConfig.COMPONENT_TYPE_POLAR,
/**
* 绘制图形
*/
_buildShape : function () {
for (var i = 0; i < this.polar.length; i ++) {
this._index = i;
this.reformOption(this.polar[i]);
this._queryTarget = [this.polar[i], this.option];
this._createVector(i);
this._buildSpiderWeb(i);
this._buildText(i);
this._adjustIndicatorValue(i);
this._addAxisLabel(i);
}
for (var i = 0; i < this.shapeList.length; i ++) {
this.zr.addShape(this.shapeList[i]);
}
},
/**
* 生成蜘蛛网顶点坐标
* @param {number} polar的index
*/
_createVector : function (index) {
var item = this.polar[index];
var indicator = this.deepQuery(this._queryTarget, 'indicator');
var length = indicator.length;
var startAngle = item.startAngle ;
var dStep = 2 * Math.PI / length;
var radius = this._getRadius();
var __ecIndicator = item.__ecIndicator = [];
var vector;
for (var i = 0 ;i < length ; i ++) {
vector = ecCoordinates.polar2cartesian(
radius, startAngle * Math.PI / 180 + dStep * i
);
__ecIndicator.push({
// 将图形翻转
vector : [vector[1], -vector[0]]
});
}
},
/**
* 获取外圈的半径
*
* @return {number}
*/
_getRadius : function () {
var item = this.polar[this._index];
return this.parsePercent(
item.radius,
Math.min(this.zr.getWidth(), this.zr.getHeight()) / 2
);
},
/**
* 构建蜘蛛网
* @param {number} polar的index
*/
_buildSpiderWeb : function (index) {
var item = this.polar[index];
var __ecIndicator = item.__ecIndicator;
var splitArea = item.splitArea;
var splitLine = item.splitLine;
var center = this.getCenter(index);
var splitNumber = item.splitNumber;
var strokeColor = splitLine.lineStyle.color;
var lineWidth = splitLine.lineStyle.width;
var show = splitLine.show;
var axisLine = this.deepQuery(this._queryTarget, 'axisLine');
this._addArea(
__ecIndicator, splitNumber, center,
splitArea, strokeColor, lineWidth, show
);
axisLine.show && this._addLine(
__ecIndicator, center, axisLine
);
},
/**
* 绘制axisLabel
*/
_addAxisLabel : function (index) {
var accMath = require('../util/accMath');
var item = this.polar[index];
var indicator = this.deepQuery(this._queryTarget, 'indicator');
var __ecIndicator = item.__ecIndicator;
var axisLabel;
var vector;
var style;
var newStyle;
var splitNumber = this.deepQuery(this._queryTarget, 'splitNumber');
var center = this.getCenter(index);
var vector;
var value;
var text;
var theta;
// var startAngle = this.deepQuery(this._queryTarget, 'startAngle');
var offset;
var interval;
for (var i = 0; i < indicator.length; i ++) {
axisLabel = this.deepQuery(
[indicator[i], item, this.option], 'axisLabel'
);
if (axisLabel.show) {
var textStyle = this.deepQuery([axisLabel, item, this.option], 'textStyle');
style = {};
style.textFont = this.getFont(textStyle);
style.color = textStyle.color;
style = zrUtil.merge(style, axisLabel);
style.lineWidth = style.width;
vector = __ecIndicator[i].vector;
value = __ecIndicator[i].value;
theta = i / indicator.length * 2 * Math.PI;
offset = axisLabel.offset || 10;
interval = axisLabel.interval || 0;
if (!value) {
return;
}
for (var j = 1 ; j <= splitNumber; j += interval + 1) {
newStyle = zrUtil.merge({}, style);
text = accMath.accAdd(value.min, accMath.accMul(value.step, j));
newStyle.text = this.numAddCommas(text);
newStyle.x = j * vector[0] / splitNumber
+ Math.cos(theta) * offset + center[0];
newStyle.y = j * vector[1] / splitNumber
+ Math.sin(theta) * offset + center[1];
this.shapeList.push(new TextShape({
zlevel: this.getZlevelBase(),
z: this.getZBase(),
style : newStyle,
draggable : false,
hoverable : false
}));
}
}
}
},
/**
* 绘制坐标头的文字
* @param {number} polar的index
*/
_buildText : function (index) {
var item = this.polar[index];
var __ecIndicator = item.__ecIndicator;
var vector;
var indicator = this.deepQuery(this._queryTarget, 'indicator');
var center = this.getCenter(index);
var style;
var textAlign;
var name;
var rotation;
var x = 0;
var y = 0;
var margin;
var textStyle;
for (var i = 0; i < indicator.length; i ++) {
name = this.deepQuery(
[indicator[i], item, this.option], 'name'
);
if (!name.show) {
continue;
}
textStyle = this.deepQuery(
[name, item, this.option],
'textStyle'
);
style = {};
style.textFont = this.getFont(textStyle);
style.color = textStyle.color;
if (typeof name.formatter == 'function') {
style.text = name.formatter.call(this.myChart, indicator[i].text, i);
}
else if (typeof name.formatter == 'string'){
style.text = name.formatter.replace(
'{value}', indicator[i].text
);
}
else {
style.text = indicator[i].text;
}
__ecIndicator[i].text = style.text;
vector = __ecIndicator[i].vector;
if (Math.round(vector[0]) > 0) {
textAlign = 'left';
}
else if (Math.round(vector[0]) < 0) {
textAlign = 'right';
}
else {
textAlign = 'center';
}
if (name.margin == null) {
vector = this._mapVector(vector, center, 1.1);
}
else {
margin = name.margin;
x = vector[0] > 0 ? margin : - margin;
y = vector[1] > 0 ? margin : - margin;
x = vector[0] === 0 ? 0 : x;
y = vector[1] === 0 ? 0 : y;
vector = this._mapVector(vector, center, 1);
}
style.textAlign = textAlign;
style.x = vector[0] + x;
style.y = vector[1] + y;
if (name.rotate) {
rotation = [
name.rotate / 180 * Math.PI,
vector[0], vector[1]
];
}
else {
rotation = [0, 0, 0];
}
this.shapeList.push(new TextShape({
zlevel: this.getZlevelBase(),
z: this.getZBase(),
style : style,
draggable : false,
hoverable : false,
rotation : rotation
}));
}
},
getIndicatorText : function(polarIndex, indicatorIndex) {
return this.polar[polarIndex]
&& this.polar[polarIndex].__ecIndicator[indicatorIndex]
&& this.polar[polarIndex].__ecIndicator[indicatorIndex].text;
},
/**
* 添加一个隐形的盒子 当做drop的容器 暴露给外部的图形类使用
* @param {number} polar的index
* @return {Object} 添加的盒子图形
*/
getDropBox : function (index) {
var index = index || 0;
var item = this.polar[index];
var center = this.getCenter(index);
var __ecIndicator = item.__ecIndicator;
var len = __ecIndicator.length;
var pointList = [];
var vector;
var shape;
var type = item.type;
if (type == 'polygon') {
for (var i = 0; i < len; i ++) {
vector = __ecIndicator[i].vector;
pointList.push(this._mapVector(vector, center, 1.2));
}
shape = this._getShape(
pointList, 'fill', 'rgba(0,0,0,0)', '', 1
);
} else if (type == 'circle') {
shape = this._getCircle(
'', 1, 1.2, center, 'fill', 'rgba(0,0,0,0)'
);
}
return shape;
},
/**
* 绘制蜘蛛网的正n变形
*
* @param {Array<Object>} 指标数组
* @param {number} 分割线数量
* @param {Array<number>} 中点坐标
* @param {Object} 分割区域对象
* @param {string} 线条颜色
* @param {number} 线条宽度
*/
_addArea : function (
__ecIndicator, splitNumber, center,
splitArea, strokeColor, lineWidth, show
) {
var shape;
var scale;
var scale1;
var pointList;
var type = this.deepQuery(this._queryTarget, 'type');
for (var i = 0; i < splitNumber ; i ++ ) {
scale = (splitNumber - i) / splitNumber;
if (show) {
if (type == 'polygon') {
pointList = this._getPointList(
__ecIndicator, scale, center);
shape = this._getShape(
pointList, 'stroke', '', strokeColor, lineWidth
);
} else if (type == 'circle') {
shape = this._getCircle(
strokeColor, lineWidth, scale, center, 'stroke'
);
}
this.shapeList.push(shape);
}
if (splitArea.show) {
scale1 = (splitNumber - i - 1) / splitNumber;
this._addSplitArea(
__ecIndicator, splitArea, scale, scale1, center, i
);
}
}
},
/**
* 绘制圆
*
* @param {string} strokeColor
* @param {number} lineWidth
* @param {number} scale
* @param {Array.<number>} center
* @param {string} brushType
* @param {string} color
* @return {Circle}
*/
_getCircle : function (
strokeColor, lineWidth, scale, center, brushType, color
) {
var radius = this._getRadius();
return new Circle({
zlevel: this.getZlevelBase(),
z: this.getZBase(),
style: {
x: center[0],
y: center[1],
r: radius * scale,
brushType: brushType,
strokeColor: strokeColor,
lineWidth: lineWidth,
color: color
},
hoverable : false,
draggable : false
});
},
/**
* 绘制圆环
*
* @param {string} color 间隔颜色
* @param {number} scale0 小圆的scale
* @param {number} scale1 大圆的scale
* @param {Array.<number>} center 圆点
* @return {Ring}
*/
_getRing : function (color, scale0, scale1, center) {
var radius = this._getRadius();
return new Ring({
zlevel: this.getZlevelBase(),
z: this.getZBase(),
style : {
x : center[0],
y : center[1],
r : scale0 * radius,
r0 : scale1 * radius,
color : color,
brushType : 'fill'
},
hoverable : false,
draggable : false
});
},
/**
* 获取需要绘制的多边形的点集
* @param {Object} serie的指标参数
* @param {number} 缩小的系数
* @param {Array<number>} 中点坐标
*
* @return {Array<Array<number>>} 返回绘制的点集
*/
_getPointList : function (__ecIndicator, scale, center) {
var pointList = [];
var len = __ecIndicator.length;
var vector;
for (var i = 0 ; i < len ; i ++ ) {
vector = __ecIndicator[i].vector;
pointList.push(this._mapVector(vector, center, scale));
}
return pointList;
},
/**
* 获取绘制的图形
* @param {Array<Array<number>>} 绘制的点集
* @param {string} 绘制方式 stroke | fill | both 描边 | 填充 | 描边 + 填充
* @param {string} 颜色
* @param {string} 描边颜色
* @param {number} 线条宽度
* @return {Object} 绘制的图形对象
*/
_getShape : function (
pointList, brushType, color, strokeColor, lineWidth
) {
return new PolygonShape({
zlevel: this.getZlevelBase(),
z: this.getZBase(),
style : {
pointList : pointList,
brushType : brushType,
color : color,
strokeColor : strokeColor,
lineWidth : lineWidth
},
hoverable : false,
draggable : false
});
},
/**
* 绘制填充区域
*/
_addSplitArea : function (
__ecIndicator, splitArea, scale, scale1, center, colorInd
) {
var indLen = __ecIndicator.length;
var color;
var colorArr = splitArea.areaStyle.color;
var colorLen;
var vector;
var vector1;
var pointList = [];
var indLen = __ecIndicator.length;
var shape;
var type = this.deepQuery(this._queryTarget, 'type');
if (typeof colorArr == 'string') {
colorArr = [colorArr];
}
colorLen = colorArr.length;
color = colorArr[ colorInd % colorLen];
if (type == 'polygon') {
for (var i = 0; i < indLen ; i ++) {
pointList = [];
vector = __ecIndicator[i].vector;
vector1 = __ecIndicator[(i + 1) % indLen].vector;
pointList.push(this._mapVector(vector, center, scale));
pointList.push(this._mapVector(vector, center, scale1));
pointList.push(this._mapVector(vector1, center, scale1));
pointList.push(this._mapVector(vector1, center, scale));
shape = this._getShape(
pointList, 'fill', color, '', 1
);
this.shapeList.push(shape);
}
} else if (type == 'circle') {
shape = this._getRing(color, scale, scale1, center);
this.shapeList.push(shape);
}
},
/**
* 转换坐标
*
* @param {Array<number>} 原始坐标
* @param {Array<number>} 中点坐标
* @param {number} 缩小的倍数
*
* @return {Array<number>} 转换后的坐标
*/
_mapVector : function (vector, center, scale) {
return [
vector[0] * scale + center[0],
vector[1] * scale + center[1]
];
},
/**
* 获取中心点位置 暴露给外部图形类使用
* @param {number} polar的index
*/
getCenter : function (index) {
var index = index || 0;
return this.parseCenter(this.zr, this.polar[index].center);
},
/**
* 绘制从中点出发的线
*
* @param {Array<Object>} 指标对象
* @param {Array<number>} 中点坐标
* @param {string} 线条颜色
* @param {number} 线条宽度
* @param {string} 线条绘制类型
* solid | dotted | dashed 实线 | 点线 | 虚线
*/
_addLine : function (
__ecIndicator, center, axisLine
) {
var indLen = __ecIndicator.length;
var line;
var vector;
var lineStyle = axisLine.lineStyle;
var strokeColor = lineStyle.color;
var lineWidth = lineStyle.width;
var lineType = lineStyle.type;
for (var i = 0; i < indLen ; i ++ ) {
vector = __ecIndicator[i].vector;
line = this._getLine(
center[0], center[1],
vector[0] + center[0],
vector[1] + center[1],
strokeColor, lineWidth, lineType
);
this.shapeList.push(line);
}
},
/**
* 获取线条对象
* @param {number} 出发点横坐标
* @param {number} 出发点纵坐标
* @param {number} 终点横坐标
* @param {number} 终点纵坐标
* @param {string} 线条颜色
* @param {number} 线条宽度
* @param {string} 线条类型
*
* @return {Object} 线条对象
*/
_getLine : function (
xStart, yStart, xEnd, yEnd, strokeColor, lineWidth, lineType
) {
return new LineShape({
zlevel: this.getZlevelBase(),
z: this.getZBase(),
style : {
xStart : xStart,
yStart : yStart,
xEnd : xEnd,
yEnd : yEnd,
strokeColor : strokeColor,
lineWidth : lineWidth,
lineType : lineType
},
hoverable : false
});
},
/**
* 调整指标的值,当indicator中存在max时设置为固定值
* @param {number} polar的index
*/
_adjustIndicatorValue : function (index) {
var item = this.polar[index];
var indicator = this.deepQuery(this._queryTarget, 'indicator');
var len = indicator.length;
var __ecIndicator = item.__ecIndicator;
var max;
var min;
var data = this._getSeriesData(index);
var boundaryGap = item.boundaryGap;
var splitNumber = item.splitNumber;
var scale = item.scale;
var smartSteps = require('../util/smartSteps');
for (var i = 0; i < len ; i ++ ) {
if (typeof indicator[i].max == 'number') {
max = indicator[i].max;
min = indicator[i].min || 0;
}
else {
var value = this._findValue(
data, i, splitNumber, boundaryGap
);
min = value.min;
max = value.max;
}
// 非scale下双正,修正最小值为0
if (!scale && min >= 0 && max >= 0) {
min = 0;
}
// 非scale下双负,修正最大值为0
if (!scale && min <= 0 && max <= 0) {
max = 0;
}
var stepOpt = smartSteps(min, max, splitNumber);
__ecIndicator[i].value = {
min: stepOpt.min,
max: stepOpt.max,
step: stepOpt.step
};
}
},
/**
* 将series中的数据拿出来,如果没有polarIndex属性,默认为零
* @param {number} polar 的index
* @param {Array<Object>} 需要处理的数据
*/
_getSeriesData : function (index) {
var data = [];
var serie;
var serieData;
var legend = this.component.legend;
var polarIndex;
for (var i = 0; i < this.series.length; i ++) {
serie = this.series[i];
if (serie.type != ecConfig.CHART_TYPE_RADAR) {
continue;
}
serieData = serie.data || [];
for (var j = 0; j < serieData.length; j ++) {
polarIndex = this.deepQuery(
[serieData[j], serie, this.option], 'polarIndex'
) || 0;
if (polarIndex == index
&& (!legend || legend.isSelected(serieData[j].name))
) {
data.push(serieData[j]);
}
}
}
return data;
},
/**
* 查找指标合适的值
*
* 如果只有一组数据以数据中的最大值作为最大值 0为最小值
* 如果是多组,使用同一维度的进行比较 选出最大值最小值
* 对它们进行处理
* @param {Object} serie 的 data
* @param {number} index 指标的序号
* @param {number} splitNumber 分段格式
* * @param {boolean} boundaryGap 两端留白
*/
_findValue : function (data, index, splitNumber, boundaryGap) {
var max;
var min;
var one;
if (!data || data.length === 0) {
return;
}
function _compare(item) {
(item > max || max === undefined) && (max = item);
(item < min || min === undefined) && (min = item);
}
if (data.length == 1) {
min = 0;
}
if (data.length != 1) {
for (var i = 0; i < data.length; i ++) {
_compare(this.getDataFromOption(data[i].value[index]));
}
}
else {
one = data[0];
for (var i = 0; i < one.value.length; i ++) {
_compare(this.getDataFromOption(one.value[i]));
}
}
var gap = Math.abs(max - min);
min = min - Math.abs(gap * boundaryGap[0]);
max = max + Math.abs(gap * boundaryGap[1]);
if (min === max) {
if (max === 0) {
// 修复全0数据
max = 1;
}
// 修复最大值==最小值时数据整形
else if (max > 0) {
min = max / splitNumber;
}
else { // max < 0
max = max / splitNumber;
}
}
return {
max : max,
min : min
};
},
/**
* 获取每个指标上某个value对应的坐标
* @param {number} polarIndex
* @param {number} indicatorIndex
* @param {number} value
* @return {Array<number>} 对应坐标
*/
getVector : function (polarIndex, indicatorIndex, value) {
polarIndex = polarIndex || 0;
indicatorIndex = indicatorIndex || 0;
var __ecIndicator = this.polar[polarIndex].__ecIndicator;
if (indicatorIndex >= __ecIndicator.length) {
return ;
}
var indicator = this.polar[polarIndex].__ecIndicator[indicatorIndex];
var center = this.getCenter(polarIndex);
var vector = indicator.vector;
var max = indicator.value.max;
var min = indicator.value.min;
var alpha;
if (typeof value == 'undefined') {
return center;
}
switch (value) {
case 'min' :
value = min;
break;
case 'max' :
value = max;
break;
case 'center' :
value = (max + min) / 2;
break;
}
if (max != min) {
alpha = (value - min) / (max - min);
}
else {
alpha = 0.5;
}
return this._mapVector(vector, center, alpha);
},
/**
* 判断一个点是否在网内
* @param {Array<number>} 坐标
* @return {number} 返回polarindex 返回-1表示不在任何polar
*/
isInside : function (vector) {
var polar = this.getNearestIndex(vector);
if (polar) {
return polar.polarIndex;
}
return -1;
},
/**
* 如果一个点在网内,返回离它最近的数据轴的index
* @param {Array<number>} 坐标
* @return {Object} | false
* polarIndex
* valueIndex
*/
getNearestIndex : function (vector) {
var item;
var center;
var radius;
var polarVector;
var startAngle;
var indicator;
var len;
var angle;
var finalAngle;
for (var i = 0 ; i < this.polar.length; i ++) {
item = this.polar[i];
center = this.getCenter(i);
if (vector[0] == center[0] && vector[1] == center[1]) {
return {
polarIndex : i,
valueIndex : 0
};
}
radius = this._getRadius();
startAngle = item.startAngle;
indicator = item.indicator;
len = indicator.length;
angle = 2 * Math.PI / len;
// 注意y轴的翻转
polarVector = ecCoordinates.cartesian2polar(
vector[0] - center[0], center[1] - vector[1]
);
if (vector[0] - center[0] < 0) {
polarVector[1] += Math.PI;
}
if (polarVector[1] < 0) {
polarVector[1] += 2 * Math.PI;
}
// 减去startAngle的偏移量 再加2PI变成正数
finalAngle = polarVector[1] -
startAngle / 180 * Math.PI + Math.PI * 2;
if (Math.abs(Math.cos(finalAngle % (angle / 2))) * radius
> polarVector[0])
{
return {
polarIndex : i,
valueIndex : Math.floor(
(finalAngle + angle / 2 ) / angle
) % len
};
}
}
},
/**
* 获取指标信息
* @param {number} polarIndex
* @return {Array<Object>} indicator
*/
getIndicator : function (index) {
var index = index || 0;
return this.polar[index].indicator;
},
/**
* 刷新
*/
refresh : function (newOption) {
if (newOption) {
this.option = newOption;
this.polar = this.option.polar;
this.series = this.option.series;
}
this.clear();
this._buildShape();
}
};
zrUtil.inherits(Polar, Base);
require('../component').define('polar', Polar);
return Polar;
});