/** * 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'); var formatter = this.deepQuery([axisLabel, item], 'formatter'); 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)); if (typeof formatter === 'function') { text = formatter(text); } else if (typeof formatter === 'string') { text = formatter.replace('{a}','{a0}') .replace('{a0}', text); } else { text = this.numAddCommas(text); } newStyle.text = 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} 指标数组 * @param {number} 分割线数量 * @param {Array} 中点坐标 * @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.} 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.} 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} 中点坐标 * * @return {Array>} 返回绘制的点集 */ _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>} 绘制的点集 * @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} 原始坐标 * @param {Array} 中点坐标 * @param {number} 缩小的倍数 * * @return {Array} 转换后的坐标 */ _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} 指标对象 * @param {Array} 中点坐标 * @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 opts; 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; opts = { max: max, min: min }; } 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, opts); __ecIndicator[i].value = { min: stepOpt.min, max: stepOpt.max, step: stepOpt.step }; } }, /** * 将series中的数据拿出来,如果没有polarIndex属性,默认为零 * @param {number} polar 的index * @param {Array} 需要处理的数据 */ _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} 对应坐标 */ 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} 坐标 * @return {number} 返回polarindex 返回-1表示不在任何polar */ isInside : function (vector) { var polar = this.getNearestIndex(vector); if (polar) { return polar.polarIndex; } return -1; }, /** * 如果一个点在网内,返回离它最近的数据轴的index * @param {Array} 坐标 * @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} 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; });