| // This file provides an index of all standard plot configs. |
| // In the future, this may be split into more pieces (e.g., to have |
| // year-specific indices). For now, the pattern for creating a new plot |
| // is that you provide an exported function (a la the plot*() functions imported |
| // below) which, when called, will generate the plot in the provided div. |
| // This file handles providing a master list of all known plots so that |
| // the user can just open a single web-page and select the plot that they want |
| // from a drop-down. A given plot will not be loaded until it has been selected |
| // once, at which point it will stay loaded. This means that if the user wants |
| // to switch between several plot configs, they don't incur any performance |
| // penalty associated with swapping. |
| // By default, no plot is selected, but the plot= URL parameter may be used |
| // to specify a specific plot, so that people can create links to a specific |
| // plot. |
| // The plot*() functions are called *after* we have already received a valid |
| // config from the web server, so config handlers do not need to be used. |
| // |
| // The exact setup of this will be in flux as we add more configs and figure out |
| // what setups work best--we will likely end up with separate index files for |
| // each robot year, and may even end up allowing plots to be specified solely |
| // using JSON rather than requiring people to write a script just to create |
| // a plot. |
| import {Configuration} from '../../aos/configuration_generated'; |
| import {plotDemo} from '../../aos/network/www/demo_plot'; |
| import {Connection} from '../../aos/network/www/proxy'; |
| import {plotLocalizer as plot2020Localizer} from '../../y2020/control_loops/drivetrain/localizer_plotter' |
| import {plotAccelerator as plot2020Accelerator} from '../../y2020/control_loops/superstructure/accelerator_plotter' |
| import {plotFinisher as plot2020Finisher} from '../../y2020/control_loops/superstructure/finisher_plotter' |
| import {plotHood as plot2020Hood} from '../../y2020/control_loops/superstructure/hood_plotter' |
| import {plotTurret as plot2020Turret} from '../../y2020/control_loops/superstructure/turret_plotter' |
| import {plotSuperstructure as plot2021Superstructure} from '../../y2021_bot3/control_loops/superstructure/superstructure_plotter'; |
| import {plotCatapult as plot2022Catapult} from '../../y2022/control_loops/superstructure/catapult_plotter' |
| import {plotClimber as plot2022Climber} from '../../y2022/control_loops/superstructure/climber_plotter' |
| import {plotIntakeBack as plot2022IntakeBack, plotIntakeFront as plot2022IntakeFront} from '../../y2022/control_loops/superstructure/intake_plotter' |
| import {plotSuperstructure as plot2022Superstructure} from '../../y2022/control_loops/superstructure/superstructure_plotter' |
| import {plotTurret as plot2022Turret} from '../../y2022/control_loops/superstructure/turret_plotter' |
| import {plotLocalizer as plot2022Localizer} from '../../y2022/localizer/localizer_plotter' |
| import {plotVision as plot2022Vision} from '../../y2022/vision/vision_plotter' |
| import {plotSuperstructure as plot2023Superstructure} from '../../y2023/control_loops/superstructure/superstructure_plotter' |
| import {plotVision as plot2023Corrections} from '../../y2023/localizer/corrections_plotter' |
| import {plotLocalizer as plot2023Localizer} from '../../y2023/localizer/localizer_plotter' |
| import {plotClimber as plot2024Climber, plotIntake as plot2024Intake, plotSuperstructure as plot2024Superstructure} from '../../y2024/control_loops/superstructure/superstructure_plotter' |
| import {plotSwerve} from '../../y2024_swerve/control_loops/swerve_plotter' |
| import {plotVision as plot2024Corrections} from '../../y2024/localizer/corrections_plotter' |
| import {plotLocalizer as plot2024Localizer} from '../../y2024/localizer/localizer_plotter' |
| import {plotDownEstimator} from '../control_loops/drivetrain/down_estimator_plotter'; |
| import {plotDrivetrain} from '../control_loops/drivetrain/drivetrain_plotter'; |
| import {plotRobotState} from '../control_loops/drivetrain/robot_state_plotter' |
| import {plotSpline} from '../control_loops/drivetrain/spline_plotter'; |
| import {plotImu} from '../wpilib/imu_plotter'; |
| |
| const rootDiv = document.createElement('div'); |
| rootDiv.style.width = '100%'; |
| document.body.appendChild(rootDiv); |
| |
| const helpDiv = document.createElement('div'); |
| rootDiv.appendChild(helpDiv); |
| helpDiv.innerHTML = |
| 'Help: click + drag to pan, double click to reset, scroll to zoom, ' + |
| 'right-click + drag to zoom to rectangle, Esc to cancel. ' + |
| 'Hold the x/y keys to only pan/zoom along the x/y axes.'; |
| |
| class PlotState { |
| public readonly div: HTMLElement; |
| private initialized = false; |
| constructor( |
| parentDiv: HTMLElement, |
| private readonly initializer: |
| (conn: Connection, element: Element) => void) { |
| this.div = document.createElement('div'); |
| parentDiv.appendChild(this.div); |
| this.hide(); |
| } |
| initialize(conn: Connection): void { |
| if (!this.initialized) { |
| this.initializer(conn, this.div); |
| this.initialized = true; |
| } |
| } |
| hide(): void { |
| this.div.style.display = "none"; |
| } |
| show(): void { |
| this.div.style.display = "block"; |
| } |
| } |
| |
| const plotSelect = document.createElement('select'); |
| rootDiv.appendChild(plotSelect); |
| |
| const plotDiv = document.createElement('div'); |
| plotDiv.style.marginTop = '10px'; |
| plotDiv.style.left = '0'; |
| plotDiv.style.position = 'absolute'; |
| plotDiv.style.width = '100%'; |
| rootDiv.appendChild(plotDiv); |
| |
| // The master list of all the plots that we provide. For a given config, it |
| // is possible that not all of these plots will be usable depending on the |
| // presence of certain channels. |
| const plotIndex = new Map<string, PlotState>([ |
| ['Demo', new PlotState(plotDiv, plotDemo)], |
| ['IMU', new PlotState(plotDiv, plotImu)], |
| ['Drivetrain', new PlotState(plotDiv, plotDrivetrain)], |
| ['Spline Debug', new PlotState(plotDiv, plotSpline)], |
| ['Down Estimator', new PlotState(plotDiv, plotDownEstimator)], |
| ['Robot State', new PlotState(plotDiv, plotRobotState)], |
| ['2024 Vision', new PlotState(plotDiv, plot2024Corrections)], |
| ['2024 Localizer', new PlotState(plotDiv, plot2024Localizer)], |
| ['2024 Superstructure', new PlotState(plotDiv, plot2024Superstructure)], |
| ['2024 Swerve', new PlotState(plotDiv, plotSwerve)], |
| ['2024 Climber', new PlotState(plotDiv, plot2024Climber)], |
| ['2024 Intake', new PlotState(plotDiv, plot2024Intake)], |
| ['2023 Vision', new PlotState(plotDiv, plot2023Corrections)], |
| ['2023 Localizer', new PlotState(plotDiv, plot2023Localizer)], |
| ['2023 Superstructure', new PlotState(plotDiv, plot2023Superstructure)], |
| ['2020 Finisher', new PlotState(plotDiv, plot2020Finisher)], |
| ['2020 Accelerator', new PlotState(plotDiv, plot2020Accelerator)], |
| ['2020 Hood', new PlotState(plotDiv, plot2020Hood)], |
| ['2020 Turret', new PlotState(plotDiv, plot2020Turret)], |
| ['2020 Localizer', new PlotState(plotDiv, plot2020Localizer)], |
| ['2022 Localizer', new PlotState(plotDiv, plot2022Localizer)], |
| ['2022 Vision', new PlotState(plotDiv, plot2022Vision)], |
| ['2022 Superstructure', new PlotState(plotDiv, plot2022Superstructure)], |
| ['2022 Catapult', new PlotState(plotDiv, plot2022Catapult)], |
| ['2022 Intake Front', new PlotState(plotDiv, plot2022IntakeFront)], |
| ['2022 Intake Back', new PlotState(plotDiv, plot2022IntakeBack)], |
| ['2022 Climber', new PlotState(plotDiv, plot2022Climber)], |
| ['2022 Turret', new PlotState(plotDiv, plot2022Turret)], |
| ['Y2021 3rd Robot Superstructure', new PlotState(plotDiv, plot2021Superstructure)], |
| ]); |
| |
| const invalidSelectValue = 'null'; |
| function getDefaultPlot(): string { |
| const urlParams = (new URL(document.URL)).searchParams; |
| const urlParamKey = 'plot'; |
| if (!urlParams.has(urlParamKey)) { |
| return invalidSelectValue; |
| } |
| const desiredPlot = urlParams.get(urlParamKey); |
| if (!plotIndex.has(desiredPlot)) { |
| return invalidSelectValue; |
| } |
| return desiredPlot; |
| } |
| |
| const conn = new Connection(); |
| |
| let reloadOnChange = false; |
| |
| conn.connect(); |
| |
| conn.addConfigHandler((config: Configuration) => { |
| plotSelect.add(new Option("Select Plot", invalidSelectValue)); |
| for (const name of plotIndex.keys()) { |
| plotSelect.add(new Option(name, name)); |
| } |
| plotSelect.addEventListener('input', () => { |
| for (const plot of plotIndex.values()) { |
| plot.hide(); |
| } |
| if (plotSelect.value == invalidSelectValue) { |
| return; |
| } |
| plotIndex.get(plotSelect.value).initialize(conn); |
| plotIndex.get(plotSelect.value).show(); |
| // Set the URL so that if you reload you get back to this plot. |
| window.history.replaceState( |
| null, null, '?plot=' + encodeURIComponent(plotSelect.value)); |
| if (reloadOnChange) { |
| window.location.reload(); |
| } |
| reloadOnChange = true; |
| }); |
| plotSelect.value = getDefaultPlot(); |
| // Force the event to occur once at the start. |
| plotSelect.dispatchEvent(new Event('input')); |
| }); |