/** * echarts组件: 类目轴 * * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) * */ define(function (require) { var Base = require('./base'); // 图形依赖 var TextShape = require('zrender/shape/Text'); var LineShape = require('zrender/shape/Line'); var RectangleShape = require('zrender/shape/Rectangle'); var ecConfig = require('../config'); // 类目轴 ecConfig.categoryAxis = { zlevel: 0, // 一级层叠 z: 0, // 二级层叠 show: true, position: 'bottom', // 位置 name: '', // 坐标轴名字,默认为空 nameLocation: 'end', // 坐标轴名字位置,支持'start' | 'end' nameTextStyle: {}, // 坐标轴文字样式,默认取全局样式 boundaryGap: true, // 类目起始和结束两端空白策略 axisLine: { // 坐标轴线 show: true, // 默认显示,属性show控制显示与否 onZero: true, lineStyle: { // 属性lineStyle控制线条样式 color: '#48b', width: 2, type: 'solid' } }, axisTick: { // 坐标轴小标记 show: true, // 属性show控制显示与否,默认不显示 interval: 'auto', inside: false, // 控制小标记是否在grid里 // onGap: null, length :5, // 属性length控制线长 lineStyle: { // 属性lineStyle控制线条样式 color: '#333', width: 1 } }, axisLabel: { // 坐标轴文本标签,详见axis.axisLabel show: true, interval: 'auto', rotate: 0, margin: 8, // clickable: false, // formatter: null, textStyle: { // 其余属性默认使用全局文本样式,详见TEXTSTYLE color: '#333' } }, splitLine: { // 分隔线 show: true, // 默认显示,属性show控制显示与否 // onGap: null, lineStyle: { // 属性lineStyle(详见lineStyle)控制线条样式 color: ['#ccc'], width: 1, type: 'solid' } }, splitArea: { // 分隔区域 show: false, // 默认不显示,属性show控制显示与否 // onGap: null, areaStyle: { // 属性areaStyle(详见areaStyle)控制区域样式 color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)'] } } }; var zrUtil = require('zrender/tool/util'); var zrArea = require('zrender/tool/area'); /** * 构造函数 * @param {Object} messageCenter echart消息中心 * @param {ZRender} zr zrender实例 * @param {Object} option 类目轴参数 * @param {Grid} component 组件 */ function CategoryAxis(ecTheme, messageCenter, zr, option, myChart, axisBase) { if (option.data.length < 1) { console.error('option.data.length < 1.'); return; } Base.call(this, ecTheme, messageCenter, zr, option, myChart); this.grid = this.component.grid; for (var method in axisBase) { this[method] = axisBase[method]; } this.refresh(option); } CategoryAxis.prototype = { type : ecConfig.COMPONENT_TYPE_AXIS_CATEGORY, _getReformedLabel : function (idx) { var data = this.getDataFromOption(this.option.data[idx]); var formatter = this.option.data[idx].formatter || this.option.axisLabel.formatter; if (formatter) { if (typeof formatter == 'function') { data = formatter.call(this.myChart, data); } else if (typeof formatter == 'string') { data = formatter.replace('{value}', data); } } return data; }, /** * 计算标签显示挑选间隔 */ _getInterval : function () { var interval = this.option.axisLabel.interval; if (interval == 'auto') { // 麻烦的自适应计算 var fontSize = this.option.axisLabel.textStyle.fontSize; var data = this.option.data; var dataLength = this.option.data.length; if (this.isHorizontal()) { // 横向 if (dataLength > 3) { var gap = this.getGap(); var isEnough = false; var labelSpace; var labelSize; var step = Math.floor(0.5 / gap); step = step < 1 ? 1 : step; interval = Math.floor(15 / gap); while (!isEnough && interval < dataLength) { interval += step; isEnough = true; labelSpace = Math.floor(gap * interval); // 标签左右至少间隔为3px for (var i = Math.floor((dataLength - 1)/ interval) * interval; i >= 0; i -= interval ) { if (this.option.axisLabel.rotate !== 0) { // 有旋转 labelSize = fontSize; } else if (data[i].textStyle) { labelSize = zrArea.getTextWidth( this._getReformedLabel(i), this.getFont( zrUtil.merge( data[i].textStyle, this.option.axisLabel.textStyle ) ) ); } else { /* labelSize = zrArea.getTextWidth( this._getReformedLabel(i), font ); */ // 不定义data级特殊文本样式,用fontSize优化getTextWidth var label = this._getReformedLabel(i) + ''; var wLen = (label.match(/\w/g) || '').length; var oLen = label.length - wLen; labelSize = wLen * fontSize * 2 / 3 + oLen * fontSize; } if (labelSpace < labelSize) { // 放不下,中断循环让interval++ isEnough = false; break; } } } } else { // 少于3个则全部显示 interval = 1; } } else { // 纵向 if (dataLength > 3) { var gap = this.getGap(); interval = Math.floor(11 / gap); // 标签上下至少间隔为3px while ((gap * interval - 6) < fontSize && interval < dataLength ) { interval++; } } else { // 少于3个则全部显示 interval = 1; } } } else { // 用户自定义间隔,支持funtion interval = typeof interval == 'function' ? 1 : (interval - 0 + 1); } return interval; }, /** * 绘制图形 */ _buildShape : function () { // 标签显示的挑选间隔 this._interval = this._getInterval(); if (!this.option.show) { return; } this.option.splitArea.show && this._buildSplitArea(); this.option.splitLine.show && this._buildSplitLine(); this.option.axisLine.show && this._buildAxisLine(); this.option.axisTick.show && this._buildAxisTick(); this.option.axisLabel.show && this._buildAxisLabel(); for (var i = 0, l = this.shapeList.length; i < l; i++) { this.zr.addShape(this.shapeList[i]); } }, // 小标记 _buildAxisTick : function () { var axShape; var data = this.option.data; var dataLength = this.option.data.length; var tickOption = this.option.axisTick; var length = tickOption.length; var color = tickOption.lineStyle.color; var lineWidth = tickOption.lineStyle.width; var intervalFunction = typeof tickOption.interval == 'function' ? tickOption.interval : tickOption.interval == 'auto' ? typeof this.option.axisLabel.interval == 'function' ? this.option.axisLabel.interval : false : false; var interval = intervalFunction ? 1 : tickOption.interval == 'auto' ? this._interval : (tickOption.interval - 0 + 1); var onGap = tickOption.onGap; var optGap = onGap ? (this.getGap() / 2) : typeof onGap == 'undefined' ? (this.option.boundaryGap ? (this.getGap() / 2) : 0) : 0; var startIndex = optGap > 0 ? -interval : 0; if (this.isHorizontal()) { // 横向 var yPosition = this.option.position == 'bottom' ? (tickOption.inside ? (this.grid.getYend() - length - 1) : (this.grid.getYend() + 1)) : (tickOption.inside ? (this.grid.getY() + 1) : (this.grid.getY() - length - 1)); var x; for (var i = startIndex; i < dataLength; i += interval) { if (intervalFunction && !intervalFunction(i, data[i])) { // 回调并且回调返回false则跳过渲染 continue; } // 亚像素优化 x = this.subPixelOptimize( this.getCoordByIndex(i) + (i >= 0 ? optGap : 0), lineWidth ); axShape = { _axisShape : 'axisTick', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { xStart : x, yStart : yPosition, xEnd : x, yEnd : yPosition + length, strokeColor : color, lineWidth : lineWidth } }; this.shapeList.push(new LineShape(axShape)); } } else { // 纵向 var xPosition = this.option.position == 'left' ? (tickOption.inside ? (this.grid.getX() + 1) : (this.grid.getX() - length - 1)) : (tickOption.inside ? (this.grid.getXend() - length - 1) : (this.grid.getXend() + 1)); var y; for (var i = startIndex; i < dataLength; i += interval) { if (intervalFunction && !intervalFunction(i, data[i])) { // 回调并且回调返回false则中断渲染 continue; } // 亚像素优化 y = this.subPixelOptimize( this.getCoordByIndex(i) - (i >= 0 ? optGap : 0), lineWidth ); axShape = { _axisShape : 'axisTick', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { xStart : xPosition, yStart : y, xEnd : xPosition + length, yEnd : y, strokeColor : color, lineWidth : lineWidth } }; this.shapeList.push(new LineShape(axShape)); } } }, // 坐标轴文本 _buildAxisLabel : function () { var axShape; var data = this.option.data; var dataLength = this.option.data.length; var labelOption = this.option.axisLabel; var rotate = labelOption.rotate; var margin = labelOption.margin; var clickable = labelOption.clickable; var textStyle = labelOption.textStyle; var intervalFunction = typeof labelOption.interval == 'function' ? labelOption.interval : false; var dataTextStyle; if (this.isHorizontal()) { // 横向 var yPosition; var baseLine; if (this.option.position == 'bottom') { yPosition = this.grid.getYend() + margin; baseLine = 'top'; } else { yPosition = this.grid.getY() - margin; baseLine = 'bottom'; } for (var i = 0; i < dataLength; i += this._interval) { if ((intervalFunction && !intervalFunction(i, data[i])) // 回调并且回调返回false则中断渲染 || this._getReformedLabel(i) === '' // 空文本优化 ) { continue; } dataTextStyle = zrUtil.merge( data[i].textStyle || {}, textStyle ); axShape = { // shape : 'text', zlevel: this.getZlevelBase(), z: this.getZBase() + 3, hoverable : false, style : { x : this.getCoordByIndex(i), y : yPosition, color : dataTextStyle.color, text : this._getReformedLabel(i), textFont : this.getFont(dataTextStyle), textAlign : dataTextStyle.align || 'center', textBaseline : dataTextStyle.baseline || baseLine } }; if (rotate) { axShape.style.textAlign = rotate > 0 ? (this.option.position == 'bottom' ? 'right' : 'left') : (this.option.position == 'bottom' ? 'left' : 'right'); axShape.rotation = [ rotate * Math.PI / 180, axShape.style.x, axShape.style.y ]; } this.shapeList.push(new TextShape( this._axisLabelClickable(clickable, axShape) )); } } else { // 纵向 var xPosition; var align; if (this.option.position == 'left') { xPosition = this.grid.getX() - margin; align = 'right'; } else { xPosition = this.grid.getXend() + margin; align = 'left'; } for (var i = 0; i < dataLength; i += this._interval) { if ((intervalFunction && !intervalFunction(i, data[i])) // 回调并且回调返回false则中断渲染 || this._getReformedLabel(i) === '' // 空文本优化 ) { continue; } dataTextStyle = zrUtil.merge( data[i].textStyle || {}, textStyle ); axShape = { // shape : 'text', zlevel: this.getZlevelBase(), z: this.getZBase() + 3, hoverable : false, style : { x : xPosition, y : this.getCoordByIndex(i), color : dataTextStyle.color, text : this._getReformedLabel(i), textFont : this.getFont(dataTextStyle), textAlign : dataTextStyle.align || align, textBaseline : dataTextStyle.baseline || (i === 0 && this.option.name !== '') ? 'bottom' : (i == (dataLength - 1) && this.option.name !== '') ? 'top' : 'middle' } }; if (rotate) { axShape.rotation = [ rotate * Math.PI / 180, axShape.style.x, axShape.style.y ]; } this.shapeList.push(new TextShape( this._axisLabelClickable(clickable, axShape) )); } } }, _buildSplitLine : function () { var axShape; var data = this.option.data; var dataLength = this.option.data.length; var sLineOption = this.option.splitLine; var lineType = sLineOption.lineStyle.type; var lineWidth = sLineOption.lineStyle.width; var color = sLineOption.lineStyle.color; color = color instanceof Array ? color : [color]; var colorLength = color.length; // splitLine随axisLable var intervalFunction = typeof this.option.axisLabel.interval == 'function' ? this.option.axisLabel.interval : false; var onGap = sLineOption.onGap; var optGap = onGap ? (this.getGap() / 2) : typeof onGap == 'undefined' ? (this.option.boundaryGap ? (this.getGap() / 2) : 0) : 0; dataLength -= (onGap || (typeof onGap == 'undefined' && this.option.boundaryGap)) ? 1 : 0; if (this.isHorizontal()) { // 横向 var sy = this.grid.getY(); var ey = this.grid.getYend(); var x; for (var i = 0; i < dataLength; i += this._interval) { if (intervalFunction && !intervalFunction(i, data[i])) { // 回调并且回调返回false则跳过渲染 continue; } // 亚像素优化 x = this.subPixelOptimize( this.getCoordByIndex(i) + optGap, lineWidth ); axShape = { // shape : 'line', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { xStart : x, yStart : sy, xEnd : x, yEnd : ey, strokeColor : color[(i / this._interval) % colorLength], lineType : lineType, lineWidth : lineWidth } }; this.shapeList.push(new LineShape(axShape)); } } else { // 纵向 var sx = this.grid.getX(); var ex = this.grid.getXend(); var y; for (var i = 0; i < dataLength; i += this._interval) { if (intervalFunction && !intervalFunction(i, data[i])) { // 回调并且回调返回false则跳过渲染 continue; } // 亚像素优化 y = this.subPixelOptimize( this.getCoordByIndex(i) - optGap, lineWidth ); axShape = { // shape : 'line', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { xStart : sx, yStart : y, xEnd : ex, yEnd : y, strokeColor : color[(i / this._interval) % colorLength], lineType : lineType, lineWidth : lineWidth } }; this.shapeList.push(new LineShape(axShape)); } } }, _buildSplitArea : function () { var axShape; var data = this.option.data; var sAreaOption = this.option.splitArea; var color = sAreaOption.areaStyle.color; if (!(color instanceof Array)) { // 非数组一律认为是单一颜色的字符串,单一颜色则用一个背景,颜色错误不负责啊!!! axShape = { // shape : 'rectangle', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { x : this.grid.getX(), y : this.grid.getY(), width : this.grid.getWidth(), height : this.grid.getHeight(), color : color // type : this.option.splitArea.areaStyle.type, } }; this.shapeList.push(new RectangleShape(axShape)); } else { // 多颜色 var colorLength = color.length; var dataLength = this.option.data.length; // splitArea随axisLable var intervalFunction = typeof this.option.axisLabel.interval == 'function' ? this.option.axisLabel.interval : false; var onGap = sAreaOption.onGap; var optGap = onGap ? (this.getGap() / 2) : typeof onGap == 'undefined' ? (this.option.boundaryGap ? (this.getGap() / 2) : 0) : 0; if (this.isHorizontal()) { // 横向 var y = this.grid.getY(); var height = this.grid.getHeight(); var lastX = this.grid.getX(); var curX; for (var i = 0; i <= dataLength; i += this._interval) { if (intervalFunction && !intervalFunction(i, data[i]) && i < dataLength) { // 回调并且回调返回false则跳过渲染 continue; } curX = i < dataLength ? (this.getCoordByIndex(i) + optGap) : this.grid.getXend(); axShape = { // shape : 'rectangle', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { x : lastX, y : y, width : curX - lastX, height : height, color : color[(i / this._interval) % colorLength] // type : this.option.splitArea.areaStyle.type, } }; this.shapeList.push(new RectangleShape(axShape)); lastX = curX; } } else { // 纵向 var x = this.grid.getX(); var width = this.grid.getWidth(); var lastYend = this.grid.getYend(); var curY; for (var i = 0; i <= dataLength; i += this._interval) { if (intervalFunction && !intervalFunction(i, data[i]) && i < dataLength) { // 回调并且回调返回false则跳过渲染 continue; } curY = i < dataLength ? (this.getCoordByIndex(i) - optGap) : this.grid.getY(); axShape = { // shape : 'rectangle', zlevel: this.getZlevelBase(), z: this.getZBase(), hoverable : false, style : { x : x, y : curY, width : width, height : lastYend - curY, color : color[(i / this._interval) % colorLength] // type : this.option.splitArea.areaStyle.type } }; this.shapeList.push(new RectangleShape(axShape)); lastYend = curY; } } } }, /** * 刷新 */ refresh : function (newOption) { if (newOption) { this.option = this.reformOption(newOption); // 通用字体设置 this.option.axisLabel.textStyle = this.getTextStyle( this.option.axisLabel.textStyle ); } this.clear(); this._buildShape(); }, /** * 返回间隔 */ getGap : function () { var dataLength = this.option.data.length; var total = this.isHorizontal() ? this.grid.getWidth() : this.grid.getHeight(); if (this.option.boundaryGap) { // 留空 return total / dataLength; } else { // 顶头 return total / (dataLength > 1 ? (dataLength - 1) : 1); } }, // 根据值换算位置 getCoord : function (value) { var data = this.option.data; var dataLength = data.length; var gap = this.getGap(); var position = this.option.boundaryGap ? (gap / 2) : 0; for (var i = 0; i < dataLength; i++) { if (this.getDataFromOption(data[i]) == value) { if (this.isHorizontal()) { // 横向 position = this.grid.getX() + position; } else { // 纵向 position = this.grid.getYend() - position; } return position; // Math.floor可能引起一些偏差,但性能会更好 /* 准确更重要 return (i === 0 || i == dataLength - 1) ? position : Math.floor(position); */ } position += gap; } }, // 根据类目轴数据索引换算位置 getCoordByIndex : function (dataIndex) { if (dataIndex < 0) { if (this.isHorizontal()) { return this.grid.getX(); } else { return this.grid.getYend(); } } else if (dataIndex > this.option.data.length - 1) { if (this.isHorizontal()) { return this.grid.getXend(); } else { return this.grid.getY(); } } else { var gap = this.getGap(); var position = this.option.boundaryGap ? (gap / 2) : 0; position += dataIndex * gap; if (this.isHorizontal()) { // 横向 position = this.grid.getX() + position; } else { // 纵向 position = this.grid.getYend() - position; } return position; /* 准确更重要 return (dataIndex === 0 || dataIndex == this.option.data.length - 1) ? position : Math.floor(position); */ } }, // 根据类目轴数据索引换算类目轴名称 getNameByIndex : function (dataIndex) { return this.getDataFromOption(this.option.data[dataIndex]); }, // 根据类目轴名称换算类目轴数据索引 getIndexByName : function (name) { var data = this.option.data; var dataLength = data.length; for (var i = 0; i < dataLength; i++) { if (this.getDataFromOption(data[i]) == name) { return i; } } return -1; }, // 根据位置换算值 getValueFromCoord : function() { return ''; }, /** * 根据类目轴数据索引返回是否为主轴线 * @param {number} dataIndex 类目轴数据索引 * @return {boolean} 是否为主轴 */ isMainAxis : function (dataIndex) { return dataIndex % this._interval === 0; } }; zrUtil.inherits(CategoryAxis, Base); require('../component').define('categoryAxis', CategoryAxis); return CategoryAxis; });