Better handle high-precision numbers in plotter

WebGL has relatively low floating point precision, and so attempting to
closely examine any signal which had even modest levels of precision
(e.g., attempting to plot a 32-bit integer) is an issue.

Add logic to pre-scale all of our points and readjust them periodically
while zooming to avoid precision issues.

Change-Id: Ibd51310fc2004a30c142bd5923a57961b6a20036
diff --git a/frc971/analysis/plot_data_utils.ts b/frc971/analysis/plot_data_utils.ts
index 8d42a4a..e918379 100644
--- a/frc971/analysis/plot_data_utils.ts
+++ b/frc971/analysis/plot_data_utils.ts
@@ -4,7 +4,7 @@
 import * as plot_data from 'org_frc971/frc971/analysis/plot_data_generated';
 import {MessageHandler, TimestampedMessage} from 'org_frc971/aos/network/www/aos_plotter';
 import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
-import {Plot} from 'org_frc971/aos/network/www/plotter';
+import {Plot, Point} from 'org_frc971/aos/network/www/plotter';
 import * as proxy from 'org_frc971/aos/network/www/proxy';
 
 import Connection = proxy.Connection;
@@ -79,10 +79,10 @@
             if (lineFb.label()) {
               line.setLabel(lineFb.label());
             }
-            const points = new Float32Array(lineFb.pointsLength() * 2);
+            const points = [];
             for (let kk = 0; kk < lineFb.pointsLength(); ++kk) {
-              points[kk * 2] = lineFb.points(kk).x();
-              points[kk * 2 + 1] = lineFb.points(kk).y();
+              const point = lineFb.points(kk);
+              points.push(new Point(point.x(), point.y()));
             }
             if (lineFb.color()) {
               line.setColor(
diff --git a/frc971/wpilib/BUILD b/frc971/wpilib/BUILD
index 087c413..00896bb 100644
--- a/frc971/wpilib/BUILD
+++ b/frc971/wpilib/BUILD
@@ -441,6 +441,7 @@
         ":imu_batch_ts_fbs",
         "//aos:configuration_ts_fbs",
         "//aos/network/www:aos_plotter",
+        "//aos/network/www:plotter",
         "//aos/network/www:reflection_ts",
         "@com_github_google_flatbuffers//ts:flatbuffers_ts",
     ],
diff --git a/frc971/wpilib/imu_plot_utils.ts b/frc971/wpilib/imu_plot_utils.ts
index 8193c29..ee1a190 100644
--- a/frc971/wpilib/imu_plot_utils.ts
+++ b/frc971/wpilib/imu_plot_utils.ts
@@ -3,6 +3,7 @@
 import * as configuration from 'org_frc971/aos/configuration_generated';
 import * as imu from 'org_frc971/frc971/wpilib/imu_batch_generated';
 import {MessageHandler, TimestampedMessage} from 'org_frc971/aos/network/www/aos_plotter';
+import {Point} from 'org_frc971/aos/network/www/plotter';
 import {Table} from 'org_frc971/aos/network/www/reflection';
 import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
 
@@ -14,7 +15,7 @@
 
 export class ImuMessageHandler extends MessageHandler {
   // Calculated magnitude of the measured acceleration from the IMU.
-  private acceleration_magnitudes: number[] = [];
+  private acceleration_magnitudes: Point[] = [];
   constructor(private readonly schema: Schema) {
     super(schema);
   }
@@ -36,37 +37,38 @@
       }
       const time = message.monotonicTimestampNs().toFloat64() * 1e-9;
       this.messages.push(new TimestampedMessage(table, time));
-      this.acceleration_magnitudes.push(time);
-      this.acceleration_magnitudes.push(Math.hypot(
-          message.accelerometerX(), message.accelerometerY(),
-          message.accelerometerZ()));
+      this.acceleration_magnitudes.push(new Point(
+          time,
+          Math.hypot(
+              message.accelerometerX(), message.accelerometerY(),
+              message.accelerometerZ())));
     }
   }
 
   // Computes a moving average for a given input, using a basic window centered
   // on each value.
-  private movingAverageCentered(input: Float32Array): Float32Array {
-    const num_measurements = input.length / 2;
-    const filtered_measurements = new Float32Array(input);
+  private movingAverageCentered(input: Point[]): Point[] {
+    const num_measurements = input.length;
+    const filtered_measurements = [];
     for (let ii = 0; ii < num_measurements; ++ii) {
       let sum = 0;
       let count = 0;
       for (let jj = Math.max(0, Math.ceil(ii - FILTER_WINDOW_SIZE / 2));
            jj < Math.min(num_measurements, ii + FILTER_WINDOW_SIZE / 2); ++jj) {
-        sum += input[jj * 2 + 1];
+        sum += input[jj].y;
         ++count;
       }
-      filtered_measurements[ii * 2 + 1] = sum / count;
+      filtered_measurements.push(new Point(input[ii].x, sum / count));
     }
-    return new Float32Array(filtered_measurements);
+    return filtered_measurements;
   }
 
-  getField(field: string[]): Float32Array {
+  getField(field: string[]): Point[] {
     // Any requested input that ends with "_filtered" will get a moving average
     // applied to the original field.
     const filtered_suffix = "_filtered";
     if (field[0] == "acceleration_magnitude") {
-      return new Float32Array(this.acceleration_magnitudes);
+      return this.acceleration_magnitudes;
     } else if (field[0].endsWith(filtered_suffix)) {
       return this.movingAverageCentered(this.getField(
           [field[0].slice(0, field[0].length - filtered_suffix.length)]));