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