|  | // Copyright (C) 2018 The Android Open Source Project | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | import {TimeSpan} from '../common/time'; | 
|  |  | 
|  | import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants'; | 
|  | import {TimeScale} from './time_scale'; | 
|  |  | 
|  | export const DESIRED_PX_PER_STEP = 80; | 
|  |  | 
|  | /** | 
|  | * Returns the step size of a grid line in seconds. | 
|  | * The returned step size has two properties: | 
|  | * (1) It is 1, 2, or 5, multiplied by some integer power of 10. | 
|  | * (2) The number steps in |range| produced by |stepSize| is as close as | 
|  | *     possible to |desiredSteps|. | 
|  | */ | 
|  | export function getGridStepSize(range: number, desiredSteps: number): number { | 
|  | // First, get the largest possible power of 10 that is smaller than the | 
|  | // desired step size, and set it to the current step size. | 
|  | // For example, if the range is 2345ms and the desired steps is 10, then the | 
|  | // desired step size is 234.5 and the step size will be set to 100. | 
|  | const desiredStepSize = range / desiredSteps; | 
|  | const zeros = Math.floor(Math.log10(desiredStepSize)); | 
|  | const initialStepSize = Math.pow(10, zeros); | 
|  |  | 
|  | // This function first calculates how many steps within the range a certain | 
|  | // stepSize will produce, and returns the difference between that and | 
|  | // desiredSteps. | 
|  | const distToDesired = (evaluatedStepSize: number) => | 
|  | Math.abs(range / evaluatedStepSize - desiredSteps); | 
|  |  | 
|  | // We know that |initialStepSize| is a power of 10, and | 
|  | // initialStepSize <= desiredStepSize <= 10 * initialStepSize. There are four | 
|  | // possible candidates for final step size: 1, 2, 5 or 10 * initialStepSize. | 
|  | // We pick the candidate that minimizes distToDesired(stepSize). | 
|  | const stepSizeMultipliers = [2, 5, 10]; | 
|  |  | 
|  | let minimalDistance = distToDesired(initialStepSize); | 
|  | let minimizingStepSize = initialStepSize; | 
|  |  | 
|  | for (const multiplier of stepSizeMultipliers) { | 
|  | const newStepSize = multiplier * initialStepSize; | 
|  | const newDistance = distToDesired(newStepSize); | 
|  | if (newDistance < minimalDistance) { | 
|  | minimalDistance = newDistance; | 
|  | minimizingStepSize = newStepSize; | 
|  | } | 
|  | } | 
|  | return minimizingStepSize; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Generator that returns that (given a width im px, span, and scale) returns | 
|  | * pairs of [xInPx, timestampInS] pairs describing where gridlines should be | 
|  | * drawn. | 
|  | */ | 
|  | export function gridlines(width: number, span: TimeSpan, timescale: TimeScale): | 
|  | Array<[number, number]> { | 
|  | const desiredSteps = width / DESIRED_PX_PER_STEP; | 
|  | const step = getGridStepSize(span.duration, desiredSteps); | 
|  | const actualSteps = Math.floor(span.duration / step); | 
|  | const start = Math.round(span.start / step) * step; | 
|  | const lines: Array<[number, number]> = []; | 
|  | let previousTimestamp = Number.NEGATIVE_INFINITY; | 
|  | // Iterating over the number of steps instead of | 
|  | // for (let s = start; s < span.end; s += step) because if start is very large | 
|  | // number and step very small, s will never reach end. | 
|  | for (let i = 0; i <= actualSteps; i++) { | 
|  | let xPos = TRACK_SHELL_WIDTH; | 
|  | const timestamp = start + i * step; | 
|  | xPos += Math.floor(timescale.timeToPx(timestamp)); | 
|  | if (xPos < TRACK_SHELL_WIDTH) continue; | 
|  | if (xPos > width) break; | 
|  | if (Math.abs(timestamp - previousTimestamp) > Number.EPSILON) { | 
|  | previousTimestamp = timestamp; | 
|  | lines.push([xPos, timestamp]); | 
|  | } | 
|  | } | 
|  | return lines; | 
|  | } | 
|  |  | 
|  | export function drawGridLines( | 
|  | ctx: CanvasRenderingContext2D, | 
|  | x: TimeScale, | 
|  | timeSpan: TimeSpan, | 
|  | width: number, | 
|  | height: number): void { | 
|  | ctx.strokeStyle = TRACK_BORDER_COLOR; | 
|  | ctx.lineWidth = 1; | 
|  |  | 
|  | for (const xAndTime of gridlines(width, timeSpan, x)) { | 
|  | ctx.beginPath(); | 
|  | ctx.moveTo(xAndTime[0] + 0.5, 0); | 
|  | ctx.lineTo(xAndTime[0] + 0.5, height); | 
|  | ctx.stroke(); | 
|  | } | 
|  | } |