1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {assertTrue} from '../base/logging'; 16import {duration, time, Time, TimeSpan} from '../base/time'; 17 18const micros = 1000n; 19const millis = 1000n * micros; 20const seconds = 1000n * millis; 21const minutes = 60n * seconds; 22const hours = 60n * minutes; 23const days = 24n * hours; 24 25// These patterns cover the entire range of 0 - 2^63-1 nanoseconds 26const patterns: [bigint, string][] = [ 27 [1n, '|'], 28 [2n, '|:'], 29 [5n, '|....'], 30 [10n, '|....:....'], 31 [20n, '|.:.'], 32 [50n, '|....'], 33 [100n, '|....:....'], 34 [200n, '|.:.'], 35 [500n, '|....'], 36 [1n * micros, '|....:....'], 37 [2n * micros, '|.:.'], 38 [5n * micros, '|....'], 39 [10n * micros, '|....:....'], 40 [20n * micros, '|.:.'], 41 [50n * micros, '|....'], 42 [100n * micros, '|....:....'], 43 [200n * micros, '|.:.'], 44 [500n * micros, '|....'], 45 [1n * millis, '|....:....'], 46 [2n * millis, '|.:.'], 47 [5n * millis, '|....'], 48 [10n * millis, '|....:....'], 49 [20n * millis, '|.:.'], 50 [50n * millis, '|....'], 51 [100n * millis, '|....:....'], 52 [200n * millis, '|.:.'], 53 [500n * millis, '|....'], 54 [1n * seconds, '|....:....'], 55 [2n * seconds, '|.:.'], 56 [5n * seconds, '|....'], 57 [10n * seconds, '|....:....'], 58 [30n * seconds, '|.:.:.'], 59 [1n * minutes, '|.....'], 60 [2n * minutes, '|.:.'], 61 [5n * minutes, '|.....'], 62 [10n * minutes, '|....:....'], 63 [30n * minutes, '|.:.:.'], 64 [1n * hours, '|.....'], 65 [2n * hours, '|.:.'], 66 [6n * hours, '|.....'], 67 [12n * hours, '|.....:.....'], 68 [1n * days, '|.:.'], 69 [2n * days, '|.:.'], 70 [5n * days, '|....'], 71 [10n * days, '|....:....'], 72 [20n * days, '|.:.'], 73 [50n * days, '|....'], 74 [100n * days, '|....:....'], 75 [200n * days, '|.:.'], 76 [500n * days, '|....'], 77 [1000n * days, '|....:....'], 78 [2000n * days, '|.:.'], 79 [5000n * days, '|....'], 80 [10000n * days, '|....:....'], 81 [20000n * days, '|.:.'], 82 [50000n * days, '|....'], 83 [100000n * days, '|....:....'], 84 [200000n * days, '|.:.'], 85]; 86 87// Returns the optimal step size and pattern of ticks within the step. 88export function getPattern(minPatternSize: bigint): [duration, string] { 89 for (const [size, pattern] of patterns) { 90 if (size >= minPatternSize) { 91 return [size, pattern]; 92 } 93 } 94 95 throw new Error('Pattern not defined for this minsize'); 96} 97 98function tickPatternToArray(pattern: string): TickType[] { 99 const array = Array.from(pattern); 100 return array.map((char) => { 101 switch (char) { 102 case '|': 103 return TickType.MAJOR; 104 case ':': 105 return TickType.MEDIUM; 106 case '.': 107 return TickType.MINOR; 108 default: 109 // This is almost certainly a developer/fat-finger error 110 throw Error(`Invalid char "${char}" in pattern "${pattern}"`); 111 } 112 }); 113} 114 115export enum TickType { 116 MAJOR, 117 MEDIUM, 118 MINOR, 119} 120 121export interface Tick { 122 type: TickType; 123 time: time; 124} 125 126export const MIN_PX_PER_STEP = 120; 127export function getMaxMajorTicks(width: number) { 128 return Math.max(1, Math.floor(width / MIN_PX_PER_STEP)); 129} 130 131export function* generateTicks( 132 timeSpan: TimeSpan, 133 maxMajorTicks: number, 134 offset: time = Time.ZERO, 135): Generator<Tick> { 136 assertTrue(timeSpan.duration > 0n, 'timeSpan.duration cannot be lte 0'); 137 assertTrue(maxMajorTicks > 0, 'maxMajorTicks cannot be lte 0'); 138 139 timeSpan = timeSpan.translate(-offset); 140 const minStepSize = BigInt( 141 Math.floor(Number(timeSpan.duration) / maxMajorTicks), 142 ); 143 const [patternSize, pattern] = getPattern(minStepSize); 144 const tickPattern = tickPatternToArray(pattern); 145 146 const stepSize = patternSize / BigInt(tickPattern.length); 147 const start = Time.quantFloor(timeSpan.start, patternSize); 148 const end = timeSpan.end; 149 let patternIndex = 0; 150 151 for ( 152 let time = start; 153 time < end; 154 time = Time.add(time, stepSize), patternIndex++ 155 ) { 156 if (time >= timeSpan.start) { 157 patternIndex = patternIndex % tickPattern.length; 158 const type = tickPattern[patternIndex]; 159 yield {type, time: Time.add(time, offset)}; 160 } 161 } 162} 163