问题介绍:
Echarts插件的坐标轴类型主要分为时间(time)、数值(value)、对数(log)、类目(category)四大类,当采用二维的直角坐标系时,一般x轴通过采用类目轴,而y轴通常采用value轴。官方的有关的直角坐标系例子,大多遵循这个原则配置的。这样配置其实只适用于离散型数据,而对于时间型数据,虽然有时间轴的支持,但是效果却差强人意,甚至存在展示的缺陷。
如图1所示为日的时间型数据,从图上可以看出柱状图的柱子跟y轴有重叠,x轴刻度值少的问题。如图2、3所示为月和年的时间型数据,问题跟图1差不多,但还有一个不一样的问题,就是刻度值,不管是月还是年的数据,刻度值都显示的日的刻度值,这个对于用户来说,体验应该是不好的。更好的体验应该是,月的时间型数据,显示的刻度值应该都是月的刻度值;年的时间型数据,显示的应该都是年的刻度值。如图4、5、6,是本人优化和处理后的显示效果图。
总的来说,主要存在以下三个问题
(1)不同时间格式下,计算出来的刻度不符合预期;
(2)数据的最小值容易与坐标轴重叠,最大值容易显示在图表之外;
(3)计算出来的刻度个数偏少,数据之间容易重叠。
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
写作水平有限,敬请谅解。
谢谢。