You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
605 lines
19 KiB
605 lines
19 KiB
|
|
/* |
|
* Licensed to the Apache Software Foundation (ASF) under one |
|
* or more contributor license agreements. See the NOTICE file |
|
* distributed with this work for additional information |
|
* regarding copyright ownership. The ASF licenses this file |
|
* to you under the Apache License, Version 2.0 (the |
|
* "License"); you may not use this file except in compliance |
|
* with the License. You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, |
|
* software distributed under the License is distributed on an |
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
* KIND, either express or implied. See the License for the |
|
* specific language governing permissions and limitations |
|
* under the License. |
|
*/ |
|
|
|
|
|
/** |
|
* AUTO-GENERATED FILE. DO NOT MODIFY. |
|
*/ |
|
|
|
/* |
|
* Licensed to the Apache Software Foundation (ASF) under one |
|
* or more contributor license agreements. See the NOTICE file |
|
* distributed with this work for additional information |
|
* regarding copyright ownership. The ASF licenses this file |
|
* to you under the Apache License, Version 2.0 (the |
|
* "License"); you may not use this file except in compliance |
|
* with the License. You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, |
|
* software distributed under the License is distributed on an |
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
* KIND, either express or implied. See the License for the |
|
* specific language governing permissions and limitations |
|
* under the License. |
|
*/ |
|
import { __extends } from "tslib"; |
|
/* |
|
* A third-party license is embedded for some of the code in this file: |
|
* The "scaleLevels" was originally copied from "d3.js" with some |
|
* modifications made for this project. |
|
* (See more details in the comment on the definition of "scaleLevels" below.) |
|
* The use of the source code of this file is also subject to the terms |
|
* and consitions of the license of "d3.js" (BSD-3Clause, see |
|
* </licenses/LICENSE-d3>). |
|
*/ |
|
// [About UTC and local time zone]: |
|
// In most cases, `number.parseDate` will treat input data string as local time |
|
// (except time zone is specified in time string). And `format.formateTime` returns |
|
// local time by default. option.useUTC is false by default. This design has |
|
// considered these common cases: |
|
// (1) Time that is persistent in server is in UTC, but it is needed to be displayed |
|
// in local time by default. |
|
// (2) By default, the input data string (e.g., '2011-01-02') should be displayed |
|
// as its original time, without any time difference. |
|
|
|
import * as numberUtil from '../util/number.js'; |
|
import { ONE_SECOND, ONE_MINUTE, ONE_HOUR, ONE_DAY, ONE_YEAR, format, leveledFormat, getUnitValue, timeUnits, fullLeveledFormatter, getPrimaryTimeUnit, isPrimaryTimeUnit, getDefaultFormatPrecisionOfInterval, fullYearGetterName, monthSetterName, fullYearSetterName, dateSetterName, hoursGetterName, hoursSetterName, minutesSetterName, secondsSetterName, millisecondsSetterName, monthGetterName, dateGetterName, minutesGetterName, secondsGetterName, millisecondsGetterName } from '../util/time.js'; |
|
import * as scaleHelper from './helper.js'; |
|
import IntervalScale from './Interval.js'; |
|
import Scale from './Scale.js'; |
|
import { warn } from '../util/log.js'; |
|
import { filter, isNumber, map } from 'zrender/lib/core/util.js'; // FIXME 公用? |
|
|
|
var bisect = function (a, x, lo, hi) { |
|
while (lo < hi) { |
|
var mid = lo + hi >>> 1; |
|
|
|
if (a[mid][1] < x) { |
|
lo = mid + 1; |
|
} else { |
|
hi = mid; |
|
} |
|
} |
|
|
|
return lo; |
|
}; |
|
|
|
var TimeScale = |
|
/** @class */ |
|
function (_super) { |
|
__extends(TimeScale, _super); |
|
|
|
function TimeScale(settings) { |
|
var _this = _super.call(this, settings) || this; |
|
|
|
_this.type = 'time'; |
|
return _this; |
|
} |
|
/** |
|
* Get label is mainly for other components like dataZoom, tooltip. |
|
*/ |
|
|
|
|
|
TimeScale.prototype.getLabel = function (tick) { |
|
var useUTC = this.getSetting('useUTC'); |
|
return format(tick.value, fullLeveledFormatter[getDefaultFormatPrecisionOfInterval(getPrimaryTimeUnit(this._minLevelUnit))] || fullLeveledFormatter.second, useUTC, this.getSetting('locale')); |
|
}; |
|
|
|
TimeScale.prototype.getFormattedLabel = function (tick, idx, labelFormatter) { |
|
var isUTC = this.getSetting('useUTC'); |
|
var lang = this.getSetting('locale'); |
|
return leveledFormat(tick, idx, labelFormatter, lang, isUTC); |
|
}; |
|
/** |
|
* @override |
|
*/ |
|
|
|
|
|
TimeScale.prototype.getTicks = function () { |
|
var interval = this._interval; |
|
var extent = this._extent; |
|
var ticks = []; // If interval is 0, return []; |
|
|
|
if (!interval) { |
|
return ticks; |
|
} |
|
|
|
ticks.push({ |
|
value: extent[0], |
|
level: 0 |
|
}); |
|
var useUTC = this.getSetting('useUTC'); |
|
var innerTicks = getIntervalTicks(this._minLevelUnit, this._approxInterval, useUTC, extent); |
|
ticks = ticks.concat(innerTicks); |
|
ticks.push({ |
|
value: extent[1], |
|
level: 0 |
|
}); |
|
return ticks; |
|
}; |
|
|
|
TimeScale.prototype.calcNiceExtent = function (opt) { |
|
var extent = this._extent; // If extent start and end are same, expand them |
|
|
|
if (extent[0] === extent[1]) { |
|
// Expand extent |
|
extent[0] -= ONE_DAY; |
|
extent[1] += ONE_DAY; |
|
} // If there are no data and extent are [Infinity, -Infinity] |
|
|
|
|
|
if (extent[1] === -Infinity && extent[0] === Infinity) { |
|
var d = new Date(); |
|
extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate()); |
|
extent[0] = extent[1] - ONE_DAY; |
|
} |
|
|
|
this.calcNiceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); |
|
}; |
|
|
|
TimeScale.prototype.calcNiceTicks = function (approxTickNum, minInterval, maxInterval) { |
|
approxTickNum = approxTickNum || 10; |
|
var extent = this._extent; |
|
var span = extent[1] - extent[0]; |
|
this._approxInterval = span / approxTickNum; |
|
|
|
if (minInterval != null && this._approxInterval < minInterval) { |
|
this._approxInterval = minInterval; |
|
} |
|
|
|
if (maxInterval != null && this._approxInterval > maxInterval) { |
|
this._approxInterval = maxInterval; |
|
} |
|
|
|
var scaleIntervalsLen = scaleIntervals.length; |
|
var idx = Math.min(bisect(scaleIntervals, this._approxInterval, 0, scaleIntervalsLen), scaleIntervalsLen - 1); // 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]; |
|
}; |
|
|
|
TimeScale.prototype.parse = function (val) { |
|
// val might be float. |
|
return isNumber(val) ? val : +numberUtil.parseDate(val); |
|
}; |
|
|
|
TimeScale.prototype.contain = function (val) { |
|
return scaleHelper.contain(this.parse(val), this._extent); |
|
}; |
|
|
|
TimeScale.prototype.normalize = function (val) { |
|
return scaleHelper.normalize(this.parse(val), this._extent); |
|
}; |
|
|
|
TimeScale.prototype.scale = function (val) { |
|
return scaleHelper.scale(val, this._extent); |
|
}; |
|
|
|
TimeScale.type = 'time'; |
|
return TimeScale; |
|
}(IntervalScale); |
|
/** |
|
* This implementation was originally copied from "d3.js" |
|
* <https://github.com/d3/d3/blob/b516d77fb8566b576088e73410437494717ada26/src/time/scale.js> |
|
* with some modifications made for this program. |
|
* See the license statement at the head of this file. |
|
*/ |
|
|
|
|
|
var scaleIntervals = [// Format interval |
|
['second', ONE_SECOND], ['minute', ONE_MINUTE], ['hour', ONE_HOUR], ['quarter-day', ONE_HOUR * 6], ['half-day', ONE_HOUR * 12], ['day', ONE_DAY * 1.2], ['half-week', ONE_DAY * 3.5], ['week', ONE_DAY * 7], ['month', ONE_DAY * 31], ['quarter', ONE_DAY * 95], ['half-year', ONE_YEAR / 2], ['year', ONE_YEAR] // 1Y |
|
]; |
|
|
|
function isUnitValueSame(unit, valueA, valueB, isUTC) { |
|
var dateA = numberUtil.parseDate(valueA); |
|
var dateB = numberUtil.parseDate(valueB); |
|
|
|
var isSame = function (unit) { |
|
return getUnitValue(dateA, unit, isUTC) === getUnitValue(dateB, unit, isUTC); |
|
}; |
|
|
|
var isSameYear = function () { |
|
return isSame('year'); |
|
}; // const isSameHalfYear = () => isSameYear() && isSame('half-year'); |
|
// const isSameQuater = () => isSameYear() && isSame('quarter'); |
|
|
|
|
|
var isSameMonth = function () { |
|
return isSameYear() && isSame('month'); |
|
}; |
|
|
|
var isSameDay = function () { |
|
return isSameMonth() && isSame('day'); |
|
}; // const isSameHalfDay = () => isSameDay() && isSame('half-day'); |
|
|
|
|
|
var isSameHour = function () { |
|
return isSameDay() && isSame('hour'); |
|
}; |
|
|
|
var isSameMinute = function () { |
|
return isSameHour() && isSame('minute'); |
|
}; |
|
|
|
var isSameSecond = function () { |
|
return isSameMinute() && isSame('second'); |
|
}; |
|
|
|
var isSameMilliSecond = function () { |
|
return isSameSecond() && isSame('millisecond'); |
|
}; |
|
|
|
switch (unit) { |
|
case 'year': |
|
return isSameYear(); |
|
|
|
case 'month': |
|
return isSameMonth(); |
|
|
|
case 'day': |
|
return isSameDay(); |
|
|
|
case 'hour': |
|
return isSameHour(); |
|
|
|
case 'minute': |
|
return isSameMinute(); |
|
|
|
case 'second': |
|
return isSameSecond(); |
|
|
|
case 'millisecond': |
|
return isSameMilliSecond(); |
|
} |
|
} // 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, daysInMonth) { |
|
approxInterval /= ONE_DAY; |
|
return approxInterval > 16 ? 16 // Math.floor(daysInMonth / 2) + 1 // In this case we only want one tick between two months. |
|
: approxInterval > 7.5 ? 7 // TODO week 7 or day 8? |
|
: approxInterval > 3.5 ? 4 : approxInterval > 1.5 ? 2 : 1; |
|
} |
|
|
|
function getMonthInterval(approxInterval) { |
|
var APPROX_ONE_MONTH = 30 * ONE_DAY; |
|
approxInterval /= APPROX_ONE_MONTH; |
|
return approxInterval > 6 ? 6 : approxInterval > 3 ? 3 : approxInterval > 2 ? 2 : 1; |
|
} |
|
|
|
function getHourInterval(approxInterval) { |
|
approxInterval /= ONE_HOUR; |
|
return approxInterval > 12 ? 12 : approxInterval > 6 ? 6 : approxInterval > 3.5 ? 4 : approxInterval > 2 ? 2 : 1; |
|
} |
|
|
|
function getMinutesAndSecondsInterval(approxInterval, isMinutes) { |
|
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) { |
|
return numberUtil.nice(approxInterval, true); |
|
} |
|
|
|
function getFirstTimestampOfUnit(date, unitName, isUTC) { |
|
var 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(bottomUnitName, approxInterval, isUTC, extent) { |
|
var safeLimit = 10000; |
|
var unitNames = timeUnits; |
|
var iter = 0; |
|
|
|
function addTicksInSpan(interval, minTimestamp, maxTimestamp, getMethodName, setMethodName, isDate, out) { |
|
var date = new Date(minTimestamp); |
|
var dateTime = minTimestamp; |
|
var d = date[getMethodName](); // if (isDate) { |
|
// d -= 1; // Starts with 0; PENDING |
|
// } |
|
|
|
while (dateTime < maxTimestamp && dateTime <= extent[1]) { |
|
out.push({ |
|
value: dateTime |
|
}); |
|
d += interval; |
|
date[setMethodName](d); |
|
dateTime = date.getTime(); |
|
} // This extra tick is for calcuating ticks of next level. Will not been added to the final result |
|
|
|
|
|
out.push({ |
|
value: dateTime, |
|
notAdd: true |
|
}); |
|
} |
|
|
|
function addLevelTicks(unitName, lastLevelTicks, levelTicks) { |
|
var newAddedTicks = []; |
|
var isFirstLevel = !lastLevelTicks.length; |
|
|
|
if (isUnitValueSame(getPrimaryTimeUnit(unitName), extent[0], extent[1], isUTC)) { |
|
return; |
|
} |
|
|
|
if (isFirstLevel) { |
|
lastLevelTicks = [{ |
|
// TODO Optimize. Not include so may ticks. |
|
value: getFirstTimestampOfUnit(new Date(extent[0]), unitName, isUTC) |
|
}, { |
|
value: extent[1] |
|
}]; |
|
} |
|
|
|
for (var i = 0; i < lastLevelTicks.length - 1; i++) { |
|
var startTick = lastLevelTicks[i].value; |
|
var endTick = lastLevelTicks[i + 1].value; |
|
|
|
if (startTick === endTick) { |
|
continue; |
|
} |
|
|
|
var interval = void 0; |
|
var getterName = void 0; |
|
var setterName = void 0; |
|
var isDate = false; |
|
|
|
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; |
|
} |
|
|
|
addTicksInSpan(interval, startTick, endTick, getterName, setterName, isDate, newAddedTicks); |
|
|
|
if (unitName === 'year' && levelTicks.length > 1 && i === 0) { |
|
// Add nearest years to the left extent. |
|
levelTicks.unshift({ |
|
value: levelTicks[0].value - interval |
|
}); |
|
} |
|
} |
|
|
|
for (var i = 0; i < newAddedTicks.length; i++) { |
|
levelTicks.push(newAddedTicks[i]); |
|
} // newAddedTicks.length && console.log(unitName, newAddedTicks); |
|
|
|
|
|
return newAddedTicks; |
|
} |
|
|
|
var levelsTicks = []; |
|
var currentLevelTicks = []; |
|
var tickCount = 0; |
|
var lastLevelTickCount = 0; |
|
|
|
for (var i = 0; i < unitNames.length && iter++ < safeLimit; ++i) { |
|
var primaryTimeUnit = getPrimaryTimeUnit(unitNames[i]); |
|
|
|
if (!isPrimaryTimeUnit(unitNames[i])) { |
|
// TODO |
|
continue; |
|
} |
|
|
|
addLevelTicks(unitNames[i], levelsTicks[levelsTicks.length - 1] || [], currentLevelTicks); |
|
var nextPrimaryTimeUnit = unitNames[i + 1] ? getPrimaryTimeUnit(unitNames[i + 1]) : null; |
|
|
|
if (primaryTimeUnit !== nextPrimaryTimeUnit) { |
|
if (currentLevelTicks.length) { |
|
lastLevelTickCount = tickCount; // Remove the duplicate so the tick count can be precisely. |
|
|
|
currentLevelTicks.sort(function (a, b) { |
|
return a.value - b.value; |
|
}); |
|
var levelTicksRemoveDuplicated = []; |
|
|
|
for (var i_1 = 0; i_1 < currentLevelTicks.length; ++i_1) { |
|
var tickValue = currentLevelTicks[i_1].value; |
|
|
|
if (i_1 === 0 || currentLevelTicks[i_1 - 1].value !== tickValue) { |
|
levelTicksRemoveDuplicated.push(currentLevelTicks[i_1]); |
|
|
|
if (tickValue >= extent[0] && tickValue <= extent[1]) { |
|
tickCount++; |
|
} |
|
} |
|
} |
|
|
|
var targetTickNum = (extent[1] - extent[0]) / approxInterval; // Added too much in this level and not too less in last level |
|
|
|
if (tickCount > targetTickNum * 1.5 && lastLevelTickCount > targetTickNum / 1.5) { |
|
break; |
|
} // Only treat primary time unit as one level. |
|
|
|
|
|
levelsTicks.push(levelTicksRemoveDuplicated); |
|
|
|
if (tickCount > targetTickNum || bottomUnitName === unitNames[i]) { |
|
break; |
|
} |
|
} // Reset if next unitName is primary |
|
|
|
|
|
currentLevelTicks = []; |
|
} |
|
} |
|
|
|
if (process.env.NODE_ENV !== 'production') { |
|
if (iter >= safeLimit) { |
|
warn('Exceed safe limit.'); |
|
} |
|
} |
|
|
|
var levelsTicksInExtent = filter(map(levelsTicks, function (levelTicks) { |
|
return filter(levelTicks, function (tick) { |
|
return tick.value >= extent[0] && tick.value <= extent[1] && !tick.notAdd; |
|
}); |
|
}), function (levelTicks) { |
|
return levelTicks.length > 0; |
|
}); |
|
var ticks = []; |
|
var maxLevel = levelsTicksInExtent.length - 1; |
|
|
|
for (var i = 0; i < levelsTicksInExtent.length; ++i) { |
|
var levelTicks = levelsTicksInExtent[i]; |
|
|
|
for (var k = 0; k < levelTicks.length; ++k) { |
|
ticks.push({ |
|
value: levelTicks[k].value, |
|
level: maxLevel - i |
|
}); |
|
} |
|
} |
|
|
|
ticks.sort(function (a, b) { |
|
return a.value - b.value; |
|
}); // Remove duplicates |
|
|
|
var result = []; |
|
|
|
for (var i = 0; i < ticks.length; ++i) { |
|
if (i === 0 || ticks[i].value !== ticks[i - 1].value) { |
|
result.push(ticks[i]); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
Scale.registerClass(TimeScale); |
|
export default TimeScale; |