Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bg/plot #55

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
50 changes: 50 additions & 0 deletions src/com/stuypulse/stuylib/util/plot/DataSeries.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* Copyright (c) 2022 StuyPulse Robotics. All rights reserved. */
/* This work is licensed under the terms of the MIT license */
/* found in the root directory of this project. */

package com.stuypulse.stuylib.util.plot;

import java.util.ArrayList;
import java.util.List;

import com.stuypulse.stuylib.math.Vector2D;

public class DataSeries extends Series {

private List<Double> xValues;
private List<Double> yValues;

public DataSeries(String label, Vector2D... points) {
super(new Config(label, points.length), false);

xValues = new ArrayList<Double>();
yValues = new ArrayList<Double>();

for (Vector2D point : points) {
xValues.add(point.x);
yValues.add(point.y);
}
}

@Override
public int size() {
return yValues.size();
}

@Override
protected List<Double> getSafeXValues() {
return xValues;
}

@Override
protected List<Double> getSafeYValues() {
return yValues;
}

@Override
protected void pop() {}

@Override
protected void poll() {}

}
84 changes: 48 additions & 36 deletions src/com/stuypulse/stuylib/util/plot/Playground.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public interface Constants {
String X_AXIS = "x-axis";
String Y_AXIS = "y-axis";

int WIDTH = 800;
int HEIGHT = 600;
int WIDTH = 640;
int HEIGHT = 480;

double MIN_X = 0.0;
double MAX_X = 1.0;
Expand All @@ -36,11 +36,17 @@ public interface Constants {

Settings SETTINGS =
new Settings()
.setSize(WIDTH, HEIGHT)
.setAxes(TITLE, X_AXIS, Y_AXIS)
.setXRange(MIN_X, MAX_X)
.setYRange(MIN_Y, MAX_Y);

public static Settings settings(String title) {
return new Settings()
.setAxes(title, X_AXIS, Y_AXIS)
.setXRange(MIN_X, MAX_X)
.setYRange(MIN_Y, MAX_Y);
}

public static Series make(String id, IFilter function) {
return new FuncSeries(new Config(id, CAPACITY), new Domain(MIN_X, MAX_X), function);
}
Expand All @@ -59,39 +65,45 @@ public static Series make(String id, BStream series) {
}

public static void main(String[] args) throws InterruptedException {
Plot plot = new Plot(Constants.SETTINGS);

plot.addSeries(Constants.make("y=x", x -> x))
.addSeries(
Constants.make(
"interp",
new LinearInterpolator(
new Vector2D(0.0, 0.43),
new Vector2D(0.2, 0.56),
new Vector2D(0.4, 0.72),
new Vector2D(0.6, 0.81),
new Vector2D(0.8, 0.02),
new Vector2D(1.0, 0.11))))
.addSeries(Constants.make("mouse y", IStream.create(plot::getMouseY)))
.addSeries(
Constants.make(
"lpf",
IStream.create(plot::getMouseY).filtered(new LowPassFilter(0.2))))
.addSeries(
Constants.make("mouse bool", BStream.create(() -> plot.getMouseY() > 0.5)))
.addSeries(
Constants.make(
"debounced",
BStream.create(() -> plot.getMouseY() > 0.5)
.filtered(new BDebounce.Both(1.0))))
.addSeries(Constants.make("mouse position", VStream.create(plot::getMouse)))
.addSeries(
Constants.make(
"jerk limit",
VStream.create(plot::getMouse)
.filtered(new VJerkLimit(10.0, 5.0))));

while (plot.isRunning()) {
Plot plot = new Plot();

plot.addTab(Constants.settings("Functions"))
.addSeries(Constants.make("y=x", x -> x))
.addSeries(
Constants.make(
"interp",
new LinearInterpolator(
new Vector2D(0.0, 0.43),
new Vector2D(0.2, 0.56),
new Vector2D(0.4, 0.72),
new Vector2D(0.6, 0.81),
new Vector2D(0.8, 0.02),
new Vector2D(1.0, 0.11))))

.addTab(Constants.settings("Filters"))
.addSeries(Constants.make("mouse y", IStream.create(plot::getMouseY)))
.addSeries(
Constants.make(
"lpf",
IStream.create(plot::getMouseY).filtered(new LowPassFilter(0.2))))
.addSeries(
Constants.make("mouse bool", BStream.create(() -> plot.getMouseY() > 0.5)))
.addSeries(
Constants.make(
"debounced",
BStream.create(() -> plot.getMouseY() > 0.5)
.filtered(new BDebounce.Both(1.0))))

.addTab(Constants.settings("XY Graph"))
.addSeries(Constants.make("mouse position", VStream.create(plot::getMouse)))
.addSeries(Constants.make(
"jerk limit",
VStream.create(plot::getMouse)
.filtered(new VJerkLimit(10.0, 5.0))))

.build(Constants.TITLE, Constants.WIDTH, Constants.HEIGHT);

for (;;) {
plot.update();
Thread.sleep(20);
}
Expand Down
128 changes: 47 additions & 81 deletions src/com/stuypulse/stuylib/util/plot/Plot.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import com.stuypulse.stuylib.math.Vector2D;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JFrame;
import org.knowm.xchart.XChartPanel;
import org.knowm.xchart.XYChart;
import org.knowm.xchart.XYChartBuilder;
import javax.swing.JTabbedPane;

/**
* A plot contains and manages the window to which any data is drawn.
Expand All @@ -25,72 +25,57 @@
*/
public class Plot {

/** A collection of Series to be graphed */
private List<Series> plots;
private List<Tab> tabs;
private Map<String, Integer> names;

/** The window that is created */
private JFrame frame;

/** A reference to the XChart library */
private XYChart instance;

private XChartPanel<XYChart> panel;
private JTabbedPane pane;

/** A utility for finding mouse positions */
private MouseTracker mouse;

/** A boolean to ensure the plot is updated at least once */
private boolean runOnce;

/**
* Creates a configured plot
*
* @param settings plot and window settings
*/
public Plot(Settings settings) {
// Setup series
plots = new ArrayList<>();

// Setup window
frame = new JFrame(settings.getTitle());
public Plot() {
tabs = new ArrayList<Tab>();
names = new HashMap<String, Integer>();
}

public void build(String title, int width, int height) {
pane = new JTabbedPane();

frame = new JFrame(title);

frame.getContentPane()
.setPreferredSize(new Dimension(settings.getWidth(), settings.getHeight()));
.setPreferredSize(new Dimension(width, height));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setResizable(false);

// Create XYChart using settings
instance =
new XYChartBuilder()
.title(settings.getTitle())
.xAxisTitle(settings.getXAxis())
.yAxisTitle(settings.getYAxis())
.build();

instance.getStyler().setYAxisMin(settings.getYMin());
instance.getStyler().setYAxisMax(settings.getYMax());
for (Tab tab : tabs) {
pane.addTab(tab.panel.getName(), tab.panel);
}

instance.getStyler().setXAxisMin(settings.getXMin());
instance.getStyler().setXAxisMax(settings.getXMax());
frame.getContentPane().add(pane);

panel = new XChartPanel<XYChart>(instance);
panel.setName(settings.getTitle());
frame.pack();

mouse = new MouseTracker(panel);

frame.getContentPane().add(panel);
frame.setResizable(false);

frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);

mouse = new MouseTracker(frame);
}

frame.setLocationRelativeTo(null);
frame.setVisible(true);
public Plot addTab(Settings settings) {
Tab tab = new Tab(settings);

runOnce = true;
}
tabs.add(tab);
names.put(settings.getTitle(), tabs.size() - 1);

/** Creates a plot with default settings */
public Plot() {
this(new Settings());
return this;
}

/** @return mouse position */
Expand All @@ -114,44 +99,25 @@ public double getMouseX() {
* @param series series to add
* @return reference to self
*/
public Plot addSeries(Series... series) {
for (Series e : series) plots.add(e);
return this;
}

/** allows the series to update the XYChart */
public void updateSeries() {
for (Series plot : plots) {
plot.update(instance);
public Plot addSeries(String tabId, Series... series) {
if (!names.containsKey(tabId)) {
System.err.println("Invalid tab ID \"" + tabId + "\" given");
return this;
}
}

/** repaints the screen */
public void display() {
Toolkit.getDefaultToolkit().sync();
panel.revalidate();
panel.repaint();
tabs.get(names.get(tabId)).addSeries(series);
return this;
}

public void update() {
updateSeries();
display();
public Plot addSeries(Series... series) {
tabs.get(tabs.size() - 1).addSeries(series);
return this;
}

/**
* Checks if any series are polling to see if the plot should still update.
*
* @return if the plot should still run
*/
public boolean isRunning() {
if (runOnce) {
runOnce = false;
return true;
}
public void update() {
int selected = pane.getSelectedIndex();

for (Series e : plots) {
if (e.isPolling()) return true;
}
return false;
if (tabs.get(selected).isRunning())
tabs.get(selected).update();
}
}
Loading