| /** |
| * echarts图表类:仪表盘 |
| * |
| * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 |
| * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) |
| * |
| */ |
| define(function (require) { |
| var ChartBase = require('./base'); |
| |
| // 图形依赖 |
| var GaugePointerShape = require('../util/shape/GaugePointer'); |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var RectangleShape = require('zrender/shape/Rectangle'); |
| var CircleShape = require('zrender/shape/Circle'); |
| var SectorShape = require('zrender/shape/Sector'); |
| |
| var ecConfig = require('../config'); |
| // 仪表盘默认参数 |
| ecConfig.gauge = { |
| zlevel: 0, // 一级层叠 |
| z: 2, // 二级层叠 |
| center: ['50%', '50%'], // 默认全局居中 |
| clickable: true, |
| legendHoverLink: true, |
| radius: '75%', |
| startAngle: 225, |
| endAngle: -45, |
| min: 0, // 最小值 |
| max: 100, // 最大值 |
| precision: 0, // 小数精度,默认为0,无小数点 |
| splitNumber: 10, // 分割段数,默认为10 |
| axisLine: { // 坐标轴线 |
| show: true, // 默认显示,属性show控制显示与否 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: [[0.2, '#228b22'],[0.8, '#48b'],[1, '#ff4500']], |
| width: 30 |
| } |
| }, |
| axisTick: { // 坐标轴小标记 |
| show: true, // 属性show控制显示与否,默认不显示 |
| splitNumber: 5, // 每份split细分多少段 |
| length :8, // 属性length控制线长 |
| lineStyle: { // 属性lineStyle控制线条样式 |
| color: '#eee', |
| width: 1, |
| type: 'solid' |
| } |
| }, |
| axisLabel: { // 坐标轴文本标签,详见axis.axisLabel |
| show: true, |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: 'auto' |
| } |
| }, |
| splitLine: { // 分隔线 |
| show: true, // 默认显示,属性show控制显示与否 |
| length :30, // 属性length控制线长 |
| lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式 |
| color: '#eee', |
| width: 2, |
| type: 'solid' |
| } |
| }, |
| pointer: { |
| show: true, |
| length: '80%', |
| width: 8, |
| color: 'auto' |
| }, |
| title: { |
| show: true, |
| offsetCenter: [0, '-40%'], // x, y,单位px |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: '#333', |
| fontSize: 15 |
| } |
| }, |
| detail: { |
| show: true, |
| backgroundColor: 'rgba(0,0,0,0)', |
| borderWidth: 0, |
| borderColor: '#ccc', |
| width: 100, |
| height: 40, |
| offsetCenter: [0, '40%'], // x, y,单位px |
| // formatter: null, |
| textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE |
| color: 'auto', |
| fontSize: 30 |
| } |
| } |
| }; |
| |
| var ecData = require('../util/ecData'); |
| var accMath = require('../util/accMath'); |
| var zrUtil = require('zrender/tool/util'); |
| |
| /** |
| * 构造函数 |
| * @param {Object} messageCenter echart消息中心 |
| * @param {ZRender} zr zrender实例 |
| * @param {Object} series 数据 |
| * @param {Object} component 组件 |
| */ |
| function Gauge(ecTheme, messageCenter, zr, option, myChart) { |
| // 图表基类 |
| ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| this.refresh(option); |
| } |
| |
| Gauge.prototype = { |
| type: ecConfig.CHART_TYPE_GAUGE, |
| /** |
| * 绘制图形 |
| */ |
| _buildShape: function () { |
| var series = this.series; |
| // 复用参数索引 |
| this._paramsMap = {}; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type === ecConfig.CHART_TYPE_GAUGE) { |
| series[i] = this.reformOption(series[i]); |
| this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink; |
| this._buildSingleGauge(i); |
| this.buildMark(i); |
| } |
| } |
| |
| this.addShapeList(); |
| }, |
| |
| /** |
| * 构建单个仪表盘 |
| * |
| * @param {number} seriesIndex 系列索引 |
| */ |
| _buildSingleGauge: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| |
| this._paramsMap[seriesIndex] = { |
| center: this.parseCenter(this.zr, serie.center), |
| radius: this.parseRadius(this.zr, serie.radius), |
| startAngle: serie.startAngle.toFixed(2) - 0, |
| endAngle: serie.endAngle.toFixed(2) - 0 |
| }; |
| this._paramsMap[seriesIndex].totalAngle = this._paramsMap[seriesIndex].startAngle |
| - this._paramsMap[seriesIndex].endAngle; |
| |
| this._colorMap(seriesIndex); |
| |
| this._buildAxisLine(seriesIndex); |
| |
| this._buildSplitLine(seriesIndex); |
| |
| this._buildAxisTick(seriesIndex); |
| |
| this._buildAxisLabel(seriesIndex); |
| |
| this._buildPointer(seriesIndex); |
| |
| this._buildTitle(seriesIndex); |
| |
| this._buildDetail(seriesIndex); |
| }, |
| |
| // 轴线 |
| _buildAxisLine: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.axisLine.show) { |
| return; |
| } |
| var min = serie.min; |
| var total = serie.max - min; |
| var params = this._paramsMap[seriesIndex]; |
| var center = params.center; |
| var startAngle = params.startAngle; |
| var totalAngle = params.totalAngle; |
| var colorArray = params.colorArray; |
| var lineStyle = serie.axisLine.lineStyle; |
| var lineWidth = this.parsePercent(lineStyle.width, params.radius[1]); |
| var r = params.radius[1]; |
| var r0 = r - lineWidth; |
| |
| var sectorShape; |
| var lastAngle = startAngle; |
| var newAngle; |
| for (var i = 0, l = colorArray.length; i < l; i++) { |
| newAngle = startAngle - totalAngle * (colorArray[i][0] - min) / total; |
| sectorShape = this._getSector( |
| center, r0, r, |
| newAngle, // startAngle |
| lastAngle, // endAngle |
| colorArray[i][1], // color |
| lineStyle |
| ); |
| lastAngle = newAngle; |
| sectorShape._animationAdd = 'r'; |
| ecData.set(sectorShape, 'seriesIndex', seriesIndex); |
| ecData.set(sectorShape, 'dataIndex', i); |
| this.shapeList.push(sectorShape); |
| } |
| }, |
| |
| // 坐标轴分割线 |
| _buildSplitLine: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.splitLine.show) { |
| return; |
| } |
| |
| var params = this._paramsMap[seriesIndex]; |
| var splitNumber = serie.splitNumber; |
| var min = serie.min; |
| var total = serie.max - min; |
| var splitLine = serie.splitLine; |
| var length = this.parsePercent(splitLine.length, params.radius[1]); |
| var lineStyle = splitLine.lineStyle; |
| var color = lineStyle.color; |
| var center = params.center; |
| var startAngle = params.startAngle * Math.PI / 180; |
| var totalAngle = params.totalAngle * Math.PI / 180; |
| var r = params.radius[1]; |
| var r0 = r - length; |
| |
| var angle; |
| var sinAngle; |
| var cosAngle; |
| for (var i = 0; i <= splitNumber; i++) { |
| angle = startAngle - totalAngle / splitNumber * i; |
| sinAngle = Math.sin(angle); |
| cosAngle = Math.cos(angle); |
| this.shapeList.push(new LineShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 1, |
| hoverable: false, |
| style: { |
| xStart: center[0] + cosAngle * r, |
| yStart: center[1] - sinAngle * r, |
| xEnd: center[0] + cosAngle * r0, |
| yEnd: center[1] - sinAngle * r0, |
| strokeColor: color === 'auto' |
| ? this._getColor(seriesIndex, min + total / splitNumber * i) |
| : color, |
| lineType: lineStyle.type, |
| lineWidth: lineStyle.width, |
| shadowColor: lineStyle.shadowColor, |
| shadowBlur: lineStyle.shadowBlur, |
| shadowOffsetX: lineStyle.shadowOffsetX, |
| shadowOffsetY: lineStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| // 小标记 |
| _buildAxisTick: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.axisTick.show) { |
| return; |
| } |
| |
| var params = this._paramsMap[seriesIndex]; |
| var splitNumber = serie.splitNumber; |
| var min = serie.min; |
| var total = serie.max - min; |
| var axisTick = serie.axisTick; |
| var tickSplit = axisTick.splitNumber; |
| var length = this.parsePercent(axisTick.length, params.radius[1]); |
| var lineStyle = axisTick.lineStyle; |
| var color = lineStyle.color; |
| |
| var center = params.center; |
| var startAngle = params.startAngle * Math.PI / 180; |
| var totalAngle = params.totalAngle * Math.PI / 180; |
| var r = params.radius[1]; |
| var r0 = r - length; |
| |
| var angle; |
| var sinAngle; |
| var cosAngle; |
| for (var i = 0, l = splitNumber * tickSplit; i <= l; i++) { |
| if (i % tickSplit === 0) { // 同splitLine |
| continue; |
| } |
| angle = startAngle - totalAngle / l * i; |
| sinAngle = Math.sin(angle); |
| cosAngle = Math.cos(angle); |
| this.shapeList.push(new LineShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 1, |
| hoverable: false, |
| style: { |
| xStart: center[0] + cosAngle * r, |
| yStart: center[1] - sinAngle * r, |
| xEnd: center[0] + cosAngle * r0, |
| yEnd: center[1] - sinAngle * r0, |
| strokeColor: color === 'auto' |
| ? this._getColor(seriesIndex, min + total / l * i) |
| : color, |
| lineType: lineStyle.type, |
| lineWidth: lineStyle.width, |
| shadowColor: lineStyle.shadowColor, |
| shadowBlur: lineStyle.shadowBlur, |
| shadowOffsetX: lineStyle.shadowOffsetX, |
| shadowOffsetY: lineStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| // 坐标轴文本 |
| _buildAxisLabel: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.axisLabel.show) { |
| return; |
| } |
| |
| var splitNumber = serie.splitNumber; |
| var min = serie.min; |
| var total = serie.max - min; |
| var textStyle = serie.axisLabel.textStyle; |
| var textFont = this.getFont(textStyle); |
| var color = textStyle.color; |
| |
| var params = this._paramsMap[seriesIndex]; |
| var center = params.center; |
| var startAngle = params.startAngle; |
| var totalAngle = params.totalAngle; |
| var r0 = params.radius[1] |
| - this.parsePercent(serie.splitLine.length, params.radius[1]) |
| - 5; |
| |
| var angle; |
| var sinAngle; |
| var cosAngle; |
| var value; |
| for (var i = 0; i <= splitNumber; i++) { |
| value = accMath.accAdd( |
| min , accMath.accMul(accMath.accDiv(total , splitNumber), i) |
| ); |
| angle = startAngle - totalAngle / splitNumber * i; |
| sinAngle = Math.sin(angle * Math.PI / 180); |
| cosAngle = Math.cos(angle * Math.PI / 180); |
| angle = (angle + 360) % 360; |
| this.shapeList.push(new TextShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 1, |
| hoverable: false, |
| style: { |
| x: center[0] + cosAngle * r0, |
| y: center[1] - sinAngle * r0, |
| color: color === 'auto' ? this._getColor(seriesIndex, value) : color, |
| text: this._getLabelText(serie.axisLabel.formatter, value), |
| textAlign: (angle >= 110 && angle <= 250) |
| ? 'left' |
| : (angle <= 70 || angle >= 290) |
| ? 'right' |
| : 'center', |
| textBaseline: (angle >= 10 && angle <= 170) |
| ? 'top' |
| : (angle >= 190 && angle <= 350) |
| ? 'bottom' |
| : 'middle', |
| textFont: textFont, |
| shadowColor: textStyle.shadowColor, |
| shadowBlur: textStyle.shadowBlur, |
| shadowOffsetX: textStyle.shadowOffsetX, |
| shadowOffsetY: textStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| _buildPointer: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.pointer.show) { |
| return; |
| } |
| var total = serie.max - serie.min; |
| var pointer = serie.pointer; |
| |
| var params = this._paramsMap[seriesIndex]; |
| var length = this.parsePercent(pointer.length, params.radius[1]); |
| var width = this.parsePercent(pointer.width, params.radius[1]); |
| var center = params.center; |
| var value = this._getValue(seriesIndex); |
| value = value < serie.max ? value : serie.max; |
| |
| var angle = (params.startAngle - params.totalAngle / total * (value - serie.min)) |
| * Math.PI / 180; |
| var color = pointer.color === 'auto' |
| ? this._getColor(seriesIndex, value) |
| : pointer.color; |
| |
| var pointShape = new GaugePointerShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 1, |
| clickable: this.query(serie, 'clickable'), |
| style: { |
| x: center[0], |
| y: center[1], |
| r: length, |
| startAngle: params.startAngle * Math.PI / 180, |
| angle: angle, |
| color: color, |
| width: width, |
| shadowColor: pointer.shadowColor, |
| shadowBlur: pointer.shadowBlur, |
| shadowOffsetX: pointer.shadowOffsetX, |
| shadowOffsetY: pointer.shadowOffsetY |
| }, |
| highlightStyle: { |
| brushType: 'fill', |
| width: width > 2 ? 2 : (width / 2), |
| color: '#fff' |
| } |
| }); |
| ecData.pack( |
| pointShape, |
| this.series[seriesIndex], seriesIndex, |
| this.series[seriesIndex].data[0], 0, |
| this.series[seriesIndex].data[0].name, |
| value |
| ); |
| this.shapeList.push(pointShape); |
| |
| this.shapeList.push(new CircleShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 2, |
| hoverable: false, |
| style: { |
| x: center[0], |
| y: center[1], |
| r: pointer.width / 2.5, |
| color: '#fff' |
| } |
| })); |
| }, |
| |
| _buildTitle: function(seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.title.show) { |
| return; |
| } |
| |
| var data = serie.data[0]; |
| var name = data.name != null ? data.name : ''; |
| if (name !== '') { // 不要帮我代码规范 |
| var title = serie.title; |
| var offsetCenter = title.offsetCenter; |
| var textStyle = title.textStyle; |
| var textColor = textStyle.color; |
| var params = this._paramsMap[seriesIndex]; |
| var x = params.center[0] + this.parsePercent(offsetCenter[0], params.radius[1]); |
| var y = params.center[1] + this.parsePercent(offsetCenter[1], params.radius[1]); |
| this.shapeList.push(new TextShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + ( |
| (Math.abs(x - params.center[0]) + Math.abs(y - params.center[1])) |
| < textStyle.fontSize * 2 ? 2 : 1 |
| ), |
| hoverable: false, |
| style: { |
| x: x, |
| y: y, |
| color: textColor === 'auto' ? this._getColor(seriesIndex) : textColor, |
| text: name, |
| textAlign: 'center', |
| textFont: this.getFont(textStyle), |
| shadowColor: textStyle.shadowColor, |
| shadowBlur: textStyle.shadowBlur, |
| shadowOffsetX: textStyle.shadowOffsetX, |
| shadowOffsetY: textStyle.shadowOffsetY |
| } |
| })); |
| } |
| }, |
| |
| _buildDetail: function(seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (!serie.detail.show) { |
| return; |
| } |
| |
| var detail = serie.detail; |
| var offsetCenter = detail.offsetCenter; |
| var color = detail.backgroundColor; |
| var textStyle = detail.textStyle; |
| var textColor = textStyle.color; |
| |
| var params = this._paramsMap[seriesIndex]; |
| var value = this._getValue(seriesIndex); |
| var x = params.center[0] - detail.width / 2 |
| + this.parsePercent(offsetCenter[0], params.radius[1]); |
| var y = params.center[1] |
| + this.parsePercent(offsetCenter[1], params.radius[1]); |
| this.shapeList.push(new RectangleShape({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + ( |
| (Math.abs(x + detail.width / 2 - params.center[0]) |
| + Math.abs(y + detail.height / 2 - params.center[1])) < textStyle.fontSize |
| ? 2 : 1 |
| ), |
| hoverable: false, |
| style: { |
| x: x, |
| y: y, |
| width: detail.width, |
| height: detail.height, |
| brushType: 'both', |
| color: color === 'auto' ? this._getColor(seriesIndex, value) : color, |
| lineWidth: detail.borderWidth, |
| strokeColor: detail.borderColor, |
| |
| shadowColor: detail.shadowColor, |
| shadowBlur: detail.shadowBlur, |
| shadowOffsetX: detail.shadowOffsetX, |
| shadowOffsetY: detail.shadowOffsetY, |
| |
| text: this._getLabelText(detail.formatter, value), |
| textFont: this.getFont(textStyle), |
| textPosition: 'inside', |
| textColor: textColor === 'auto' ? this._getColor(seriesIndex, value) : textColor |
| } |
| })); |
| }, |
| |
| _getValue: function(seriesIndex) { |
| return this.getDataFromOption(this.series[seriesIndex].data[0]); |
| }, |
| |
| /** |
| * 颜色索引 |
| */ |
| _colorMap: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| var min = serie.min; |
| var total = serie.max - min; |
| var color = serie.axisLine.lineStyle.color; |
| if (!(color instanceof Array)) { |
| color = [[1, color]]; |
| } |
| var colorArray = []; |
| for (var i = 0, l = color.length; i < l; i++) { |
| colorArray.push([color[i][0] * total + min, color[i][1]]); |
| } |
| this._paramsMap[seriesIndex].colorArray = colorArray; |
| }, |
| |
| /** |
| * 自动颜色 |
| */ |
| _getColor: function (seriesIndex, value) { |
| if (value == null) { |
| value = this._getValue(seriesIndex); |
| } |
| |
| var colorArray = this._paramsMap[seriesIndex].colorArray; |
| for (var i = 0, l = colorArray.length; i < l; i++) { |
| if (colorArray[i][0] >= value) { |
| return colorArray[i][1]; |
| } |
| } |
| return colorArray[colorArray.length - 1][1]; |
| }, |
| |
| /** |
| * 构建扇形 |
| */ |
| _getSector: function (center, r0, r, startAngle, endAngle, color, lineStyle) { |
| return new SectorShape ({ |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase(), |
| hoverable: false, |
| style: { |
| x: center[0], // 圆心横坐标 |
| y: center[1], // 圆心纵坐标 |
| r0: r0, // 圆环内半径 |
| r: r, // 圆环外半径 |
| startAngle: startAngle, |
| endAngle: endAngle, |
| brushType: 'fill', |
| color: color, |
| shadowColor: lineStyle.shadowColor, |
| shadowBlur: lineStyle.shadowBlur, |
| shadowOffsetX: lineStyle.shadowOffsetX, |
| shadowOffsetY: lineStyle.shadowOffsetY |
| } |
| }); |
| }, |
| |
| /** |
| * 根据lable.format计算label text |
| */ |
| _getLabelText: function (formatter, value) { |
| if (formatter) { |
| if (typeof formatter === 'function') { |
| return formatter.call(this.myChart, value); |
| } |
| else if (typeof formatter === 'string') { |
| return formatter.replace('{value}', value); |
| } |
| } |
| return value; |
| }, |
| |
| /** |
| * 刷新 |
| */ |
| refresh: function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| |
| this.backupShapeList(); |
| this._buildShape(); |
| } |
| }; |
| |
| zrUtil.inherits(Gauge, ChartBase); |
| |
| // 图表注册 |
| require('../chart').define('gauge', Gauge); |
| |
| return Gauge; |
| }); |