Implement interface for using web plotter from C++

It's not actually usable yet due to ODR violations created by
abseil being compiled into libwebrtc_full.a, but it does work based
on testing I've done with using websockets for data transfer.

Change-Id: I574570c7b5c85df9e53321bfb971a608d20b9803
diff --git a/frc971/analysis/plot_data_utils.ts b/frc971/analysis/plot_data_utils.ts
new file mode 100644
index 0000000..8d42a4a
--- /dev/null
+++ b/frc971/analysis/plot_data_utils.ts
@@ -0,0 +1,95 @@
+// Provides a plot which handles plotting the plot defined by a
+// frc971.analysis.Plot message.
+import * as configuration from 'org_frc971/aos/configuration_generated';
+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 * as proxy from 'org_frc971/aos/network/www/proxy';
+
+import Connection = proxy.Connection;
+import Schema = configuration.reflection.Schema;
+import PlotFb = plot_data.frc971.analysis.Plot;
+
+export function plotData(conn: Connection, parentDiv: Element) {
+  // Set up a selection box to allow the user to choose between plots to show.
+  const plotSelect = document.createElement('select');
+  parentDiv.appendChild(plotSelect);
+  const plots = new Map<string, HTMLElement>();
+  const invalidSelectValue = 'null';
+  plotSelect.addEventListener('input', () => {
+    for (const plot of plots.values()) {
+      plot.style.display = 'none';
+    }
+    if (plotSelect.value == invalidSelectValue) {
+      return;
+    }
+    plots.get(plotSelect.value).style.display = 'block';
+  });
+  plotSelect.add(new Option('Select Plot', invalidSelectValue));
+
+  const plotDiv = document.createElement('div');
+  plotDiv.style.position = 'absolute';
+  plotDiv.style.top = '30';
+  plotDiv.style.left = '0';
+  parentDiv.appendChild(plotDiv);
+
+  conn.addReliableHandler(
+      '/analysis', 'frc971.analysis.Plot', (data: Uint8Array, time: number) => {
+        const plotFb = PlotFb.getRootAsPlot(
+            new ByteBuffer(data) as unknown as flatbuffers.ByteBuffer);
+        const name = (!plotFb.title()) ? 'Plot ' + plots.size : plotFb.title();
+        const div = document.createElement('div');
+        div.style.display = 'none';
+        plots.set(name, div);
+        plotDiv.appendChild(div);
+        plotSelect.add(new Option(name, name));
+
+        const linkedXAxes: Plot[] = [];
+
+        for (let ii = 0; ii < plotFb.figuresLength(); ++ii) {
+          const figure = plotFb.figures(ii);
+          const figureDiv = document.createElement('div');
+          figureDiv.style.top = figure.position().top().toString();
+          figureDiv.style.left = figure.position().left().toString();
+          figureDiv.style.position = 'absolute';
+          div.appendChild(figureDiv);
+          const plot = new Plot(
+              figureDiv, figure.position().width(), figure.position().height());
+
+          if (figure.title()) {
+            plot.getAxisLabels().setTitle(figure.title());
+          }
+          if (figure.xlabel()) {
+            plot.getAxisLabels().setXLabel(figure.xlabel());
+          }
+          if (figure.ylabel()) {
+            plot.getAxisLabels().setYLabel(figure.xlabel());
+          }
+          if (figure.shareXAxis()) {
+            for (const other of linkedXAxes) {
+              plot.linkXAxis(other);
+            }
+            linkedXAxes.push(plot);
+          }
+
+          for (let jj = 0; jj < figure.linesLength(); ++jj) {
+            const lineFb = figure.lines(jj);
+            const line = plot.getDrawer().addLine();
+            if (lineFb.label()) {
+              line.setLabel(lineFb.label());
+            }
+            const points = new Float32Array(lineFb.pointsLength() * 2);
+            for (let kk = 0; kk < lineFb.pointsLength(); ++kk) {
+              points[kk * 2] = lineFb.points(kk).x();
+              points[kk * 2 + 1] = lineFb.points(kk).y();
+            }
+            if (lineFb.color()) {
+              line.setColor(
+                  [lineFb.color().r(), lineFb.color().g(), lineFb.color().b()]);
+            }
+            line.setPoints(points);
+          }
+        }
+      });
+}