Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 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 | |
Primiano Tucci | f30cd9c | 2018-08-13 01:53:26 +0200 | [diff] [blame] | 15 | import {TimeSpan} from '../common/time'; |
| 16 | |
| 17 | import {TimeScale} from './time_scale'; |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 18 | |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 19 | export const DESIRED_PX_PER_STEP = 80; |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 20 | |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 21 | export function drawGridLines( |
Deepanjan Roy | 4bc80b1 | 2018-08-02 10:26:45 -0400 | [diff] [blame] | 22 | ctx: CanvasRenderingContext2D, |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 23 | x: TimeScale, |
Primiano Tucci | f30cd9c | 2018-08-13 01:53:26 +0200 | [diff] [blame] | 24 | timeSpan: TimeSpan, |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 25 | height: number): void { |
Primiano Tucci | e36ca63 | 2018-08-21 14:32:23 +0200 | [diff] [blame] | 26 | const width = x.deltaTimeToPx(timeSpan.duration); |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 27 | const desiredSteps = width / DESIRED_PX_PER_STEP; |
Primiano Tucci | f30cd9c | 2018-08-13 01:53:26 +0200 | [diff] [blame] | 28 | const step = getGridStepSize(timeSpan.duration, desiredSteps); |
| 29 | const start = Math.round(timeSpan.start / step) * step; |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 30 | |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 31 | ctx.strokeStyle = '#999999'; |
| 32 | ctx.lineWidth = 1; |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 33 | |
Primiano Tucci | f30cd9c | 2018-08-13 01:53:26 +0200 | [diff] [blame] | 34 | for (let sec = start; sec < timeSpan.end; sec += step) { |
| 35 | const xPos = Math.floor(x.timeToPx(sec)) + 0.5; |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 36 | |
| 37 | if (xPos >= 0 && xPos <= width) { |
| 38 | ctx.beginPath(); |
| 39 | ctx.moveTo(xPos, 0); |
| 40 | ctx.lineTo(xPos, height); |
| 41 | ctx.stroke(); |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 42 | } |
| 43 | } |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 44 | } |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 45 | |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 46 | /** |
Primiano Tucci | f30cd9c | 2018-08-13 01:53:26 +0200 | [diff] [blame] | 47 | * Returns the step size of a grid line in seconds. |
| 48 | * The returned step size has two properties: |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 49 | * (1) It is 1, 2, or 5, multiplied by some integer power of 10. |
| 50 | * (2) The number steps in |range| produced by |stepSize| is as close as |
| 51 | * possible to |desiredSteps|. |
| 52 | */ |
Primiano Tucci | f30cd9c | 2018-08-13 01:53:26 +0200 | [diff] [blame] | 53 | export function getGridStepSize(range: number, desiredSteps: number): number { |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 54 | // First, get the largest possible power of 10 that is smaller than the |
| 55 | // desired step size, and set it to the current step size. |
| 56 | // For example, if the range is 2345ms and the desired steps is 10, then the |
| 57 | // desired step size is 234.5 and the step size will be set to 100. |
| 58 | const desiredStepSize = range / desiredSteps; |
| 59 | const zeros = Math.floor(Math.log10(desiredStepSize)); |
| 60 | const initialStepSize = Math.pow(10, zeros); |
| 61 | |
| 62 | // This function first calculates how many steps within the range a certain |
| 63 | // stepSize will produce, and returns the difference between that and |
| 64 | // desiredSteps. |
| 65 | const distToDesired = (evaluatedStepSize: number) => |
| 66 | Math.abs(range / evaluatedStepSize - desiredSteps); |
| 67 | |
| 68 | // We know that |initialStepSize| is a power of 10, and |
| 69 | // initialStepSize <= desiredStepSize <= 10 * initialStepSize. There are four |
| 70 | // possible candidates for final step size: 1, 2, 5 or 10 * initialStepSize. |
| 71 | // We pick the candidate that minimizes distToDesired(stepSize). |
| 72 | const stepSizeMultipliers = [2, 5, 10]; |
| 73 | |
| 74 | let minimalDistance = distToDesired(initialStepSize); |
| 75 | let minimizingStepSize = initialStepSize; |
| 76 | |
| 77 | for (const multiplier of stepSizeMultipliers) { |
| 78 | const newStepSize = multiplier * initialStepSize; |
| 79 | const newDistance = distToDesired(newStepSize); |
| 80 | if (newDistance < minimalDistance) { |
| 81 | minimalDistance = newDistance; |
| 82 | minimizingStepSize = newStepSize; |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 83 | } |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 84 | } |
Michail Schwab | 7e4b89e | 2018-07-27 10:48:40 -0400 | [diff] [blame] | 85 | return minimizingStepSize; |
Michail Schwab | a86aae4 | 2018-07-20 11:58:28 -0400 | [diff] [blame] | 86 | } |