// Copyright (C) 2018 The Android Open Source Project // // Licensed 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 {assertTrue} from '../base/logging'; import {duration, time, Time, TimeSpan} from '../base/time'; const micros = 1000n; const millis = 1000n * micros; const seconds = 1000n * millis; const minutes = 60n * seconds; const hours = 60n * minutes; const days = 24n * hours; // These patterns cover the entire range of 0 - 2^63-1 nanoseconds const patterns: [bigint, string][] = [ [1n, '|'], [2n, '|:'], [5n, '|....'], [10n, '|....:....'], [20n, '|.:.'], [50n, '|....'], [100n, '|....:....'], [200n, '|.:.'], [500n, '|....'], [1n * micros, '|....:....'], [2n * micros, '|.:.'], [5n * micros, '|....'], [10n * micros, '|....:....'], [20n * micros, '|.:.'], [50n * micros, '|....'], [100n * micros, '|....:....'], [200n * micros, '|.:.'], [500n * micros, '|....'], [1n * millis, '|....:....'], [2n * millis, '|.:.'], [5n * millis, '|....'], [10n * millis, '|....:....'], [20n * millis, '|.:.'], [50n * millis, '|....'], [100n * millis, '|....:....'], [200n * millis, '|.:.'], [500n * millis, '|....'], [1n * seconds, '|....:....'], [2n * seconds, '|.:.'], [5n * seconds, '|....'], [10n * seconds, '|....:....'], [30n * seconds, '|.:.:.'], [1n * minutes, '|.....'], [2n * minutes, '|.:.'], [5n * minutes, '|.....'], [10n * minutes, '|....:....'], [30n * minutes, '|.:.:.'], [1n * hours, '|.....'], [2n * hours, '|.:.'], [6n * hours, '|.....'], [12n * hours, '|.....:.....'], [1n * days, '|.:.'], [2n * days, '|.:.'], [5n * days, '|....'], [10n * days, '|....:....'], [20n * days, '|.:.'], [50n * days, '|....'], [100n * days, '|....:....'], [200n * days, '|.:.'], [500n * days, '|....'], [1000n * days, '|....:....'], [2000n * days, '|.:.'], [5000n * days, '|....'], [10000n * days, '|....:....'], [20000n * days, '|.:.'], [50000n * days, '|....'], [100000n * days, '|....:....'], [200000n * days, '|.:.'], ]; // Returns the optimal step size and pattern of ticks within the step. export function getPattern(minPatternSize: bigint): [duration, string] { for (const [size, pattern] of patterns) { if (size >= minPatternSize) { return [size, pattern]; } } throw new Error('Pattern not defined for this minsize'); } function tickPatternToArray(pattern: string): TickType[] { const array = Array.from(pattern); return array.map((char) => { switch (char) { case '|': return TickType.MAJOR; case ':': return TickType.MEDIUM; case '.': return TickType.MINOR; default: // This is almost certainly a developer/fat-finger error throw Error(`Invalid char "${char}" in pattern "${pattern}"`); } }); } export enum TickType { MAJOR, MEDIUM, MINOR, } export interface Tick { type: TickType; time: time; } export const MIN_PX_PER_STEP = 120; export function getMaxMajorTicks(width: number) { return Math.max(1, Math.floor(width / MIN_PX_PER_STEP)); } export function* generateTicks( timeSpan: TimeSpan, maxMajorTicks: number, offset: time = Time.ZERO, ): Generator { assertTrue(timeSpan.duration > 0n, 'timeSpan.duration cannot be lte 0'); assertTrue(maxMajorTicks > 0, 'maxMajorTicks cannot be lte 0'); timeSpan = timeSpan.translate(-offset); const minStepSize = BigInt( Math.floor(Number(timeSpan.duration) / maxMajorTicks), ); const [patternSize, pattern] = getPattern(minStepSize); const tickPattern = tickPatternToArray(pattern); const stepSize = patternSize / BigInt(tickPattern.length); const start = Time.quantFloor(timeSpan.start, patternSize); const end = timeSpan.end; let patternIndex = 0; for ( let time = start; time < end; time = Time.add(time, stepSize), patternIndex++ ) { if (time >= timeSpan.start) { patternIndex = patternIndex % tickPattern.length; const type = tickPattern[patternIndex]; yield {type, time: Time.add(time, offset)}; } } }