Echarts时间坐标轴刻度的改进和优化

来源:互联网 发布:各个国家顶级域名 编辑:程序博客网 时间:2024/05/17 04:44

问题介绍:
Echarts插件的坐标轴类型主要分为时间(time)、数值(value)、对数(log)、类目(category)四大类,当采用二维的直角坐标系时,一般x轴通过采用类目轴,而y轴通常采用value轴。官方的有关的直角坐标系例子,大多遵循这个原则配置的。这样配置其实只适用于离散型数据,而对于时间型数据,虽然有时间轴的支持,但是效果却差强人意,甚至存在展示的缺陷。

如图1所示为日的时间型数据,从图上可以看出柱状图的柱子跟y轴有重叠,x轴刻度值少的问题。如图2、3所示为月和年的时间型数据,问题跟图1差不多,但还有一个不一样的问题,就是刻度值,不管是月还是年的数据,刻度值都显示的日的刻度值,这个对于用户来说,体验应该是不好的。更好的体验应该是,月的时间型数据,显示的刻度值应该都是月的刻度值;年的时间型数据,显示的应该都是年的刻度值。如图4、5、6,是本人优化和处理后的显示效果图。

总的来说,主要存在以下三个问题
(1)不同时间格式下,计算出来的刻度不符合预期;
(2)数据的最小值容易与坐标轴重叠,最大值容易显示在图表之外;
(3)计算出来的刻度个数偏少,数据之间容易重叠。

这里写图片描述

图1 时间格式为日的时间轴展示

这里写图片描述
图2 时间格式为月的时间轴展示

这里写图片描述
图3 时间格式为年的时间轴展示

这里写图片描述
图4 时间格式为日的时间轴展示

这里写图片描述
图5 时间格式为月的时间轴展示

这里写图片描述
图6 时间格式为年的时间轴展示

Echarts时间型刻度的计算方法:

通过阅读Echarts源码,可以了解Echarts对于时间型刻度的计算方法: 在Echarts的架构中, echarts/scale/Time模块负责对时间刻度进行处理和计算,但求解刻度的算法还是采用线性比例尺的算法,即将时间型数据转为时间戳(变为纯数字),然后通过线性比例尺求刻度的方法计算得出时间戳类型的刻度,最后通过时间转换函数,将时间戳刻度,转为时间型刻度。而且在源码里面,将数据的最小值和最大值,默认设置为刻度的最小值和最大值,并且刻度个数通常只有五个,偏少。

改进和优化方法:
针对前文总结出的三点缺陷,现提出以下的改进和优化方法:
(1)针对不同时间格式下刻度的计算问题,借鉴d3.js的时间比例尺接口和方法,并将其整合到Echarts源码中。d3.js针对不同时间格式,采用不同函数计算时间刻度,针对年、月、日、小时分别有year、month、day、hour等函数。不同时间格式函数处理刻度方法稍有不同。具体方法下文会详细介绍。
(2)针对最小值容易与坐标轴重叠,最大值容易显示在图形之外问题。采用保留刻度法予以解决。保留刻度法:即数据最小值与刻度最小值之差为正且小于步长20%,或者数据最小值与刻度最小值之差为负,则最小刻度需要再向下增加一个刻度;若数据最大值与刻度最大值之差为负且绝对值小于步长20%,或者数据最小值与刻度最小值之差为正, 则最大刻度需要再向上增加一个刻度。
(3)针对计算出来的刻度个数偏少问题,采用自适应宽度的方法,力求最大的刻度个数。
规定刻度标签的旋转45度,每一个标签占用30px的宽度,这样,如果图表的宽为600px,则理论上可以在坐标轴上最大放置20个刻度。

具体实现:
(1)通过调试d3.js源码和研究,发现d3.js处理时间比例尺的接口主要是以下代码,代码中newInterval可以看做一个构造函数,传入不同的参数,都会返回interval对象,但interval对象的方法却因为参数和变量值的不一样,而有不同的功能。比如:year对象专门处理只与年相关的比例尺,month对象则专门处理与月相关的比例尺,minute对象则专门处理与分钟相关的比例尺…这些对象有1个方法,是计算比例尺的关键,它就是range(start,stop,step)函数,它接受三个参数,start:数据的开始点(数据最小值),stop:数据的终点(数据最大值),step:相邻刻度的步长。以年为例,假设现在数据最小值是2011,最大值是2028年,步长是2。则通过range函数,计算出来的刻度是[1293811200000, 1356969600000, 1420041600000, 1483200000000, 1546272000000, 1609430400000, 1672502400000, 1735660800000, 1798732800000],接着再将时间戳格式化[“2011-01-01 00:00:00”, “2013-01-01 00:00:00”, “2015-01-01 00:00:00”, “2017-01-01 00:00:00”, “2019-01-01 00:00:00”, “2021-01-01 00:00:00”, “2023-01-01 00:00:00”, “2025-01-01 00:00:00”, “2027-01-01 00:00:00”]。从结果可以看出,计算出来的刻度值只是在年份上递增,满足我们的预期,同理,其他月、日、小时的时间格式比例尺计算方法也跟这个类似。

 function(module, exports, __webpack_require__) {    var t0$1 = new Date;    var t1$1 = new Date;    var timeInterval = {};    function newInterval(floori, offseti, count, field) {      function interval(date) {        return floori(date = new Date(+date)), date;      }      interval.floor = interval;      interval.ceil = function(date) {        return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date;      };      interval.round = function(date) {        var d0 = interval(date),            d1 = interval.ceil(date);        return date - d0 < d1 - date ? d0 : d1;      };      interval.offset = function(date, step) {        return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date;      };      interval.range = function(start, stop, step) {        var range = [];        start = interval.ceil(start);        step = step == null ? 1 : Math.floor(step);        if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date        do range.push(new Date(+start).getTime()); while (offseti(start, step), floori(start), start < stop)        return range;      };      interval.filter = function(test) {        return newInterval(function(date) {          if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1);        }, function(date, step) {          if (date >= date) {            if (step < 0) while (++step <= 0) {              while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty            } else while (--step >= 0) {              while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty            }          }        });      };      if (count) {        interval.count = function(start, end) {          t0$1.setTime(+start), t1$1.setTime(+end);          floori(t0$1), floori(t1$1);          return Math.floor(count(t0$1, t1$1));        };        interval.every = function(step) {          step = Math.floor(step);          return !isFinite(step) || !(step > 0) ? null              : !(step > 1) ? interval              : interval.filter(field                  ? function(d) { return field(d) % step === 0; }                  : function(d) { return interval.count(0, d) % step === 0; });        };      }      return interval;    }    var millisecond = newInterval(function() {      // noop    }, function(date, step) {      date.setTime(+date + step);    }, function(start, end) {      return end - start;    });    // An optimized implementation for this simple case.    millisecond.every = function(k) {      k = Math.floor(k);      if (!isFinite(k) || !(k > 0)) return null;      if (!(k > 1)) return millisecond;      return newInterval(function(date) {        date.setTime(Math.floor(date / k) * k);      }, function(date, step) {        date.setTime(+date + step * k);      }, function(start, end) {        return (end - start) / k;      });    };    var milliseconds = millisecond.range;    var durationSecond$1 = 1e3;    var durationMinute$1 = 6e4;    var durationHour$1 = 36e5;    var durationDay$1 = 864e5;    var durationWeek$1 = 6048e5;    var second = newInterval(function(date) {      date.setTime(Math.floor(date / durationSecond$1) * durationSecond$1);    }, function(date, step) {      date.setTime(+date + step * durationSecond$1);    }, function(start, end) {      return (end - start) / durationSecond$1;    }, function(date) {      return date.getUTCSeconds();    });    var seconds = second.range;    var minute = newInterval(function(date) {      date.setTime(Math.floor(date / durationMinute$1) * durationMinute$1);    }, function(date, step) {      date.setTime(+date + step * durationMinute$1);    }, function(start, end) {      return (end - start) / durationMinute$1;    }, function(date) {      return date.getMinutes();    });    var minutes = minute.range;    var hour = newInterval(function(date) {      var offset = date.getTimezoneOffset() * durationMinute$1 % durationHour$1;      if (offset < 0) offset += durationHour$1;      date.setTime(Math.floor((+date - offset) / durationHour$1) * durationHour$1 + offset);    }, function(date, step) {      date.setTime(+date + step * durationHour$1);    }, function(start, end) {      return (end - start) / durationHour$1;    }, function(date) {      return date.getHours();    });    var hours = hour.range;    var day = newInterval(function(date) {      date.setHours(0, 0, 0, 0);    }, function(date, step) {      date.setDate(date.getDate() + step);    }, function(start, end) {      return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute$1) / durationDay$1;    }, function(date) {      return date.getDate() - 1;    });    var days = day.range;    function weekday(i) {      return newInterval(function(date) {        date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7);        date.setHours(0, 0, 0, 0);      }, function(date, step) {        date.setDate(date.getDate() + step * 7);      }, function(start, end) {        return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute$1) / durationWeek$1;      });    }    var sunday = weekday(0);    var monday = weekday(1);    var tuesday = weekday(2);    var wednesday = weekday(3);    var thursday = weekday(4);    var friday = weekday(5);    var saturday = weekday(6);    var sundays = sunday.range;    var mondays = monday.range;    var tuesdays = tuesday.range;    var wednesdays = wednesday.range;    var thursdays = thursday.range;    var fridays = friday.range;    var saturdays = saturday.range;    var month = newInterval(function(date) {      date.setDate(1);      date.setHours(0, 0, 0, 0);    }, function(date, step) {      date.setMonth(date.getMonth() + step);    }, function(start, end) {      return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12;    }, function(date) {      return date.getMonth();    });    var months = month.range;    var year = newInterval(function(date) {      date.setMonth(0, 1);      date.setHours(0, 0, 0, 0);    }, function(date, step) {      date.setFullYear(date.getFullYear() + step);    }, function(start, end) {      return end.getFullYear() - start.getFullYear();    }, function(date) {      return date.getFullYear();    });    // An optimized implementation for this simple case.    year.every = function(k) {      return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function(date) {        date.setFullYear(Math.floor(date.getFullYear() / k) * k);        date.setMonth(0, 1);        date.setHours(0, 0, 0, 0);      }, function(date, step) {        date.setFullYear(date.getFullYear() + step * k);      });    };    var years = year.range;    timeInterval.timeYear = year;    timeInterval.timeYears = years;    timeInterval.timeMonths = months;    timeInterval.timeMonth = month;    timeInterval.timeSecond = second;    timeInterval.timeSeconds = seconds;    timeInterval.timeDay = day;    timeInterval.timeDays = days;       timeInterval.timeHour = hour;    timeInterval.timeHours = hours;     timeInterval.timeMinute = minute;    timeInterval.timeMinutes = minutes;    module.exports = timeInterval; },       

(2)针对第二个问题的解决方法,主要是对(1)中算出的刻度进一步处理,说白了,就是给刻度的两个端点适量留一些空白,以便不影响柱状图等图形显示。具体代码如下所示:

        /**           * 优化并调整刻度的最大刻度和最小刻度,           * 若存在,直接返回刻度值           * @param {Object} params            * @param {Array.<Number>} params.ticks: 包含刻度的数组           * @param {Array.<Number>} params.extent: 最大刻度和最小刻度数组           * @param {Array.<Number>} params.niceTickExtent: 更新后的最大刻度和最小刻度数组           * @param {Number} params.splitNum: 刻度轴的分割数目           * @param {Function} params.offset: 时间轴时,为时间的偏移函数;数值轴时为null           * @param {Number} params.step: 时间轴时,刻度之间的步长           * @param {Number} params.intervalPrecision: 数值轴时,刻度值的精度        */        helper.adjustTickExtent = function (params) {            var ticks = params.ticks,            extent = params.extent,            niceTickExtent = params.niceTickExtent,            splitNum = params.splitNum;            var context = this;            var interval;            var name = extent[0] + '@#' + extent[1] + '&' + splitNum;            // 缓存存在直接返回                     if (this.cache[name]) {                return this.cache[name];            }               if (processOnlyNum (params, context)) {                return ticks;            }               preprocessTicks(params);                                calTickMin(params, extent[0]);            calTickMax(params, extent[1]);                                                      setAdjustExtent.call(this, niceTickExtent, extent, ticks, splitNum);            return ticks;            /**               * 当数据最大值和最小值相等时               * 此时刻度值数目只有三个               * @param {Object} params: 包含参数值的对象               * @param {Object} context: 执行环境的上下文            */            function processOnlyNum (params, context) {                var ticks = params.ticks;                var offset = params.offset;                var adjustInterval = 1;                var extent = params.extent;                var step = params.step;                var onlyNum = params.onlyNum;                var intervalPrecision = params.intervalPrecision;                //onlyNum表示数据只有一条                if (onlyNum != null) {                    if (offset === null) {                        ticks.length = 0;                        adjustInterval = extent[1] - extent[0];                        ticks[0] = extent[0];                        ticks[1] = extent[1];                        onlyNum == extent[0] ? ticks.unshift(extent[0] - adjustInterval) : ticks.push(extent[1] + adjustInterval);                                  } else {                        ticks.length = 0;                        ticks[0] = offset(onlyNum, -step).getTime();                         ticks[1] = onlyNum;                        ticks[2] = offset(onlyNum, step).getTime();                     }                           setAdjustExtent.call(context, niceTickExtent, extent, ticks, splitNum);                    return true;                }                 return false;            }            /**               * 预处理刻度,功能:               *(1)移除刻度中数值等于extent[0]和extent[1]的刻度               *(2)将只包含一个刻度和两个刻度的刻度数组扩展成多个               * @param {Object} params: 包含各种参数的对象            */            function preprocessTicks(params) {                  var ticks = params.ticks;                var offset = params.offset;                var extent = params.extent;                var step = params.step;                var intervalPrecision = params.intervalPrecision;                //ticks等于1,只有可能是时间轴的情况                if (ticks.length == 1) {                                ticks.unshift(offset(ticks[0], -step).getTime());                     ticks.push(offset(ticks[ticks.length - 1], step).getTime());                                    } else if ((ticks.length == 2 || ticks.length == 3) && offset == null) {                    //当刻度的最小值是数据的最小值时,根据step求出最小刻度                    tick = ticks[0];                                                            if (tick == extent[0] && (tick % step != 0)) {                       tick = roundNumber(tick - (tick % step) , intervalPrecision);                       ticks[0] = tick;                     }                    //当刻度的最大值是数据的最大值时,根据step求出最大刻度                    tick = ticks[ticks.length - 1];                    if (tick == extent[1] && (tick % step != 0)) {                                          tick = roundNumber(tick + step - (tick % step) , intervalPrecision);                        ticks[ticks.length - 1] = tick;                    }                               } else if (ticks.length > 3) {                    ticks[0] == extent[0] && ticks.shift();                    ticks[ticks.length - 1] == extent[1] && ticks.pop();                }                           }            /**               * 计算刻度的最小刻度               * @param {Object} params: 包含各种参数的对象               * @param {Number} min:数据的最小值                     */                      function calTickMin(params, min) {                  var ticks = params.ticks;                var offset = params.offset;                                         var step = params.step;                         var intervalPrecision = params.intervalPrecision;                   var interval = offset === null ? step : (ticks[1] - ticks[0]);                                      var i = 0, tickMin, differ, tick;                           while (true) {                    i++;                    if (i == 3) {                        break;                    }                    tickMin = ticks[0];                    differ = min - tickMin;                                                    if (differ > 0 && differ >= interval * 0.2 ) {                                          break;                    }                       /*                      * 若数据最小值与刻度最小值之差为正且小于步长20%,                      * 或者数据最小值与刻度最小值之差为负                      * 则最小刻度需要再向下增加一个刻度                        */                                          if ((differ > 0 && differ < interval * 0.2) || differ <= 0) {                           //数值轴                        if (offset == null) {                            tick = roundNumber(tickMin - step, intervalPrecision);                        //时间轴                        } else {                            tick = offset(tickMin, -step).getTime();                        }                                                           ticks.unshift(tick);                                    }                }               }            /**               * 计算刻度的最小刻度               * @param {Object} params: 包含各种参数的对象               * @param {Number} min:数据的最小值                       */              function calTickMax(params, max, interval) {                var ticks = params.ticks;                var offset = params.offset;                                         var step = params.step;                         var intervalPrecision = params.intervalPrecision;                var interval = offset === null ? step : (ticks[1] - ticks[0]);                                  var i = 0, tickMax, differ, tick;                       while (true) {                      i++;                        //防止陷入死循环                    if (i == 3) {                        break;                    }                                   tickMax = ticks[ticks.length - 1];                    differ = max - tickMax;                    if (differ < 0 && Math.abs(differ) >= interval * 0.2) {                                         break;                    }                    /*                      * 若数据最大值与刻度最大值之差为负且绝对值小于步长20%,                      * 或者数据最小值与刻度最小值之差为正                      * 则最大刻度需要再向上增加一个刻度                        */                                                      if (differ >= 0 || (differ < 0 && Math.abs(differ) < interval * 0.2)) {                        if (offset == null) {                            tick = roundNumber(tickMax + step, intervalPrecision);                        } else {                            tick = offset(tickMax, step).getTime();                        }                        ticks.push(tick);                               }                 }            }            /**             * 设置extent,并存入缓存             * @param {Array} niceTickExtent             * @param {Array} extent             * @param {Array} ticks             * @param {Array} splitNum             */            function setAdjustExtent(niceTickExtent, extent, ticks, splitNum) {                //修正轴的extent                                niceTickExtent[0] = extent[0] = ticks[0];                niceTickExtent[1] = extent[1] = ticks[ticks.length - 1];                    var name = extent[0] + '@#' + extent[1] + '&' + splitNum;                this.cache[name] = ticks;            }                   };      

(3)第三个问题,相对来说,简单很多,采用比较暴力的方法,规定标签统一倾斜45度展示,每一个标签在x轴上占用40px。这样可以计算出一根轴上最大容纳的标签数目。方法主要扩展在axisHelper对象上。

        /**           * 计算出轴能容纳的最大标签个数           * @param {Number} width:轴的总宽           * @return {Number} num: 容纳的标签数目        */      axisHelper.getMaxTickNum = function (width) {          var perWidth = 40px;          var num = Math.floor(width / perWidth);          num = num < 1 ? 1 : num;          return num;    };        axisHelper.niceScaleExtent = function (scale, model) {            var extent = axisHelper.getScaleExtent(scale, model);            var axis = model.axis;            var fixMin = model.getMin() != null;            var fixMax = model.getMax() != null;            var splitNumber = model.get('splitNumber');            var mulDimension = model.get('mulDimension');            //  时间轴刻度自适应            if (axis.model.get('tickMax') && axis.type != 'category' && axis.dim == 'x') {                splitNumber = axisHelper.getMaxTickNum(axis._extent[1] - axis._extent[0], mulDimension);                    scale.splitNumber = splitNumber;            }            if (scale.type === 'log') {                scale.base = model.get('logBase');            }                   scale.setExtent(extent[0], extent[1]);            scale.niceExtent({                splitNumber: splitNumber,                fixMin: fixMin,                fixMax: fixMax,                minInterval: scale.type === 'interval' ? model.get('minInterval') : null            });            // If some one specified the min, max. And the default calculated interval            // is not good enough. He can specify the interval. It is often appeared            // in angle axis with angle 0 - 360. Interval calculated in interval scale is hard            // to be 60.            // FIXME            var interval = model.get('interval');            if (interval != null) {                scale.setInterval && scale.setInterval(interval);            }        };

(4)为了实现这些问题,还有一些初始化工作需要处理,主要有一下代码实现:

/**           * 根据条件计算出最佳的时间刻度值           * @param {Object} params: 包含参数的集合           * @param {Array} params.extent: 刻度的范围           * @param {Array} params.niceTickExtent: 更新后的刻度范围           * @param {Number} params.splitNum: 刻度分割数目           * @param {String} params.defaultFormat: 初始的时间格式           * @param {Number} params.onlyNum: 是否只有单个数值                  */        helper.processTimeTicks = function (params) {            var ticks = [], timeIntervalPro;            var extent = params.extent;            var splitNum = params.splitNum;            var format = selectTimeFormat(extent, splitNum, timeInterval, params.defaultFormat);            timeIntervalPro = Math.ceil(timeInterval[format].count(extent[0], extent[1]) / splitNum);            timeIntervalPro = timeIntervalPro < 1 ? 1 : timeIntervalPro;            ticks = timeInterval[format+'s'](extent[0], extent[1], timeIntervalPro);            //调整刻度的最大刻度和最小刻度            ticks = this.adjustTickExtent({                ticks: ticks,                extent: extent,                 niceTickExtent: params.niceTickExtent,                splitNum: splitNum,                offset: timeInterval[format].offset,                step: timeIntervalPro,                onlyNum: params.onlyNum            });            return ticks;            /**              * 判断使用哪种时间格式求时间刻度              * @param {Array} extent: 刻度的范围              * @param {Number} splitNum: 刻度分割数目              * @param {Function} timeInterval: 处理时间刻度的函数集合              * @param {String} defaultFormat: 初始的时间格式            */            function selectTimeFormat(extent, splitNum, timeInterval, defaultFormat)  {                var format = 'timeSecond';                if (defaultFormat == 'timeYear') {                    return 'timeYear';                }                if (timeInterval.timeYear.count(extent[0], extent[1]) >= splitNum) {                    format = 'timeYear';                } else if (timeInterval.timeMonth.count(extent[0], extent[1]) >= splitNum) {                    format = 'timeMonth';                } else if (timeInterval.timeDay.count(extent[0], extent[1]) >= splitNum) {                    format = 'timeDay';                } else if (timeInterval.timeHour.count(extent[0], extent[1]) >= splitNum) {                    format = 'timeHour';                } else if (timeInterval.timeMinute.count(extent[0], extent[1]) >= splitNum) {                    format = 'timeMinute';                } else {                    format = 'timeSecond';                }                format = correctResult(format, defaultFormat);                return format;                /**                   * 判断出的时间格式,可能不满足展示要求,                   * 需要重新进行修正                   * @param {String} format: 判断出的时间格式                   * @param {String} defaultFormat: 初始化的时间格式                   * @return {String} format: 修正后的时间格式                */                function correctResult(format, defaultFormat) {                    if (defaultFormat == 'timeDay') {                        if (!/timeYear|timeMonth|timeDay/.test(format)) {                            format = 'timeDay';                        }                    } else if (defaultFormat == 'timeMonth') {                        if (!/timeYear|timeMonth/.test(format)) {                            format = 'timeMonth';                        }                    } else if (defaultFormat == 'timeSecond') {                        if (!/timeHour|timeMinute|timeSecond/.test(format)) {                            format = 'timeSecond';                        }                    }                    return format;                }            }        };        /**         * 得到时间格式对应的时间类型         * @param {String} timeFormat: 时间格式         * @return {String} type: 时间类型:year、month、day、second;         */        helper.getTimeType = function(timeFormat) {            var type = null;            if (['month', 'year', 'day', 'second'].indexOf(timeFormat) > -1) {                type = timeFormat;            } else if (timeFormat == 'hh:mm:ss' || timeFormat == 'yyyy-MM-dd hh:mm:ss' || timeFormat == 'yyyy/MM/dd hh:mm:ss') {                type = 'second';            } else if (timeFormat == 'yyyy') {                type = 'year';            } else if (timeFormat == 'yyyy/MM' || timeFormat == 'yyyyMM' || timeFormat == 'yyyy-MM') {                type = 'month';            } else if (/(yyyy\-MM\-dd)|(yyyy\/MM\/dd)|(dd\/MM\/yyyy)|(yyyyMMdd)/i.test(timeFormat)) {                type = 'day';            } else {                type = 'second';            }            return type;        };        helper.intervalScaleGetTicks = function (extent, niceTickExtent, params) {            var ticks = [], timeIntervalPro;            var splitNum = params && params.splitNum || 5;            var mulDimension = params && params.mulDimension;            var timeFormat = params && params.timeFormat;            var interval = params.interval;            var intervalPrecision = params.intervalPrecision;            var type = params.type;            // If interval is 0, return [];            if (!interval) {                return ticks;            }            splitNum == 1 && (splitNum += 2) ||             splitNum == 2 && (splitNum += 1);            var name = extent[0] + '@#' + extent[1] + '&' + splitNum;            if (this.cache[name]) {                return this.cache[name];            }            //沈才良添加 修复时间处于年和月的显示错误            if (type == 'time' && params) {                 timeFormat = this.getTimeType(timeFormat);                if (['year', 'month', 'day', 'second'].indexOf(timeFormat) > -1) {                    ticks = this.processTimeTicks({                        extent: extent,                         niceTickExtent: niceTickExtent,                         splitNum: splitNum,                         defaultFormat: 'time' + timeFormat.slice(0, 1).toUpperCase() + timeFormat.slice(1),                         onlyNum: params.onlyNum                    });                    return ticks;                }            }            // Consider this case: using dataZoom toolbox, zoom and zoom.            var safeLimit = 10000;            if (extent[0] < niceTickExtent[0]) {                ticks.push(extent[0]);            }            var tick = niceTickExtent[0];            while (tick <= niceTickExtent[1]) {                ticks.push(tick);                // Avoid rounding error                tick = roundNumber(tick + interval, intervalPrecision);                if (tick === ticks[ticks.length - 1]) {                    // Consider out of safe float point, e.g.,                    // -3711126.9907707 + 2e-10 === -3711126.9907707                    break;                }                if (ticks.length > safeLimit) {                    return [];                }            }            // Consider this case: the last item of ticks is smaller            // than niceTickExtent[1] and niceTickExtent[1] === extent[1].            if (extent[1] > (ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1])) {                ticks.push(extent[1]);            }                   }                       return ticks;               };

(5)Echarts的年时间轴option配置项,主要是设置两个参数”timeFormat”、 “tickMax”。”timeFormat”表示时间轴的格式,可以为”yyyy”、“yyyy-MM”、“yyyy-MM-dd”“hh:mm:ss”等,tickMax则表示是否启用最大刻度个数,blooean类型:true表示启用。

        var option ={            "color":[                "#4cc5f4",                "#fa5879",                "#f8e402",                          ],            "tooltip":{                "trigger": "item"            },            "legend":{                "show":false,                "data":[                    "2013-01",                    "2013-07",                    "2013-09",                    "2013-11",                    "2013-03",                    "2013-05",                    "2013-02",                    "2013-04",                    "2013-06",                    "2013-08",                    "2013-10",                    "2013-12"                ]            },            "calculable":true,            "xAxis":[                {                    "type":"time",                                                          "timeFormat": 'yyyy',                                   "rotate":45,                    "tickMax": true,                     min: 'dataMin',                     max: 'dataMax',                    "axisLabel":{                        "tooltip":{                            "show":true                                                 },                        formatter: function (params) {                            return new Date(params).Format('yyyy');                        }                    },                    "silent":false,                    "triggerEvent":true,                    "selectEvent":true                }            ],            "yAxis":[                {                    "type":"value",                    "key":"huan_bi",                    "field":"huan_bi",                    "rotate":0,                    "boundaryGap":null,                    "scale":false,                    "axisLabel":{                        "margin":10,                        "tooltip":{                            "show":true                        }                    },                    "silent":false,                    "triggerEvent":true,                    "selectEvent":true,                    "show":true,                    "splitLine":{                        "show":false                    },                    "axisTick":{                        "secondTick":{                            "show":false,                            "length":3                        },                        "length":6                    }                }            ],            "series":[                 {                    "name":"huan_bi",                     "data":[               [                    "1998",                    0.11299999999999999                ],                [                    "1999",                    0.11299999999999999                ],                [                    "2000",                    0.12                ],                [                    "2001",                    0.135                ],                [                    "2003",                    0.149                ]            ],                    "selectEvent":true,                    "key":"huan_bi",                    "type":"bar",                    barWidth:'20%',                    "symbol":"circle",                    "showAllSymbol":true,                    "label":{                        "normal":{                            "show":false,                            "textStyle":{                                "color":"#000"                            },                            "formatter":"",                            "position":"top"                        }                    }                }            ],            "backgroundColor":"#fff"        };

详细源码:http://download.csdn.net/download/mulumeng981/9977121

写作水平有限,敬请谅解。
谢谢。

原创粉丝点击