Echarts時間座標軸刻度的改進和優化
問題介紹:
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.<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');
}
},