UNPKG

highcharts

Version:
223 lines (222 loc) 9.43 kB
/* * * * (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 * * * */ 'use strict'; 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;