xref: /aosp_15_r20/external/perfetto/ui/src/frontend/gridline_helper.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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