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