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

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

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

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

Echarts时间坐标轴刻度的改进和优化_第1张图片

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

Echarts时间坐标轴刻度的改进和优化_第2张图片
图2 时间格式为月的时间轴展示

Echarts时间坐标轴刻度的改进和优化_第3张图片
图3 时间格式为年的时间轴展示

Echarts时间坐标轴刻度的改进和优化_第4张图片
图4 时间格式为日的时间轴展示

Echarts时间坐标轴刻度的改进和优化_第5张图片
图5 时间格式为月的时间轴展示

Echarts时间坐标轴刻度的改进和优化_第6张图片
图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.} params.ticks: 包含刻度的数组
           * @param {Array.} params.extent: 最大刻度和最小刻度数组
           * @param {Array.} 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

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

你可能感兴趣的:(javascript,数据可视化,echarts,echarts3-0)