highcharts
Version:
JavaScript charting framework
223 lines (222 loc) • 9.43 kB
JavaScript
/* *
*
* (c) 2010-2026 Highsoft AS
* Author: Torstein Hønsi
*
* Integration of this software requires a license.
* - For commercial use, see www.highcharts.com/license
* - For non-commercial, see www.highcharts.com/license-eula
*
*
* */
;
import TimeBase from '../Shared/TimeBase.js';
import { defined, extend } from '../Shared/Utilities.js';
import { timeUnits } from './Utilities.js';
/* *
*
* Constants
*
* */
class Time extends TimeBase {
getBoundaryTicks(tickPositions, unitRange, visibleMin, visibleMax) {
const boundaryTicks = {};
// No boundary ticks for year range.
if (unitRange === timeUnits.year) {
return boundaryTicks;
}
const hasVisibleRange = defined(visibleMin) && defined(visibleMax), needsHourBoundary = unitRange < timeUnits.hour, needsDayBoundary = unitRange < timeUnits.day, needsMonthBoundary = unitRange < timeUnits.month, tickAlignment = (unitRange >= timeUnits.hour ?
900000 : unitRange >= timeUnits.minute ?
60000 : unitRange >= timeUnits.second ?
1000 : false);
// Handle boundary ticks. Use a reasonable dropout threshold
// to prevent looping over dense data grouping (#6156).
if (tickPositions.length < 10000) {
let isFirstVisibleTick = true;
for (let i = 0; i < tickPositions.length; i++) {
const t = tickPositions[i];
// Loop only over visible ticks.
if (hasVisibleRange) {
if (t < visibleMin) {
continue;
}
if (t > visibleMax) {
break;
}
}
// Mark first visible tick as boundary, if timeUnit is month or
// hour.
if (isFirstVisibleTick) {
if (unitRange === timeUnits.month) {
boundaryTicks[t] = 'year';
isFirstVisibleTick = false;
continue;
}
if (unitRange === timeUnits.hour) {
boundaryTicks[t] = 'day';
isFirstVisibleTick = false;
continue;
}
}
// Skip misaligned ticks to save performance.
if (tickAlignment && t % tickAlignment !== 0) {
isFirstVisibleTick = false;
continue;
}
const [, // Unused 'year' var
month, day, hours, minutes, seconds, milliseconds] = this.toParts(t);
const isMidnight = !hours && !minutes && !seconds && !milliseconds;
if (needsHourBoundary && minutes === 0) {
boundaryTicks[t] = 'hour';
}
if (needsDayBoundary && isMidnight) {
boundaryTicks[t] = 'day';
}
if (needsMonthBoundary && day === 1 && isMidnight) {
boundaryTicks[t] = 'month';
}
if (month === 0 && day === 1 && isMidnight) {
boundaryTicks[t] = 'year';
}
isFirstVisibleTick = false;
}
}
return boundaryTicks;
}
/**
* Return an array with time positions distributed on round time values
* right and right after min and max. Used in datetime axes as well as for
* grouping data on a datetime axis.
*
* @function Highcharts.Time#getTimeTicks
*
* @param {Highcharts.TimeNormalizedObject} normalizedInterval
* The interval in axis values (ms) and the count
*
* @param {number} [min]
* The minimum in axis values
*
* @param {number} [max]
* The maximum in axis values
*
* @param {number} [startOfWeek=1]
*
* @return {Highcharts.AxisTickPositionsArray}
* Time positions
*/
getTimeTicks(normalizedInterval, min, max, startOfWeek) {
const time = this, tickPositions = [], { count = 1, unitRange } = normalizedInterval, visibleMin = min, visibleMax = max;
let [year, month, dayOfMonth, hours, minutes, seconds] = time.toParts(min), milliseconds = (min || 0) % 1000, variableDayLength;
startOfWeek ?? (startOfWeek = 1);
if (defined(min)) { // #1300
milliseconds = unitRange >= timeUnits.second ?
0 : // #3935
count * Math.floor(milliseconds / count);
if (unitRange >= timeUnits.second) { // Second
seconds = unitRange >= timeUnits.minute ?
0 : // #3935
count * Math.floor(seconds / count);
}
if (unitRange >= timeUnits.minute) { // Minute
minutes = unitRange >= timeUnits.hour ?
0 :
count * Math.floor(minutes / count);
}
if (unitRange >= timeUnits.hour) { // Hour
hours = unitRange >= timeUnits.day ?
0 :
count * Math.floor(hours / count);
}
if (unitRange >= timeUnits.day) { // Day
dayOfMonth = unitRange >= timeUnits.month ?
1 :
Math.max(1, count * Math.floor(dayOfMonth / count));
}
if (unitRange >= timeUnits.month) { // Month
month = unitRange >= timeUnits.year ? 0 :
count * Math.floor(month / count);
}
if (unitRange >= timeUnits.year) { // Year
year -= year % count;
}
// Week is a special case that runs outside the hierarchy
if (unitRange === timeUnits.week) {
if (count) {
min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds);
}
// Get start of current week, independent of count
const weekday = this.dateTimeFormat({
timeZone: this.timezone,
weekday: 'narrow'
}, min, 'es'),
// Spanish weekday index
weekdayNo = 'DLMXJVS'.indexOf(weekday);
dayOfMonth += -weekdayNo + startOfWeek +
// We don't want to skip days that are before
// startOfWeek (#7051)
(weekdayNo < startOfWeek ? -7 : 0);
}
min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds);
// Handle local timezone offset
if (time.variableTimezone && defined(max)) {
// Detect whether we need to take the DST crossover into
// consideration. If we're crossing over DST, the day length may
// be 23h or 25h and we need to compute the exact clock time for
// each tick instead of just adding hours. This comes at a cost,
// so first we find out if it is needed (#4951).
variableDayLength = (
// Long range, assume we're crossing over.
max - min > 4 * timeUnits.month ||
// Short range, check if min and max are in different time
// zones.
time.getTimezoneOffset(min) !==
time.getTimezoneOffset(max));
}
// Iterate and add tick positions at appropriate values
let t = min, i = 1;
while (t < max) {
tickPositions.push(t);
// Increase the years
if (unitRange === timeUnits.year) {
t = time.makeTime(year + i * count, 0);
// Increase the months
}
else if (unitRange === timeUnits.month) {
t = time.makeTime(year, month + i * count);
// If we're using local time, the interval is not fixed as it
// jumps one hour at the DST crossover
}
else if (variableDayLength && (unitRange === timeUnits.day ||
unitRange === timeUnits.week)) {
t = time.makeTime(year, month, dayOfMonth +
i * count * (unitRange === timeUnits.day ? 1 : 7));
}
else if (variableDayLength &&
unitRange === timeUnits.hour &&
count > 1) {
// Make sure boundary ticks are preserved across DST (#6797,
// #7621)
t = time.makeTime(year, month, dayOfMonth, hours + i * count);
// Else, the interval is fixed and we use simple addition
}
else {
t += unitRange * count;
}
i++;
}
// Push the last time
tickPositions.push(t);
}
tickPositions.info = extend(normalizedInterval, {
boundaryTicks: this.getBoundaryTicks(tickPositions, unitRange, visibleMin, visibleMax),
totalRange: unitRange * count
});
return tickPositions;
}
}
/* *
*
* Default export
*
* */
export default Time;