Add support for plotting arbitrary flatbuffer vectors

Adds support for both:
-Plotting every element in a vector.
-Plotting repeated sub-messages.

Change-Id: I52de3dbcf7bcf345a7a21d9de6d04692926a9590
diff --git a/aos/network/www/aos_plotter.ts b/aos/network/www/aos_plotter.ts
index b84bfc4..35c27a7 100644
--- a/aos/network/www/aos_plotter.ts
+++ b/aos/network/www/aos_plotter.ts
@@ -60,6 +60,32 @@
     this.messages.push(
         new TimestampedMessage(Table.getRootTable(new ByteBuffer(data)), time));
   }
+  private readField<T>(
+      message: Table, fieldName: string,
+      normalReader: (message: Table, name: string) => T | null,
+      vectorReader: (message: Table, name: string) => T[] | null): T[]|null {
+    // Typescript handles bindings in non-obvious ways that aren't caught well
+    // by the compiler.
+    normalReader = normalReader.bind(this.parser);
+    vectorReader = vectorReader.bind(this.parser);
+    const regex = /(.*)\[([0-9]*)\]/;
+    const match = fieldName.match(regex);
+    if (match) {
+      const name = match[1];
+      const vector = vectorReader(message, name);
+      if (vector === null) {
+        return null;
+      }
+      if (match[2] === "") {
+        return vector;
+      } else {
+        const index = parseInt(match[2]);
+        return (index < vector.length) ? [vector[index]] : null;
+      }
+    }
+    const singleResult = normalReader(message, fieldName);
+    return singleResult ? [singleResult] : null;
+  }
   // Returns a time-series of every single instance of the given field. Format
   // of the return value is [time0, value0, time1, value1,... timeN, valueN],
   // to match with the Line.setPoint() interface.
@@ -70,41 +96,44 @@
   getField(field: string[]): Float32Array {
     const fieldName = field[field.length - 1];
     const subMessage = field.slice(0, field.length - 1);
-    const results = new Float32Array(this.messages.length * 2);
+    const results = [];
     for (let ii = 0; ii < this.messages.length; ++ii) {
-      let message = this.messages[ii].message;
+      let tables = [this.messages[ii].message];
       for (const subMessageName of subMessage) {
-        message = this.parser.readTable(message, subMessageName);
-        if (message === undefined) {
-          break;
+        let nextTables = [];
+        for (const table of tables) {
+          const nextTable = this.readField(
+              table, subMessageName, Parser.prototype.readTable,
+              Parser.prototype.readVectorOfTables);
+          if (nextTable === null) {
+            continue;
+          }
+          nextTables = nextTables.concat(nextTable);
         }
+        tables = nextTables;
       }
-      results[ii * 2] = this.messages[ii].time;
-      const regex = /(.*)\[([0-9]*)\]/;
-      let name = fieldName;
-      let match = fieldName.match(regex);
-      let index = undefined;
-      if (match) {
-        name = match[1]
-        index = parseInt(match[2])
-      }
-      if (message === undefined) {
-        results[ii * 2 + 1] = NaN;
+      const time = this.messages[ii].time;
+      if (tables.length === 0) {
+        results.push(time);
+        results.push(NaN);
       } else {
-        if (index === undefined) {
-          results[ii * 2 + 1] = this.parser.readScalar(message, name);
-        } else {
-          const vector =
-              this.parser.readVectorOfScalars(message, name);
-          if (index < vector.length) {
-            results[ii * 2 + 1] = vector[index];
+        for (const table of tables) {
+          const values = this.readField(
+              table, fieldName, Parser.prototype.readScalar,
+              Parser.prototype.readVectorOfScalars);
+          if (values === null) {
+            results.push(time);
+            results.push(NaN);
           } else {
-            results[ii * 2 + 1] = NaN;
+            for (const value of values) {
+              results.push(time);
+              results.push((value === null) ? NaN : value);
+            }
           }
         }
       }
     }
-    return results;
+    return new Float32Array(results);
   }
   numMessages(): number {
     return this.messages.length;