Home | History | Annotate | Download | only in frontend
      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 
     15 import {TimeSpan} from '../common/time';
     16 
     17 import {TimeScale} from './time_scale';
     18 import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './track_constants';
     19 
     20 export 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  */
     29 export 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  */
     69 export 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 start = Math.round(span.start / step) * step;
     74   const lines: Array<[number, number]> = [];
     75   for (let s = start; s < span.end; s += step) {
     76     let xPos = TRACK_SHELL_WIDTH;
     77     xPos += Math.floor(timescale.timeToPx(s));
     78     if (xPos < TRACK_SHELL_WIDTH) continue;
     79     if (xPos > width) break;
     80     lines.push([xPos, s]);
     81   }
     82   return lines;
     83 }
     84 
     85 export function drawGridLines(
     86     ctx: CanvasRenderingContext2D,
     87     x: TimeScale,
     88     timeSpan: TimeSpan,
     89     width: number,
     90     height: number): void {
     91   ctx.strokeStyle = TRACK_BORDER_COLOR;
     92   ctx.lineWidth = 1;
     93 
     94   for (const xAndTime of gridlines(width, timeSpan, x)) {
     95     ctx.beginPath();
     96     ctx.moveTo(xAndTime[0] + 0.5, 0);
     97     ctx.lineTo(xAndTime[0] + 0.5, height);
     98     ctx.stroke();
     99   }
    100 }
    101