blob: 4bf6c5a8d61f3df16b5f74ec43ba1a0784cb52e6 [file] [log] [blame]
James Kuszmaul5f5e1232020-12-22 20:58:00 -08001// This file provides an index of all standard plot configs.
2// In the future, this may be split into more pieces (e.g., to have
3// year-specific indices). For now, the pattern for creating a new plot
4// is that you provide an exported function (a la the plot*() functions imported
5// below) which, when called, will generate the plot in the provided div.
6// This file handles providing a master list of all known plots so that
7// the user can just open a single web-page and select the plot that they want
8// from a drop-down. A given plot will not be loaded until it has been selected
9// once, at which point it will stay loaded. This means that if the user wants
10// to switch between several plot configs, they don't incur any performance
11// penalty associated with swapping.
12// By default, no plot is selected, but the plot= URL parameter may be used
13// to specify a specific plot, so that people can create links to a specific
14// plot.
15// The plot*() functions are called *after* we have already received a valid
16// config from the web server, so config handlers do not need to be used.
17//
18// The exact setup of this will be in flux as we add more configs and figure out
19// what setups work best--we will likely end up with separate index files for
20// each robot year, and may even end up allowing plots to be specified solely
21// using JSON rather than requiring people to write a script just to create
22// a plot.
23import * as configuration from 'org_frc971/aos/configuration_generated';
24import * as proxy from 'org_frc971/aos/network/www/proxy';
25import {plotImu} from 'org_frc971/frc971/wpilib/imu_plotter';
James Kuszmaulc4ae11c2020-12-26 16:26:58 -080026import {plotDrivetrain} from 'org_frc971/frc971/control_loops/drivetrain/drivetrain_plotter';
James Kuszmaulac2b6b42021-03-07 22:38:06 -080027import {plotDownEstimator} from 'org_frc971/frc971/control_loops/drivetrain/down_estimator_plotter';
milind upadhyay9bd381d2021-01-23 13:44:13 -080028import {plotRobotState} from
29 'org_frc971/frc971/control_loops/drivetrain/robot_state_plotter'
Austin Schuh7d63eab2021-03-06 20:15:02 -080030import {plotFinisher} from
31 'org_frc971/y2020/control_loops/superstructure/finisher_plotter'
32import {plotAccelerator} from
33 'org_frc971/y2020/control_loops/superstructure/accelerator_plotter'
Austin Schuh2efe1682021-03-06 22:47:15 -080034import {plotHood} from
35 'org_frc971/y2020/control_loops/superstructure/hood_plotter'
James Kuszmaul5f5e1232020-12-22 20:58:00 -080036import {plotDemo} from 'org_frc971/aos/network/www/demo_plot';
James Kuszmaul48671362020-12-24 13:54:16 -080037import {plotData} from 'org_frc971/frc971/analysis/plot_data_utils';
James Kuszmaul5f5e1232020-12-22 20:58:00 -080038
39import Connection = proxy.Connection;
40import Configuration = configuration.aos.Configuration;
41
42const rootDiv = document.createElement('div');
43document.body.appendChild(rootDiv);
44
45const helpDiv = document.createElement('div');
46rootDiv.appendChild(helpDiv);
47helpDiv.innerHTML =
James Kuszmaul461b0682020-12-22 22:20:21 -080048 'Help: click + drag to pan, double click to reset, scroll to zoom, ' +
49 'right-click + drag to zoom to rectangle, Esc to cancel. ' +
James Kuszmaul5f5e1232020-12-22 20:58:00 -080050 'Hold the x/y keys to only pan/zoom along the x/y axes.';
51
52class PlotState {
53 public readonly div: HTMLElement;
54 private initialized = false;
55 constructor(
56 parentDiv: HTMLElement,
57 private readonly initializer:
58 (conn: Connection, element: Element) => void) {
59 this.div = document.createElement('div');
60 parentDiv.appendChild(this.div);
61 this.hide();
62 }
63 initialize(conn: Connection): void {
64 if (!this.initialized) {
65 this.initializer(conn, this.div);
66 this.initialized = true;
67 }
68 }
69 hide(): void {
70 this.div.style.display = "none";
71 }
72 show(): void {
73 this.div.style.display = "block";
74 }
75}
76
77const plotSelect = document.createElement('select');
78rootDiv.appendChild(plotSelect);
79
80const plotDiv = document.createElement('div');
81plotDiv.style.top = (plotSelect.getBoundingClientRect().bottom + 10).toString();
82plotDiv.style.left = '0';
83plotDiv.style.position = 'absolute';
84rootDiv.appendChild(plotDiv);
85
86// The master list of all the plots that we provide. For a given config, it
87// is possible that not all of these plots will be usable depending on the
88// presence of certain channels.
89const plotIndex = new Map<string, PlotState>([
90 ['Demo', new PlotState(plotDiv, plotDemo)],
James Kuszmaul48671362020-12-24 13:54:16 -080091 ['IMU', new PlotState(plotDiv, plotImu)],
James Kuszmaulc4ae11c2020-12-26 16:26:58 -080092 ['Drivetrain', new PlotState(plotDiv, plotDrivetrain)],
James Kuszmaulac2b6b42021-03-07 22:38:06 -080093 ['Down Estimator', new PlotState(plotDiv, plotDownEstimator)],
milind upadhyay9bd381d2021-01-23 13:44:13 -080094 ['Robot State', new PlotState(plotDiv, plotRobotState)],
Austin Schuh7d63eab2021-03-06 20:15:02 -080095 ['Finisher', new PlotState(plotDiv, plotFinisher)],
96 ['Accelerator', new PlotState(plotDiv, plotAccelerator)],
Austin Schuh2efe1682021-03-06 22:47:15 -080097 ['Hood', new PlotState(plotDiv, plotHood)],
James Kuszmaul48671362020-12-24 13:54:16 -080098 ['C++ Plotter', new PlotState(plotDiv, plotData)],
James Kuszmaul5f5e1232020-12-22 20:58:00 -080099]);
100
101const invalidSelectValue = 'null';
102function getDefaultPlot(): string {
103 const urlParams = (new URL(document.URL)).searchParams;
104 const urlParamKey = 'plot';
105 if (!urlParams.has(urlParamKey)) {
106 return invalidSelectValue;
107 }
108 const desiredPlot = urlParams.get(urlParamKey);
109 if (!plotIndex.has(desiredPlot)) {
110 return invalidSelectValue;
111 }
112 return desiredPlot;
113}
114
115const conn = new Connection();
116
James Kuszmaulb8989f72021-03-07 20:00:02 -0800117let reloadOnChange = false;
118
James Kuszmaul5f5e1232020-12-22 20:58:00 -0800119conn.connect();
120
121conn.addConfigHandler((config: Configuration) => {
122 plotSelect.add(new Option("Select Plot", invalidSelectValue));
123 for (const name of plotIndex.keys()) {
124 plotSelect.add(new Option(name, name));
125 }
126 plotSelect.addEventListener('input', () => {
127 for (const plot of plotIndex.values()) {
128 plot.hide();
129 }
130 if (plotSelect.value == invalidSelectValue) {
131 return;
132 }
133 plotIndex.get(plotSelect.value).initialize(conn);
134 plotIndex.get(plotSelect.value).show();
James Kuszmaulc4ae11c2020-12-26 16:26:58 -0800135 // Set the URL so that if you reload you get back to this plot.
136 window.history.replaceState(
137 null, null, '?plot=' + encodeURIComponent(plotSelect.value));
James Kuszmaulb8989f72021-03-07 20:00:02 -0800138 if (reloadOnChange) {
139 window.location.reload();
140 }
141 reloadOnChange = true;
James Kuszmaul5f5e1232020-12-22 20:58:00 -0800142 });
143 plotSelect.value = getDefaultPlot();
144 // Force the event to occur once at the start.
145 plotSelect.dispatchEvent(new Event('input'));
146});