You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/07/31 09:37:26 UTC
[incubator-echarts] branch next updated: refact(axis): optimize
time ticks calculation.
This is an automated email from the ASF dual-hosted git repository.
shenyi pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
The following commit(s) were added to refs/heads/next by this push:
new 8c280f6 refact(axis): optimize time ticks calculation.
8c280f6 is described below
commit 8c280f6bbdcdccd11debf81a432514018493cb0a
Author: pissang <bm...@gmail.com>
AuthorDate: Fri Jul 31 17:36:32 2020 +0800
refact(axis): optimize time ticks calculation.
Each level will step with an interval. Make the final results more stable on each zoom level.
---
src/scale/Time.ts | 546 +++++++++++++++++++++++++++++++-----------------------
src/util/time.ts | 130 +++++++++----
2 files changed, 405 insertions(+), 271 deletions(-)
diff --git a/src/scale/Time.ts b/src/scale/Time.ts
index ade85c5..a0991a2 100644
--- a/src/scale/Time.ts
+++ b/src/scale/Time.ts
@@ -54,20 +54,36 @@ import {
fullLeveledFormatter,
getPrimaryTimeUnit,
isPrimaryTimeUnit,
- getDefaultFormatPrecisionOfInterval
+ getDefaultFormatPrecisionOfInterval,
+ fullYearGetterName,
+ monthSetterName,
+ fullYearSetterName,
+ dateSetterName,
+ hoursGetterName,
+ hoursSetterName,
+ minutesSetterName,
+ secondsSetterName,
+ millisecondsSetterName,
+ monthGetterName,
+ dateGetterName,
+ minutesGetterName,
+ secondsGetterName,
+ millisecondsGetterName
} from '../util/time';
import * as scaleHelper from './helper';
import IntervalScale from './Interval';
import Scale from './Scale';
-import {TimeScaleTick} from '../util/types';
+import {TimeScaleTick, ScaleTick} from '../util/types';
import {TimeAxisLabelFormatterOption} from '../coord/axisCommonTypes';
import { warn } from '../util/log';
import { LocaleOption } from '../locale';
import Model from '../model/Model';
+import { filter, map, extend } from 'zrender/src/core/util';
+import { unionTypeAnnotation } from '@babel/types';
// FIXME 公用?
const bisect = function (
- a: [string, number][],
+ a: [string | number, number][],
x: number,
lo: number,
hi: number
@@ -98,7 +114,7 @@ class TimeScale extends IntervalScale {
_approxInterval: number;
- _intervalUnit: TimeUnit;
+ _minLevelUnit: TimeUnit;
/**
* Get label is mainly for other components like dataZoom, tooltip.
@@ -108,7 +124,7 @@ class TimeScale extends IntervalScale {
return format(
tick.value,
fullLeveledFormatter[
- getDefaultFormatPrecisionOfInterval(getPrimaryTimeUnit(this._intervalUnit))
+ getDefaultFormatPrecisionOfInterval(getPrimaryTimeUnit(this._minLevelUnit))
] || fullLeveledFormatter.second,
useUTC
);
@@ -146,7 +162,7 @@ class TimeScale extends IntervalScale {
const useUTC = this.getSetting('useUTC');
const innerTicks = getIntervalTicks(
- this._intervalUnit,
+ this._minLevelUnit,
this._approxInterval,
this._interval,
useUTC,
@@ -204,15 +220,16 @@ class TimeScale extends IntervalScale {
}
const scaleIntervalsLen = scaleIntervals.length;
- const idx = bisect(scaleIntervals, this._approxInterval, 0, scaleIntervalsLen) - 1;
-
- const intervals = scaleIntervals[
- Math.max(Math.min(idx, scaleIntervalsLen - 1), 0
- )];
+ const idx = Math.min(
+ bisect(scaleIntervals, this._approxInterval, 0, scaleIntervalsLen),
+ scaleIntervalsLen - 1
+ );
- // Interval will be used in getTicks
- this._interval = intervals[1];
- this._intervalUnit = intervals[0];
+ // Interval that can be used to calculate ticks
+ this._interval = scaleIntervals[idx][1];
+ // Min level used when picking ticks from top down.
+ // We check one more level to avoid the ticks are to sparse in some case.
+ this._minLevelUnit = scaleIntervals[Math.max(idx - 1, 0)][0];
}
parse(val: number | string | Date): number {
@@ -299,268 +316,331 @@ function isUnitValueSame(
}
}
+// const primaryUnitGetters = {
+// year: fullYearGetterName(),
+// month: monthGetterName(),
+// day: dateGetterName(),
+// hour: hoursGetterName(),
+// minute: minutesGetterName(),
+// second: secondsGetterName(),
+// millisecond: millisecondsGetterName()
+// };
+
+// const primaryUnitUTCGetters = {
+// year: fullYearGetterName(true),
+// month: monthGetterName(true),
+// day: dateGetterName(true),
+// hour: hoursGetterName(true),
+// minute: minutesGetterName(true),
+// second: secondsGetterName(true),
+// millisecond: millisecondsGetterName(true)
+// };
+
+function moveTick(date: Date, unitName: TimeUnit, step: number, isUTC?: boolean) {
+ step = step || 1;
+ switch (getPrimaryTimeUnit(unitName)) {
+ case 'year':
+ date[fullYearSetterName(isUTC)](date[fullYearGetterName(isUTC)]() + step);
+ break;
+ case 'month':
+ date[monthSetterName(isUTC)](date[monthGetterName(isUTC)]() + step);
+ break;
+ case 'day':
+ date[dateSetterName(isUTC)](date[dateGetterName(isUTC)]() + step);
+ break;
+ case 'hour':
+ date[hoursSetterName(isUTC)](date[hoursGetterName(isUTC)]() + step);
+ break;
+ case 'minute':
+ date[minutesSetterName(isUTC)](date[minutesGetterName(isUTC)]() + step);
+ break;
+ case 'second':
+ date[secondsSetterName(isUTC)](date[secondsGetterName(isUTC)]() + step);
+ break;
+ case 'millisecond':
+ date[millisecondsSetterName(isUTC)](date[millisecondsGetterName(isUTC)]() + step);
+ break;
+ }
+ return date.getTime();
+}
+
+// const DATE_INTERVALS = [[8, 7.5], [4, 3.5], [2, 1.5]];
+// const MONTH_INTERVALS = [[6, 5.5], [3, 2.5], [2, 1.5]];
+// const MINUTES_SECONDS_INTERVALS = [[30, 30], [20, 20], [15, 15], [10, 10], [5, 5], [2, 2]];
+
+function getDateInterval(approxInterval: number, daysInMonth: number) {
+ approxInterval /= ONE_DAY;
+ return approxInterval > 16 ? 16
+ // Math.floor(daysInMonth / 2) + 1 // In this case we only want one tick betwen two month.
+ : approxInterval > 7.5 ? 7
+ : approxInterval > 3.5 ? 4
+ : approxInterval > 1.5 ? 2 : 1;
+}
+
+function getMonthInterval(approxInterval: number) {
+ const APPROX_ONE_MONTH = 30 * ONE_DAY;
+ approxInterval /= APPROX_ONE_MONTH;
+ return approxInterval > 5 ? 5
+ : approxInterval > 3 ? 2
+ : approxInterval > 2.5 ? 2 : 1;
+}
+
+function getHourInterval(approxInterval: number) {
+ approxInterval /= ONE_HOUR;
+ return approxInterval > 12 ? 11
+ : approxInterval > 6 ? 6
+ : approxInterval > 3.5 ? 4
+ : approxInterval > 2 ? 2 : 1;
+}
+
+function getMinutesAndSecondsInterval(approxInterval: number, isMinutes?: boolean) {
+ approxInterval /= isMinutes ? ONE_MINUTE : ONE_SECOND;
+ return approxInterval > 30 ? 30
+ : approxInterval > 20 ? 20
+ : approxInterval > 15 ? 15
+ : approxInterval > 10 ? 10
+ : approxInterval > 5 ? 5
+ : approxInterval > 2 ? 2 : 1;
+}
+
+function getMillisecondsInterval(approxInterval: number) {
+ return numberUtil.nice(approxInterval, true);
+}
+
+function getFirstTimestampOfUnit(date: Date, unitName: TimeUnit, isUTC: boolean) {
+ const outDate = new Date(date);
+ switch (getPrimaryTimeUnit(unitName)) {
+ case 'year':
+ case 'month':
+ outDate[monthSetterName(isUTC)](0);
+ case 'day':
+ outDate[dateSetterName(isUTC)](1);
+ case 'hour':
+ outDate[hoursSetterName(isUTC)](0);
+ case 'minute':
+ outDate[minutesSetterName(isUTC)](0);
+ case 'second':
+ outDate[secondsSetterName(isUTC)](0);
+ outDate[millisecondsSetterName(isUTC)](0);
+ }
+ return outDate.getTime();
+}
+
function getIntervalTicks(
- unitName: TimeUnit,
+ bottomUnitName: TimeUnit,
approxInterval: number,
interval: number,
isUTC: boolean,
extent: number[]
): TimeScaleTick[] {
const safeLimit = 10000;
- const utc = isUTC ? 'UTC' : '';
- const ticks: TimeScaleTick[] = [];
const unitNames = timeUnits;
- let levelId = 0;
-
- const setFullYearMethodName = 'set' + utc + 'FullYear' as 'setFullYear' | 'setUTCFullYear';
- const setMonthMethodName = 'set' + utc + 'Month' as 'setMonth' | 'setUTCMonth';
- const setDateMethodName = 'set' + utc + 'Date' as 'setDate' | 'setUTCDate';
- const setHoursMethodName = 'set' + utc + 'Hours' as 'setHours' | 'setUTCHours';
- const setMinutesMethodName = 'set' + utc + 'Minutes' as 'setMinutes' | 'setUTCMinutes';
- const setSecondsMethodName = 'set' + utc + 'Seconds' as 'setSeconds' | 'setUTCSeconds';
- const setMillisecondsMethodName = 'set' + utc + 'Milliseconds' as 'setMilliseconds' | 'setUTCMilliseconds';
-
- const getFullYearMethodName = 'get' + utc + 'FullYear' as 'getFullYear' | 'getUTCFullYear';
- const getMonthMethodName = 'get' + utc + 'Month' as 'getMonth' | 'getUTCMonth';
- const getDateMethodName = 'get' + utc + 'Date' as 'getDate' | 'getUTCDate';
- const getHoursMethodName = 'get' + utc + 'Hours' as 'getHours' | 'getUTCHours';
- const getMinutesMethodName = 'get' + utc + 'Minutes' as 'getMinutes' | 'getUTCMinutes';
- const getSecondsMethodName = 'get' + utc + 'Seconds' as 'getSeconds' | 'getUTCSeconds';
- const getMillisecondsMethodName = 'get' + utc + 'Milliseconds' as 'getMilliseconds' | 'getUTCMilliseconds';
+ // const bottomPrimaryUnitName = getPrimaryTimeUnit(bottomUnitName);
+
+ interface InnerTimeTick extends TimeScaleTick {
+ notAdd?: boolean
+ }
let iter = 0;
- for (let i = 0, hasTickInLevel = false; i < unitNames.length && iter++ < safeLimit; ++i) {
- let date = new Date(extent[0]);
-
- if (unitNames[i] === 'week' || unitNames[i] === 'half-week') {
- date[setHoursMethodName](0);
- date[setMinutesMethodName](0);
- date[setSecondsMethodName](0);
- date[setMillisecondsMethodName](0);
-
- if (extent[0] === date.getTime()) {
- ticks.push({
- value: extent[0],
- level: levelId
- });
- hasTickInLevel = true;
- }
+ function addTicksInSpan(
+ interval: number,
+ minTimestamp: number, maxTimestamp: number,
+ getMethodName: string,
+ setMethodName: string,
+ isDate: boolean,
+ out: InnerTimeTick[]
+ ) {
+ // if (maxTimestamp <= minTimestamp) {
+ // // Failed
+ // return true;
+ // }
+ const date = new Date(minTimestamp) as any;
+ let dateTime = minTimestamp;
+ let d = date[getMethodName]();
- outer: while (iter++ < safeLimit) {
- const tmpDate = new Date(date);
- tmpDate[setDateMethodName](1);
- tmpDate[setMonthMethodName](tmpDate[getMonthMethodName]() + 1);
- tmpDate[setDateMethodName](0); // Set day to 0 to return the last day of last month.
- const daysInMonth = tmpDate.getDate();
+ // if (isDate) {
+ // d -= 1; // Starts with 0; PENDING
+ // }
- const dateInterval = approxInterval > ONE_DAY * 16 ? Math.floor(daysInMonth / 2) + 1 // In this case we only want one tick betwen two month.
- : approxInterval > ONE_DAY * 8 ? 8
- : approxInterval > ONE_DAY * 3.5 ? 4
- : approxInterval > ONE_DAY * 1.5 ? 2 : 1;
+ while (dateTime < maxTimestamp && dateTime <= extent[1]) {
+ out.push({
+ value: dateTime
+ });
- // const dates = approxInterval > ONE_DAY * 8 ? [15]
- // : (approxInterval > ONE_DAY * 3.5 ? [8, 16, 24] : [4, 8, 12, 16, 20, 24, 28]);
+ d += interval;
+ date[setMethodName](d);
+ dateTime = date.getTime();
+ }
- for (let d = dateInterval; d < daysInMonth; d += dateInterval) {
+ // This extra tick is for calcuating ticks of next level. Will not been added to the final result
+ out.push({
+ value: dateTime,
+ notAdd: true
+ });
+ }
- date[setDateMethodName](d);
- const dateTime = date.getTime();
- if (dateTime > extent[1]) {
- break outer;
- }
- else if (dateTime >= extent[0]) {
- ticks.push({
- value: dateTime,
- level: levelId
- });
- hasTickInLevel = true;
- }
- }
+ function addLevelTicks(
+ unitName: TimeUnit,
+ lastLevelTicks: InnerTimeTick[],
+ levelTicks: InnerTimeTick[]
+ ) {
+ const newAddedTicks: ScaleTick[] = [];
+ const isFirstLevel = !lastLevelTicks.length;
- // Reset date to 0. The date may be 30, and days of next month may be 29. Which will excced
- date[setDateMethodName](1);
- date[setMonthMethodName](date[getMonthMethodName]() + 1);
- }
+ if (isUnitValueSame(getPrimaryTimeUnit(unitName), extent[0], extent[1], isUTC)) {
+ return;
}
- else if (!isUnitValueSame(
- getPrimaryTimeUnit(unitNames[i]),
- extent[0], extent[1], isUTC
- )) {
- // Level value changes within extent
- let isFirst = true;
- while (iter++ < safeLimit) {
- switch (unitNames[i]) {
- case 'year':
- case 'half-year':
- case 'quarter':
- if (isFirst) {
- date[setMonthMethodName](0);
- date[setDateMethodName](1);
- date[setHoursMethodName](0);
- date[setMinutesMethodName](0);
- date[setSecondsMethodName](0);
- }
- else {
- const months = unitNames[i] === 'year'
- ? 12 : (unitNames[i] === 'half-year' ? 6 : 3);
- if (unitNames[i] === 'half-year' || unitNames[i] === 'quarter') {
- date[setMonthMethodName](date[getMonthMethodName]() + months);
- }
- else {
- const yearSpan = Math.max(1, Math.round(approxInterval / ONE_DAY / 365));
- date[setFullYearMethodName](date[getFullYearMethodName]() + yearSpan);
- if (date.getTime() > extent[1] && yearSpan > 1) {
- // For the last data
- date[setFullYearMethodName](date[getFullYearMethodName]() - yearSpan + 1);
- if (date.getTime() < extent[1]) {
- // The last data is not in year unit, make it invalid by larger than extent[1]
- date[setFullYearMethodName](date[getFullYearMethodName]() + yearSpan);
- }
- }
- }
- }
- break;
-
- case 'month':
- if (isFirst) {
- date[setDateMethodName](1);
- date[setHoursMethodName](0);
- date[setMinutesMethodName](0);
- date[setSecondsMethodName](0);
- }
- else {
- date[setMonthMethodName](date[getMonthMethodName]() + 1);
- }
- break;
-
- case 'day':
- case 'half-day':
- case 'quarter-day':
- if (isFirst) {
- date[setHoursMethodName](0);
- date[setMinutesMethodName](0);
- date[setSecondsMethodName](0);
- }
- else if (unitNames[i] === 'half-day') {
- date[setHoursMethodName](date[getHoursMethodName]() + 12);
- }
- else if (unitNames[i] === 'quarter-day') {
- date[setHoursMethodName](date[getHoursMethodName]() + 6);
- }
- else {
- date[setDateMethodName](date[getDateMethodName]() + 1);
- }
- break;
-
- case 'hour':
- if (isFirst) {
- date[setMinutesMethodName](0);
- date[setSecondsMethodName](0);
- }
- else {
- // date = new Date(+date + interval);
- date[setHoursMethodName](date[getHoursMethodName]() + 1);
- }
- break;
- case 'minute':
- if (isFirst) {
- date[setMinutesMethodName](0);
- date[setSecondsMethodName](0);
- }
- else {
- // date = new Date(+date + interval);
- date[setMinutesMethodName](date[getMinutesMethodName]() + 1);
- }
- break;
+ if (isFirstLevel) {
+ lastLevelTicks = [{
+ // TODO Optimize. Not include so may ticks.
+ value: getFirstTimestampOfUnit(new Date(extent[0]), unitName, isUTC)
+ }, {
+ value: extent[1]
+ }];
+ }
- case 'second':
- if (!isFirst) {
- // date = new Date(+date + interval);
- date[setSecondsMethodName](date[getSecondsMethodName]() + 1);
- }
- break;
+ for (let i = 0; i < lastLevelTicks.length - 1; i++) {
+ const startTick = lastLevelTicks[i].value;
+ const endTick = lastLevelTicks[i + 1].value;
+ if (startTick === endTick) {
+ continue;
+ }
- case 'millisecond':
- if (isFirst) {
- date[setMillisecondsMethodName](0);
- }
- else {
- // date = new Date(+date + interval);
- date[setMillisecondsMethodName](date[getMillisecondsMethodName]() + 100);
- }
- break;
- }
- if (isFirst && unitNames[i] !== 'millisecond') {
- date[setMillisecondsMethodName](0);
- }
+ let interval: number;
+ let getterName;
+ let setterName;
+ let isDate = false;
- const dateValue = date.getTime();
- if (dateValue >= extent[0] && dateValue <= extent[1]) {
- ticks.push({
- value: dateValue,
- level: levelId
- });
- hasTickInLevel = true;
- }
- else if (dateValue > extent[1]) {
+ switch (unitName) {
+ case 'year':
+ interval = Math.max(1, Math.round(approxInterval / ONE_DAY / 365));
+ getterName = fullYearGetterName(isUTC);
+ setterName = fullYearSetterName(isUTC);
+ break;
+ case 'half-year':
+ case 'quarter':
+ case 'month':
+ interval = getMonthInterval(approxInterval);
+ getterName = monthGetterName(isUTC);
+ setterName = monthSetterName(isUTC);
+ break;
+ case 'week': // PENDING If week is added. Ignore day.
+ case 'half-week':
+ case 'day':
+ interval = getDateInterval(approxInterval, 31); // Use 32 days and let interval been 16
+ getterName = dateGetterName(isUTC);
+ setterName = dateSetterName(isUTC);
+ isDate = true;
+ break;
+ case 'half-day':
+ case 'quarter-day':
+ case 'hour':
+ interval = getHourInterval(approxInterval);
+ getterName = hoursGetterName(isUTC);
+ setterName = hoursSetterName(isUTC);
+ break;
+ case 'minute':
+ interval = getMinutesAndSecondsInterval(approxInterval, true);
+ getterName = minutesGetterName(isUTC);
+ setterName = minutesSetterName(isUTC);
+ break;
+ case 'second':
+ interval = getMinutesAndSecondsInterval(approxInterval, false);
+ getterName = secondsGetterName(isUTC);
+ setterName = secondsSetterName(isUTC);
+ break;
+ case 'millisecond':
+ interval = getMillisecondsInterval(approxInterval);
+ getterName = millisecondsGetterName(isUTC);
+ setterName = millisecondsSetterName(isUTC);
break;
- }
- isFirst = false;
- }
- if (hasTickInLevel
- && isPrimaryTimeUnit(unitNames[i])
- ) {
- ++levelId;
}
+
+ addTicksInSpan(
+ interval, startTick, endTick, getterName, setterName, isDate, newAddedTicks
+ );
}
+ for (let i = 0; i < newAddedTicks.length; i++) {
+ levelTicks.push(newAddedTicks[i]);
+ }
+ // newAddedTicks.length && console.log(unitName, newAddedTicks);
+ return newAddedTicks;
+ }
- if (__DEV__) {
- if (iter >= safeLimit) {
- warn('Exceed safe limit.');
- }
+ const levelsTicks: InnerTimeTick[][] = [];
+ let currentLevelTicks: InnerTimeTick[] = [];
+
+ let tickCount = 0;
+ for (let i = 0; i < unitNames.length && iter++ < safeLimit; ++i) {
+ const primaryTimeUnit = getPrimaryTimeUnit(unitNames[i]);
+ if (!isPrimaryTimeUnit(unitNames[i])) { // TODO
+ continue;
}
+ addLevelTicks(unitNames[i], levelsTicks[levelsTicks.length - 1] || [], currentLevelTicks);
+
+ const nextPrimaryTimeUnit: PrimaryTimeUnit = unitNames[i + 1] ? getPrimaryTimeUnit(unitNames[i + 1]) : null;
+ if (primaryTimeUnit !== nextPrimaryTimeUnit) {
+ if (currentLevelTicks.length) {
+ // Remove the duplicate so the tick count can be precisely.
+ currentLevelTicks.sort((a, b) => a.value - b.value);
+ const levelTicksRemoveDuplicated = [];
+ for (let i = 0; i < currentLevelTicks.length; ++i) {
+ const tickValue = currentLevelTicks[i].value;
+ if (i === 0 || currentLevelTicks[i - 1].value !== tickValue) {
+ levelTicksRemoveDuplicated.push(currentLevelTicks[i]);
+ if (tickValue >= extent[0] && tickValue <= extent[1]) {
+ tickCount++;
+ }
+ }
+ }
+
+ // Only treat primary time unit as one level.
+ levelsTicks.push(levelTicksRemoveDuplicated);
- // Remove the duplicate so the tick count can be precisely.
- ticks.sort((a, b) => a.value - b.value);
- let tickCount = 0;
- const tickLen = ticks.length;
- if (tickLen) {
- let prev = ticks[0].value;
- for (let i = 1; i < tickLen; ++i) {
- if (prev !== ticks[i].value) {
- prev = ticks[i].value;
- tickCount++;
+ if (tickCount > (extent[1] - extent[0]) / approxInterval || bottomUnitName === unitNames[i]) {
+ break;
}
+
}
+ // Reset if next unitName is primary
+ currentLevelTicks = [];
}
- if (tickCount > (extent[1] - extent[0]) / approxInterval || unitNames[i] === unitName) {
- break;
+ }
+
+ if (__DEV__) {
+ if (iter >= safeLimit) {
+ warn('Exceed safe limit.');
}
- // if (unitNames[i] === unitName) {
- // break;
- // }
}
- let maxLevel = -Number.MAX_VALUE;
- for (let i = 0; i < ticks.length; ++i) {
- maxLevel = Math.max(maxLevel, ticks[i].level);
+ const levelsTicksInExtent = filter(map(levelsTicks, levelTicks => {
+ return filter(levelTicks, tick => tick.value >= extent[0] && tick.value <= extent[1] && !tick.notAdd);
+ }), levelTicks => levelTicks.length > 0);
+
+ const ticks: TimeScaleTick[] = [];
+ const maxLevel = levelsTicksInExtent.length - 1;
+ for (let i = 0; i < levelsTicksInExtent.length; ++i) {
+ const levelTicks = levelsTicksInExtent[i];
+ for (let k = 0; k < levelTicks.length; ++k) {
+ ticks.push({
+ value: levelTicks[k].value,
+ level: maxLevel - i
+ });
+ }
}
+ ticks.sort((a, b) => a.value - b.value);
// Remove duplicates
- const result = [];
+ const result: TimeScaleTick[] = [];
for (let i = 0; i < ticks.length; ++i) {
if (i === 0 || ticks[i].value !== ticks[i - 1].value) {
- result.push({
- value: ticks[i].value,
- level: maxLevel - ticks[i].level
- });
+ result.push(ticks[i]);
}
}
-
return result;
}
diff --git a/src/util/time.ts b/src/util/time.ts
index 589ac7f..f7de3ae 100644
--- a/src/util/time.ts
+++ b/src/util/time.ts
@@ -66,9 +66,9 @@ export const timeUnits: TimeUnit[] = [
'half-day', 'quarter-day', 'hour', 'minute', 'second', 'millisecond'
];
-export function pad(str: string, len: number): string {
+export function pad(str: string | number, len: number): string {
str += '';
- return '0000'.substr(0, len - str.length) + str;
+ return '0000'.substr(0, len - (str as string).length) + str;
}
export function getPrimaryTimeUnit(timeUnit: TimeUnit): PrimaryTimeUnit {
@@ -83,6 +83,7 @@ export function getPrimaryTimeUnit(timeUnit: TimeUnit): PrimaryTimeUnit {
case 'quarter-day':
return 'hour';
default:
+ // year, minutes, second, milliseconds
return timeUnit;
}
}
@@ -108,17 +109,16 @@ export function format(
time: Date | number, template: string, lang?: string | Model<LocaleOption>, isUTC?: boolean
): string {
const date = numberUtil.parseDate(time);
- const utc = isUTC ? 'UTC' : '';
- const y = (date as any)['get' + utc + 'FullYear']();
- const M = (date as any)['get' + utc + 'Month']() + 1;
+ const y = date[fullYearGetterName(isUTC)]();
+ const M = date[monthGetterName(isUTC)]() + 1;
const q = Math.floor((M - 1) / 4) + 1;
- const d = (date as any)['get' + utc + 'Date']();
- const e = (date as any)['get' + utc + 'Day']();
- const H = (date as any)['get' + utc + 'Hours']();
+ const d = date[dateGetterName(isUTC)]();
+ const e = date['get' + (isUTC ? 'UTC' : '') + 'Day' as 'getDay' | 'getUTCDay']();
+ const H = date[hoursGetterName(isUTC)]();
const h = (H - 1) % 12 + 1;
- const m = (date as any)['get' + utc + 'Minutes']();
- const s = (date as any)['get' + utc + 'Seconds']();
- const S = (date as any)['get' + utc + 'Milliseconds']();
+ const m = date[minutesGetterName(isUTC)]();
+ const s = date[secondsGetterName(isUTC)]();
+ const S = date[millisecondsGetterName(isUTC)]();
const localeModel = lang instanceof Model ? lang
@@ -130,28 +130,28 @@ export function format(
const dayOfWeekAbbr = timeModel.get('dayOfWeekAbbr');
return (template || '')
- .replace(/{yyyy}/g, y)
+ .replace(/{yyyy}/g, y + '')
.replace(/{yy}/g, y % 100 + '')
.replace(/{Q}/g, q + '')
.replace(/{MMMM}/g, month[M - 1])
.replace(/{MMM}/g, monthAbbr[M - 1])
.replace(/{MM}/g, pad(M, 2))
- .replace(/{M}/g, M)
+ .replace(/{M}/g, M + '')
.replace(/{dd}/g, pad(d, 2))
- .replace(/{d}/g, d)
+ .replace(/{d}/g, d + '')
.replace(/{eeee}/g, dayOfWeek[e])
.replace(/{ee}/g, dayOfWeekAbbr[e])
- .replace(/{e}/g, e)
+ .replace(/{e}/g, e + '')
.replace(/{HH}/g, pad(H, 2))
- .replace(/{H}/g, H)
+ .replace(/{H}/g, H + '')
.replace(/{hh}/g, pad(h + '', 2))
.replace(/{h}/g, h + '')
.replace(/{mm}/g, pad(m, 2))
- .replace(/{m}/g, m)
+ .replace(/{m}/g, m + '')
.replace(/{ss}/g, pad(s, 2))
- .replace(/{s}/g, s)
+ .replace(/{s}/g, s + '')
.replace(/{SSS}/g, pad(S, 3))
- .replace(/{S}/g, S);
+ .replace(/{S}/g, S + '');
}
export function leveledFormat(
@@ -220,13 +220,12 @@ export function getUnitFromValue(
isUTC: boolean
): PrimaryTimeUnit {
const date = numberUtil.parseDate(value);
- const utc = isUTC ? 'UTC' : '';
- const M = (date as any)['get' + utc + 'Month']() + 1;
- const d = (date as any)['get' + utc + 'Date']();
- const h = (date as any)['get' + utc + 'Hours']();
- const m = (date as any)['get' + utc + 'Minutes']();
- const s = (date as any)['get' + utc + 'Seconds']();
- const S = (date as any)['get' + utc + 'Milliseconds']();
+ const M = (date as any)[monthGetterName(isUTC)]() + 1;
+ const d = (date as any)[dateGetterName(isUTC)]();
+ const h = (date as any)[hoursGetterName(isUTC)]();
+ const m = (date as any)[minutesGetterName(isUTC)]();
+ const s = (date as any)[secondsGetterName(isUTC)]();
+ const S = (date as any)[millisecondsGetterName(isUTC)]();
const isSecond = S === 0;
const isMinute = isSecond && s === 0;
@@ -264,31 +263,86 @@ export function getUnitValue(
isUTC?: boolean
) : number {
const date = typeof value === 'number'
- ? numberUtil.parseDate(value) as any
+ ? numberUtil.parseDate(value)
: value;
unit = unit || getUnitFromValue(value, isUTC);
- const utc = isUTC ? 'UTC' : '';
switch (unit) {
case 'year':
- return date['get' + utc + 'FullYear']();
+ return date[fullYearGetterName(isUTC)]();
case 'half-year':
- return date['get' + utc + 'Month']() >= 6 ? 1 : 0;
+ return date[monthGetterName(isUTC)]() >= 6 ? 1 : 0;
case 'quarter':
- return Math.floor((date['get' + utc + 'Month']() + 1) / 4);
+ return Math.floor((date[monthGetterName(isUTC)]() + 1) / 4);
case 'month':
- return date['get' + utc + 'Month']();
+ return date[monthGetterName(isUTC)]();
case 'day':
- return date['get' + utc + 'Date']();
+ return date[dateGetterName(isUTC)]();
case 'half-day':
- return date['get' + utc + 'Hours']() / 24;
+ return date[hoursGetterName(isUTC)]() / 24;
case 'hour':
- return date['get' + utc + 'Hours']();
+ return date[hoursGetterName(isUTC)]();
case 'minute':
- return date['get' + utc + 'Minutes']();
+ return date[minutesGetterName(isUTC)]();
case 'second':
- return date['get' + utc + 'Seconds']();
+ return date[secondsGetterName(isUTC)]();
case 'millisecond':
- return date['get' + utc + 'Milliseconds']();
+ return date[millisecondsGetterName(isUTC)]();
}
}
+
+export function fullYearGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCFullYear' : 'getFullYear';
+}
+
+export function monthGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCMonth' : 'getMonth';
+}
+
+export function dateGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCDate' : 'getDate';
+}
+
+export function hoursGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCHours' : 'getHours';
+}
+
+export function minutesGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCMinutes' : 'getMinutes';
+}
+
+export function secondsGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCSeconds' : 'getSeconds';
+}
+
+export function millisecondsGetterName(isUTC?: boolean) {
+ return isUTC ? 'getUTCSeconds' : 'getSeconds';
+}
+
+export function fullYearSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCFullYear' : 'setFullYear';
+}
+
+export function monthSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCMonth' : 'setMonth';
+}
+
+export function dateSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCDate' : 'setDate';
+}
+
+export function hoursSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCHours' : 'setHours';
+}
+
+export function minutesSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCMinutes' : 'setMinutes';
+}
+
+export function secondsSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCSeconds' : 'setSeconds';
+}
+
+export function millisecondsSetterName(isUTC?: boolean) {
+ return isUTC ? 'setUTCSeconds' : 'setSeconds';
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org