Number of Grid Lines depends on window width

Change-Id: If164973ded74a214aff68224a9b59af5546e5339
diff --git a/ui/src/frontend/gridline_helper.ts b/ui/src/frontend/gridline_helper.ts
index 04a29da..51a1ae7 100644
--- a/ui/src/frontend/gridline_helper.ts
+++ b/ui/src/frontend/gridline_helper.ts
@@ -15,43 +15,74 @@
 import {Milliseconds, TimeScale} from './time_scale';
 import {VirtualCanvasContext} from './virtual_canvas_context';
 
-export abstract class GridlineHelper {
-  static drawGridLines(
-      ctx: VirtualCanvasContext, x: TimeScale,
-      timeBounds: [Milliseconds, Milliseconds], width: number,
-      height: number): void {
-    const step = GridlineHelper.getStepSize(timeBounds[1] - timeBounds[0]);
-    const start = Math.round(timeBounds[0] / step) * step;
+export const DESIRED_PX_PER_STEP = 80;
 
-    ctx.strokeStyle = '#999999';
-    ctx.lineWidth = 1;
+export function drawGridLines(
+    ctx: VirtualCanvasContext,
+    x: TimeScale,
+    timeBounds: [Milliseconds, Milliseconds],
+    width: number,
+    height: number): void {
+  const range = timeBounds[1] - timeBounds[0];
+  const desiredSteps = width / DESIRED_PX_PER_STEP;
+  const step = getGridStepSize(range, desiredSteps);
+  const start = Math.round(timeBounds[0] / step) * step;
 
-    for (let t: Milliseconds = start; t < timeBounds[1]; t += step) {
-      const xPos = Math.floor(x.msToPx(t)) + 0.5;
+  ctx.strokeStyle = '#999999';
+  ctx.lineWidth = 1;
 
-      if (xPos >= 0 && xPos <= width) {
-        ctx.beginPath();
-        ctx.moveTo(xPos, 0);
-        ctx.lineTo(xPos, height);
-        ctx.stroke();
-      }
+  for (let t: Milliseconds = start; t < timeBounds[1]; t += step) {
+    const xPos = Math.floor(x.msToPx(t)) + 0.5;
+
+    if (xPos >= 0 && xPos <= width) {
+      ctx.beginPath();
+      ctx.moveTo(xPos, 0);
+      ctx.lineTo(xPos, height);
+      ctx.stroke();
     }
   }
+}
 
-  /**
-   * Calculates a step size of gridlines for a given time range such that there
-   * are between 10 and 25 steps. The only possible step sizes are 2, 5, or 10,
-   * except for factors of 10 for scaling.
-   */
-  static getStepSize(range: Milliseconds): Milliseconds {
-    const zeros = Math.floor(Math.log10(range));
-    let step = Math.pow(10, zeros);
-    if (range / step < 5) {
-      step /= 5;
+/**
+ * Returns the step size of a grid line. 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: Milliseconds, desiredSteps: number): Milliseconds {
+  // 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;
     }
-    if (range / step < 10) {
-      step /= 2;
-    }
-    return step;
   }
+  return minimizingStepSize;
 }
\ No newline at end of file