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 {TimeSpan} from '../common/time'; 16 17import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants'; 18import {TimeScale} from './time_scale'; 19 20export const DESIRED_PX_PER_STEP = 80; 21 22/** 23 * Returns the step size of a grid line in seconds. 24 * The returned step size has two properties: 25 * (1) It is 1, 2, or 5, multiplied by some integer power of 10. 26 * (2) The number steps in |range| produced by |stepSize| is as close as 27 * possible to |desiredSteps|. 28 */ 29export function getGridStepSize(range: number, desiredSteps: number): number { 30 // First, get the largest possible power of 10 that is smaller than the 31 // desired step size, and set it to the current step size. 32 // For example, if the range is 2345ms and the desired steps is 10, then the 33 // desired step size is 234.5 and the step size will be set to 100. 34 const desiredStepSize = range / desiredSteps; 35 const zeros = Math.floor(Math.log10(desiredStepSize)); 36 const initialStepSize = Math.pow(10, zeros); 37 38 // This function first calculates how many steps within the range a certain 39 // stepSize will produce, and returns the difference between that and 40 // desiredSteps. 41 const distToDesired = (evaluatedStepSize: number) => 42 Math.abs(range / evaluatedStepSize - desiredSteps); 43 44 // We know that |initialStepSize| is a power of 10, and 45 // initialStepSize <= desiredStepSize <= 10 * initialStepSize. There are four 46 // possible candidates for final step size: 1, 2, 5 or 10 * initialStepSize. 47 // We pick the candidate that minimizes distToDesired(stepSize). 48 const stepSizeMultipliers = [2, 5, 10]; 49 50 let minimalDistance = distToDesired(initialStepSize); 51 let minimizingStepSize = initialStepSize; 52 53 for (const multiplier of stepSizeMultipliers) { 54 const newStepSize = multiplier * initialStepSize; 55 const newDistance = distToDesired(newStepSize); 56 if (newDistance < minimalDistance) { 57 minimalDistance = newDistance; 58 minimizingStepSize = newStepSize; 59 } 60 } 61 return minimizingStepSize; 62} 63 64/** 65 * Generator that returns that (given a width im px, span, and scale) returns 66 * pairs of [xInPx, timestampInS] pairs describing where gridlines should be 67 * drawn. 68 */ 69export function gridlines(width: number, span: TimeSpan, timescale: TimeScale): 70 Array<[number, number]> { 71 const desiredSteps = width / DESIRED_PX_PER_STEP; 72 const step = getGridStepSize(span.duration, desiredSteps); 73 const actualSteps = Math.floor(span.duration / step); 74 const start = Math.round(span.start / step) * step; 75 const lines: Array<[number, number]> = []; 76 let previousTimestamp = Number.NEGATIVE_INFINITY; 77 // Iterating over the number of steps instead of 78 // for (let s = start; s < span.end; s += step) because if start is very large 79 // number and step very small, s will never reach end. 80 for (let i = 0; i <= actualSteps; i++) { 81 let xPos = TRACK_SHELL_WIDTH; 82 const timestamp = start + i * step; 83 xPos += Math.floor(timescale.timeToPx(timestamp)); 84 if (xPos < TRACK_SHELL_WIDTH) continue; 85 if (xPos > width) break; 86 if (Math.abs(timestamp - previousTimestamp) > Number.EPSILON) { 87 previousTimestamp = timestamp; 88 lines.push([xPos, timestamp]); 89 } 90 } 91 return lines; 92} 93 94export function drawGridLines( 95 ctx: CanvasRenderingContext2D, 96 x: TimeScale, 97 timeSpan: TimeSpan, 98 width: number, 99 height: number): void { 100 ctx.strokeStyle = TRACK_BORDER_COLOR; 101 ctx.lineWidth = 1; 102 103 for (const xAndTime of gridlines(width, timeSpan, x)) { 104 ctx.beginPath(); 105 ctx.moveTo(xAndTime[0] + 0.5, 0); 106 ctx.lineTo(xAndTime[0] + 0.5, height); 107 ctx.stroke(); 108 } 109} 110