You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by ov...@apache.org on 2020/06/28 07:09:15 UTC

[incubator-echarts] branch time-formatter created (now e44445d)

This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a change to branch time-formatter
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git.


      at e44445d  feat(time): improve time axis formatter

This branch includes the following new commits:

     new e44445d  feat(time): improve time axis formatter

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org


[incubator-echarts] 01/01: feat(time): improve time axis formatter

Posted by ov...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch time-formatter
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit e44445dac2f0efee032d674ee0648d14d31a397e
Author: Ovilia <zw...@gmail.com>
AuthorDate: Wed Jun 24 17:42:41 2020 +0800

    feat(time): improve time axis formatter
---
 src/coord/axisDefault.ts |   4 +-
 src/scale/Time.ts        | 304 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/util/format.ts       |  23 +++-
 3 files changed, 319 insertions(+), 12 deletions(-)

diff --git a/src/coord/axisDefault.ts b/src/coord/axisDefault.ts
index 3777855..770b30a 100644
--- a/src/coord/axisDefault.ts
+++ b/src/coord/axisDefault.ts
@@ -163,8 +163,8 @@ const valueAxis: AxisBaseOption = zrUtil.merge({
 
 const timeAxis: AxisBaseOption = zrUtil.defaults({
     scale: true,
-    min: 'dataMin',
-    max: 'dataMax'
+    // min: 'dataMin',
+    // max: 'dataMax'
 }, valueAxis);
 
 const logAxis: AxisBaseOption = zrUtil.defaults({
diff --git a/src/scale/Time.ts b/src/scale/Time.ts
index f2f5734..987a9dd 100644
--- a/src/scale/Time.ts
+++ b/src/scale/Time.ts
@@ -82,9 +82,63 @@ class TimeScale extends IntervalScale {
     getLabel(val: number): string {
         const stepLvl = this._stepLvl;
 
-        const date = new Date(val);
+        const labelFormatType = getLabelFormatType(val, this.getSetting('useUTC'), false);
+        return formatUtil.formatTime(labelFormatType, val);
+    }
+
+    /**
+     * @override
+     * @param expandToNicedExtent Whether expand the ticks to niced extent.
+     */
+    getTicks(expandToNicedExtent?: boolean): number[] {
+        const interval = this._interval;
+        const extent = this._extent;
+        const niceTickExtent = this._niceExtent;
+
+        let ticks = [] as number[];
+        // If interval is 0, return [];
+        if (!interval) {
+            return ticks;
+        }
+
+        const safeLimit = 10000;
 
-        return formatUtil.formatTime(stepLvl[0], date, this.getSetting('useUTC'));
+        if (extent[0] < niceTickExtent[0]) {
+            if (expandToNicedExtent) {
+                ticks.push(numberUtil.round(niceTickExtent[0] - interval, 0));
+            }
+            else {
+                ticks.push(extent[0]);
+            }
+        }
+
+        const useUTC = this.getSetting('useUTC');
+
+        const scaleLevelsLen = primaryScaleLevels.length;
+        const idx = bisect(primaryScaleLevels, this._interval, 0, scaleLevelsLen);
+        const level = primaryScaleLevels[Math.min(idx, scaleLevelsLen - 1)];
+
+        const innerTicks = getLevelTicks(
+            level[0] as TimeAxisLabelPrimaryLevel,
+            useUTC,
+            extent
+        );
+        console.log(innerTicks);
+        ticks = ticks.concat(innerTicks);
+
+        // Consider this case: the last item of ticks is smaller
+        // than niceTickExtent[1] and niceTickExtent[1] === extent[1].
+        const lastNiceTick = ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1];
+        if (extent[1] > lastNiceTick) {
+            if (expandToNicedExtent) {
+                ticks.push(numberUtil.round(lastNiceTick + interval, 0));
+            }
+            else {
+                ticks.push(extent[1]);
+            }
+        }
+
+        return ticks;
     }
 
     niceExtent(
@@ -115,11 +169,13 @@ class TimeScale extends IntervalScale {
         // let extent = this._extent;
         const interval = this._interval;
 
+        const timezoneOffset = this.getSetting('useUTC')
+            ? 0 : (new Date(+extent[0] || +extent[1])).getTimezoneOffset() * 60 * 1000;
         if (!opt.fixMin) {
-            extent[0] = numberUtil.round(mathFloor(extent[0] / interval) * interval);
+            extent[0] = numberUtil.round(mathFloor((extent[0] - timezoneOffset) / interval) * interval) + timezoneOffset;
         }
         if (!opt.fixMax) {
-            extent[1] = numberUtil.round(mathCeil(extent[1] / interval) * interval);
+            extent[1] = numberUtil.round(mathCeil((extent[1] - timezoneOffset) / interval) * interval) + timezoneOffset;
         }
     }
 
@@ -233,6 +289,246 @@ const scaleLevels = [
     ['year', ONE_DAY * 380]            // 1Y
 ] as [string, number][];
 
+const primaryScaleLevels = [
+    // Format              interval
+    ['second', ONE_SECOND],          // 1s
+    ['minute', ONE_MINUTE],      // 1m
+    ['hour', ONE_HOUR],        // 1h
+    ['day', ONE_DAY],          // 1d
+    ['week', ONE_DAY * 7],             // 7d
+    ['month', ONE_DAY * 31],           // 1M
+    ['year', ONE_DAY * 380]            // 1Y
+] as [string, number][];
+
+
+type TimeAxisLabelPrimaryLevel = 'millisecond'
+    | 'second' | 'minute' | 'hour'
+    | 'day' | 'month' | 'year';
+type TimeAxisLabelLevel = TimeAxisLabelPrimaryLevel
+    | 'week' | 'quarter' | 'half-year';
+
+const primaryLevels = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
+
+function getLabelFormatType(
+    value: number | string | Date,
+    isUTC: boolean,
+    primaryOnly: boolean
+): TimeAxisLabelLevel {
+    const date = numberUtil.parseDate(value);
+    const utc = isUTC ? 'UTC' : '';
+    const M = (date as any)['get' + utc + 'Month']() + 1;
+    const w = (date as any)['get' + utc + 'Day']();
+    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 isSecond = S === 0;
+    const isMinute = isSecond && s === 0;
+    const isHour = isMinute && m === 0;
+    const isDay = isHour && h === 0;
+    const isWeek = isDay && w === 0; // TODO: first day to be configured
+    const isMonth = isDay && d === 1;
+    const isQuarter = isMonth && (M % 3 === 1);
+    const isHalfYear = isMonth && (M % 6 === 1);
+    const isYear = isMonth && M === 1;
+
+    if (isYear) {
+        return 'year';
+    }
+    else if (isHalfYear && !primaryOnly) {
+        return 'half-year';
+    }
+    else if (isQuarter && !primaryOnly) {
+        return 'quarter';
+    }
+    else if (isMonth) {
+        return 'month';
+    }
+    else if (isWeek && !primaryOnly) {
+        return 'week';
+    }
+    else if (isDay) {
+        return 'day';
+    }
+    else if (isHour) {
+        return 'hour';
+    }
+    else if (isMinute) {
+        return 'minute';
+    }
+    else if (isSecond) {
+        return 'second';
+    }
+    else {
+        return 'millisecond';
+    }
+}
+
+
+function getLabelFormatValueFromLevel(value: number | Date, isUTC: boolean, level?: TimeAxisLabelLevel) : number {
+    const date = typeof value === 'number'
+        ? numberUtil.parseDate(value) as any
+        : value;
+    level = level || getLabelFormatType(value, isUTC, true);
+    const utc = isUTC ? 'UTC' : '';
+
+    switch (level) {
+        case 'millisecond':
+            return date['get' + utc + 'Milliseconds']();
+        case 'second':
+            return date['get' + utc + 'Seconds']();
+        case 'minute':
+            return date['get' + utc + 'Minutes']();
+        case 'hour':
+            return date['get' + utc + 'Hours']();
+        case 'day':
+            return date['get' + utc + 'Date']();
+        case 'month':
+            return date['get' + utc + 'Month']();
+        case 'year':
+            return date['get' + utc + 'FullYear']();
+    }
+}
+
+
+function isLevelValueSame(level: TimeAxisLabelPrimaryLevel, valueA: number, valueB: number, isUTC: boolean): boolean {
+    const dateA = numberUtil.parseDate(valueA) as any;
+    const dateB = numberUtil.parseDate(valueB) as any;
+    const utc = isUTC ? 'UTC' : '';
+    const isSame = (compareLevel: TimeAxisLabelPrimaryLevel) => {
+        console.log(getLabelFormatValueFromLevel(dateA, isUTC, compareLevel), getLabelFormatValueFromLevel(dateB, isUTC, compareLevel), dateA, dateB);
+        return getLabelFormatValueFromLevel(dateA, isUTC, compareLevel)
+            === getLabelFormatValueFromLevel(dateB, isUTC, compareLevel);
+    };
+
+    switch (level) {
+        case 'year':
+            return isSame('year');
+        case 'month':
+            return isSame('year') && isSame('month');
+        case 'day':
+            return isSame('year') && isSame('month') && isSame('day');
+        case 'hour':
+            return isSame('year') && isSame('month') && isSame('day')
+                && isSame('hour');
+        case 'minute':
+            return isSame('year') && isSame('month') && isSame('day')
+                && isSame('hour') && isSame('minute');
+        case 'second':
+            return isSame('year') && isSame('month') && isSame('day')
+                && isSame('hour') && isSame('minute') && isSame('second');
+        case 'millisecond':
+            return isSame('year') && isSame('month') && isSame('day')
+                && isSame('hour') && isSame('minute') && isSame('second')
+                && isSame('millisecond');
+    }
+}
+
+
+function getLevelTicks(level: TimeAxisLabelLevel, isUTC: boolean, extent: number[]) {
+    const utc = isUTC ? 'UTC' : '';
+    const ticks: number[] = [];
+    for (let i = 0; i < primaryLevels.length; ++i) {
+        let date = new Date(extent[0]) as any;
+
+        if (primaryLevels[i] === 'week') {
+            date['set' + utc + 'Hours'](0);
+            date['set' + utc + 'Minutes'](0);
+            date['set' + utc + 'Seconds'](0);
+            date['set' + utc + 'Milliseconds'](0);
+
+            let isDateWithinExtent = true;
+            while (isDateWithinExtent) {
+                const dates = date['get' + utc + 'Month']() + 1 === 2
+                    ? [8, 15, 22]
+                    : [8, 16, 23];
+                for (let d = 0; d < dates.length; ++d) {
+                    date['set' + utc + 'Date'](dates[d]);
+                    const dateTime = (date as Date).getTime();
+                    if (dateTime > extent[1]) {
+                        isDateWithinExtent = false;
+                        break;
+                    }
+                    else if (dateTime >= extent[0]) {
+                        ticks.push(dateTime);
+                    }
+                }
+                date['set' + utc + 'Month'](date['get' + utc + 'Month']() + 1);
+            }
+        }
+        else if (!isLevelValueSame(level as TimeAxisLabelPrimaryLevel, extent[0], extent[1], isUTC)) {
+            // Level value changes within extent
+            while (true) {
+                if (primaryLevels[i] === 'year') {
+                    date['set' + utc + 'FullYear'](date['get' + utc + 'FullYear']() + 1);
+                    date['set' + utc + 'Month'](0);
+                    date['set' + utc + 'Date'](1);
+                    date['set' + utc + 'Hours'](0);
+                    date['set' + utc + 'Minutes'](0);
+                    date['set' + utc + 'Seconds'](0);
+                }
+                else if (primaryLevels[i] === 'month') {
+                    // This also works with Dec.
+                    date['set' + utc + 'Month'](date['get' + utc + 'Month']() + 1);
+                    date['set' + utc + 'Date'](1);
+                    date['set' + utc + 'Hours'](0);
+                    date['set' + utc + 'Minutes'](0);
+                    date['set' + utc + 'Seconds'](0);
+                }
+                else if (primaryLevels[i] === 'day') {
+                    date['set' + utc + 'Date'](date['get' + utc + 'Day']() + 1);
+                    date['set' + utc + 'Hours'](0);
+                    date['set' + utc + 'Minutes'](0);
+                    date['set' + utc + 'Seconds'](0);
+                }
+                else if (primaryLevels[i] === 'hour') {
+                    date['set' + utc + 'Hours'](date['get' + utc + 'Hours']() + 1);
+                    date['set' + utc + 'Minutes'](0);
+                    date['set' + utc + 'Seconds'](0);
+                }
+                else if (primaryLevels[i] === 'minute') {
+                    date['set' + utc + 'Minutes'](date['get' + utc + 'Minutes']() + 1);
+                    date['set' + utc + 'Minutes'](0);
+                    date['set' + utc + 'Seconds'](0);
+                }
+                else if (primaryLevels[i] === 'second') {
+                    date['set' + utc + 'Seconds'](date['get' + utc + 'Seconds']() + 1);
+                }
+                date['set' + utc + 'Milliseconds'](0); // TODO: not sure
+
+                const dateValue = (date as Date).getTime();
+                if (dateValue < extent[1]) {
+                    ticks.push(dateValue);
+                }
+                else {
+                    break;
+                }
+            }
+        }
+
+        if (primaryLevels[i] === level) {
+            break;
+        }
+    }
+
+    ticks.sort((a, b) => a - b);
+    if (ticks.length <= 1) {
+        return ticks;
+    }
+
+    // Remove duplicates
+    const result = [];
+    for (let i = 1; i < ticks.length; ++i) {
+        if (ticks[i] !== ticks[i - 1]) {
+            result.push(ticks[i]);
+        }
+    }
+    return result;
+}
+
+
 Scale.registerClass(TimeScale);
 
 export default TimeScale;
diff --git a/src/util/format.ts b/src/util/format.ts
index 8a5b152..953f644 100644
--- a/src/util/format.ts
+++ b/src/util/format.ts
@@ -199,14 +199,25 @@ function pad(str: string, len: number): string {
  * @inner
  */
 export function formatTime(tpl: string, value: number | string | Date, isUTC?: boolean) {
-    if (tpl === 'week'
-        || tpl === 'month'
-        || tpl === 'quarter'
-        || tpl === 'half-year'
-        || tpl === 'year'
-    ) {
+    if (tpl === 'year') {
         tpl = 'MM-dd\nyyyy';
     }
+    else if (tpl === 'month' || tpl === 'quarter' || tpl === 'half-year'
+    ) {
+        tpl = 'M月';
+    }
+    else if (tpl === 'week' || tpl === 'day') {
+        tpl = 'M/d';
+    }
+    else if (tpl === 'hour' || tpl === 'minute') {
+        tpl = 'hh:mm';
+    }
+    else if (tpl === 'second') {
+        tpl = 'hh:mm:ss';
+    }
+    else if (tpl === 'millisecond') {
+        tpl = 'hh:mm:ss SSS';
+    }
 
     const date = numberUtil.parseDate(value);
     const utc = isUTC ? 'UTC' : '';


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org