• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 size 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 m from 'mithril';
16import {canvasClip} from '../../base/canvas_utils';
17import {Size2D} from '../../base/geom';
18import {assertUnreachable} from '../../base/logging';
19import {Time, time, toISODateOnly} from '../../base/time';
20import {TimeScale} from '../../base/time_scale';
21import {timestampFormat} from '../../core/timestamp_format';
22import {TimestampFormat} from '../../public/timeline';
23import {Trace} from '../../public/trace';
24import {TRACK_SHELL_WIDTH} from '../css_constants';
25import {
26  generateTicks,
27  getMaxMajorTicks,
28  MIN_PX_PER_STEP,
29  TickType,
30} from './gridline_helper';
31
32export class TimeAxisPanel {
33  readonly id = 'time-axis-panel';
34  readonly height = 22;
35
36  constructor(private readonly trace: Trace) {}
37
38  render(): m.Children {
39    return m('', {style: {height: `${this.height}px`}});
40  }
41
42  renderCanvas(ctx: CanvasRenderingContext2D, size: Size2D) {
43    ctx.fillStyle = '#999';
44    ctx.textAlign = 'left';
45    ctx.font = '11px Roboto Condensed';
46
47    this.renderOffsetTimestamp(ctx);
48
49    const trackSize = {...size, width: size.width - TRACK_SHELL_WIDTH};
50    ctx.save();
51    ctx.translate(TRACK_SHELL_WIDTH, 0);
52    canvasClip(ctx, 0, 0, trackSize.width, trackSize.height);
53    this.renderPanel(ctx, trackSize);
54    ctx.restore();
55
56    ctx.fillRect(TRACK_SHELL_WIDTH - 1, 0, 1, size.height);
57  }
58
59  private renderOffsetTimestamp(ctx: CanvasRenderingContext2D): void {
60    const offset = this.trace.timeline.timestampOffset();
61    const timestampFormat = this.trace.timeline.timestampFormat;
62    switch (timestampFormat) {
63      case TimestampFormat.TraceNs:
64      case TimestampFormat.TraceNsLocale:
65        break;
66      case TimestampFormat.Seconds:
67      case TimestampFormat.Milliseconds:
68      case TimestampFormat.Microseconds:
69      case TimestampFormat.Timecode:
70        const width = renderTimestamp(ctx, offset, 6, 10, MIN_PX_PER_STEP);
71        ctx.fillText('+', 6 + width + 2, 10, 6);
72        break;
73      case TimestampFormat.UTC:
74        const offsetDate = Time.toDate(
75          this.trace.traceInfo.utcOffset,
76          this.trace.traceInfo.realtimeOffset,
77        );
78        const dateStr = toISODateOnly(offsetDate);
79        ctx.fillText(`UTC ${dateStr}`, 6, 10);
80        break;
81      case TimestampFormat.TraceTz:
82        const offsetTzDate = Time.toDate(
83          this.trace.traceInfo.traceTzOffset,
84          this.trace.traceInfo.realtimeOffset,
85        );
86        const dateTzStr = toISODateOnly(offsetTzDate);
87        ctx.fillText(dateTzStr, 6, 10);
88        break;
89      default:
90        assertUnreachable(timestampFormat);
91    }
92  }
93
94  private renderPanel(ctx: CanvasRenderingContext2D, size: Size2D): void {
95    const visibleWindow = this.trace.timeline.visibleWindow;
96    const timescale = new TimeScale(visibleWindow, {
97      left: 0,
98      right: size.width,
99    });
100    const timespan = visibleWindow.toTimeSpan();
101    const offset = this.trace.timeline.timestampOffset();
102
103    // Draw time axis.
104    if (size.width > 0 && timespan.duration > 0n) {
105      const maxMajorTicks = getMaxMajorTicks(size.width);
106      const tickGen = generateTicks(timespan, maxMajorTicks, offset);
107      for (const {type, time} of tickGen) {
108        if (type === TickType.MAJOR) {
109          const position = Math.floor(timescale.timeToPx(time));
110          ctx.fillRect(position, 0, 1, size.height);
111          const domainTime = this.trace.timeline.toDomainTime(time);
112          renderTimestamp(ctx, domainTime, position + 5, 10, MIN_PX_PER_STEP);
113        }
114      }
115    }
116  }
117}
118
119function renderTimestamp(
120  ctx: CanvasRenderingContext2D,
121  time: time,
122  x: number,
123  y: number,
124  minWidth: number,
125) {
126  const fmt = timestampFormat();
127  switch (fmt) {
128    case TimestampFormat.UTC:
129    case TimestampFormat.TraceTz:
130    case TimestampFormat.Timecode:
131      return renderTimecode(ctx, time, x, y, minWidth);
132    case TimestampFormat.TraceNs:
133      return renderRawTimestamp(ctx, time.toString(), x, y, minWidth);
134    case TimestampFormat.TraceNsLocale:
135      return renderRawTimestamp(ctx, time.toLocaleString(), x, y, minWidth);
136    case TimestampFormat.Seconds:
137      return renderRawTimestamp(ctx, Time.formatSeconds(time), x, y, minWidth);
138    case TimestampFormat.Milliseconds:
139      return renderRawTimestamp(
140        ctx,
141        Time.formatMilliseconds(time),
142        x,
143        y,
144        minWidth,
145      );
146    case TimestampFormat.Microseconds:
147      return renderRawTimestamp(
148        ctx,
149        Time.formatMicroseconds(time),
150        x,
151        y,
152        minWidth,
153      );
154    default:
155      const z: never = fmt;
156      throw new Error(`Invalid timestamp ${z}`);
157  }
158}
159
160// Print a time on the canvas in raw format.
161function renderRawTimestamp(
162  ctx: CanvasRenderingContext2D,
163  time: string,
164  x: number,
165  y: number,
166  minWidth: number,
167) {
168  ctx.font = '11px Roboto Condensed';
169  ctx.fillText(time, x, y, minWidth);
170  return ctx.measureText(time).width;
171}
172
173// Print a timecode over 2 lines with this formatting:
174// DdHH:MM:SS
175// mmm uuu nnn
176// Returns the resultant width of the timecode.
177function renderTimecode(
178  ctx: CanvasRenderingContext2D,
179  time: time,
180  x: number,
181  y: number,
182  minWidth: number,
183): number {
184  const timecode = Time.toTimecode(time);
185  ctx.font = '11px Roboto Condensed';
186
187  const {dhhmmss} = timecode;
188  const thinSpace = '\u2009';
189  const subsec = timecode.subsec(thinSpace);
190  ctx.fillText(dhhmmss, x, y, minWidth);
191  const {width: firstRowWidth} = ctx.measureText(subsec);
192
193  ctx.font = '10.5px Roboto Condensed';
194  ctx.fillText(subsec, x, y + 10, minWidth);
195  const {width: secondRowWidth} = ctx.measureText(subsec);
196
197  return Math.max(firstRowWidth, secondRowWidth);
198}
199