Skip to content

Commit

Permalink
Update Plotting Library (#45)
Browse files Browse the repository at this point in the history
* Add FuncSeries (unused) and graphs

* Remove factory methods from series

* Add min, max time to TimeSeries

* Add axes class for convenience

* Decouple updating series and drawing

* Add timespan

* Add playground with data graph

* Remove imports from series

* Add polling option to series

* Use polling in plot to standardize runtime

* Simplify playground example

* Simplify playground example

* Replace timespan with domain

* make mouse methods easier

* Add everything to playground

* Update readme

* Add BStream to playground

* Rename duration to capacity in Config

* Add Series documentation

* Replace isPolling() in Series with final var set in constructor

* Add an in place sort and lower bound finding to Interpolator (#47)

* Add lower bound method

* Remove exceptions from getSortedPoints

* Add in-place sort

Co-authored-by: iwei20 <[email protected]>

* Fix naming on polling variables/method

* Revert "Add an in place sort and lower bound finding to Interpolator (#47)" (#48)

This reverts commit b8d1730.

* Update Series documentation

* Add Plot documentation

* Change isPolling documentation to not use isPolling() method

* Make method protected

* Proofread readme

* fix config section

* Update later sections

* Update "Working with a Plot"

* Add javadocs documentation to plot classes

Co-authored-by: Myles Pasetsky <[email protected]>

Co-authored-by: BenG49 <[email protected]>
Co-authored-by: Ivan Wei <[email protected]>
Co-authored-by: iwei20 <[email protected]>
Co-authored-by: Myles Pasetsky <[email protected]>
  • Loading branch information
5 people authored Jun 30, 2022
1 parent 762ace2 commit 583a674
Show file tree
Hide file tree
Showing 8 changed files with 752 additions and 66 deletions.
103 changes: 103 additions & 0 deletions src/com/stuypulse/stuylib/util/plot/FuncSeries.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.stuypulse.stuylib.util.plot;

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

import com.stuypulse.stuylib.streams.filters.IFilter;

/**
* A FuncSeries plots a function (IFilter) over a given domain.
*
* A FuncSeries data is precomputed, so its x and y values
* are non-changing. This means that the series does not get
* polled and does not implement pop() or poll().
*
* @author Ben Goldfisher
*/
public class FuncSeries extends Series {

/** Domain describes the x-values that the series will be graphed over */
public static class Domain {

/** min and max x-values that will be graphed */
public final double min, max;

/**
* Creates a Domain
*
* @param min smallest x-value that will be graphed
* @param max largest x-value that will be graphed
*/
public Domain(double min, double max) {
this.min = min;
this.max = max;
}
}

/** Contains the precomputed (x, y) data values */
private List<Double> xValues;
private List<Double> yValues;

/**
* Creates a FuncSeries and specifies that it is not polling.
*
* @param config series config
* @param domain range of x-values inputted to the function
* @param func a function with one input and output to be graphed
*/
public FuncSeries(Config config, Domain domain, IFilter func) {
super(config, false);

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

// Fill the series capacity with evenly spaced points in the given domain
for (int i = 0; i < config.getCapacity(); i++) {
double x = (i * (domain.max - domain.min)) / config.getCapacity() + domain.min;

xValues.add(x);
yValues.add(func.get(x));
}
}

/** @return max number of stored (x, y) values */
@Override
public int size() {
return getConfig().getCapacity();
}

/**
* Returns reference to x values, which is safe because they
* are precompted and non-changing
*
* @return reference to x values
*/
@Override
protected List<Double> getSafeXValues() {
return xValues;
}

/**
* Returns reference to y values, which is safe because they
* are precompted and non-changing
*
* @return reference to y values
*/
@Override
protected List<Double> getSafeYValues() {
return yValues;
}

/**
* No-op because series is non-polling
*/
@Override
protected void pop() {}

/**
* No-op because series is non-polling
*/
@Override
protected void poll() {}

}
113 changes: 95 additions & 18 deletions src/com/stuypulse/stuylib/util/plot/Playground.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,116 @@

package com.stuypulse.stuylib.util.plot;

import com.stuypulse.stuylib.streams.IStream;
import com.stuypulse.stuylib.streams.vectors.VStream;
import com.stuypulse.stuylib.streams.vectors.filters.VJerkLimit;
import com.stuypulse.stuylib.streams.vectors.filters.VLowPassFilter;
import com.stuypulse.stuylib.streams.vectors.filters.VRateLimit;
import com.stuypulse.stuylib.math.Vector2D;
import com.stuypulse.stuylib.math.interpolation.*;
import com.stuypulse.stuylib.streams.*;
import com.stuypulse.stuylib.streams.booleans.*;
import com.stuypulse.stuylib.streams.booleans.filters.*;
import com.stuypulse.stuylib.streams.filters.*;
import com.stuypulse.stuylib.streams.vectors.*;
import com.stuypulse.stuylib.streams.vectors.filters.*;

import com.stuypulse.stuylib.util.plot.FuncSeries.Domain;
import com.stuypulse.stuylib.util.plot.Series.Config;
import com.stuypulse.stuylib.util.plot.TimeSeries.TimeSpan;

public class Playground {

public interface Constants {
int DURATION = 100;
int CAPACITY = 200;

String TITLE = "StuyLib Plotting Library";
String X_AXIS = "x-axis";
String Y_AXIS = "y-axis";

int WIDTH = 800;
int HEIGHT = 600;

double MIN_X = 0.0;
double MAX_X = 1.0;

double MIN_Y = 0.0;
double MAX_Y = 1.0;

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 Series make(String id, IFilter function) {
return new FuncSeries(new Config(id, CAPACITY), new Domain(MIN_X, MAX_X), function);
}

public static Series make(String id, IStream series) {
return new TimeSeries(new Config(id, CAPACITY), new TimeSpan(MIN_X, MAX_X), series);
}

public static Series make(String id, IStream stream) {
return Series.make(new Config(id, DURATION), stream);
public static Series make(String id, VStream series) {
return new XYSeries(new Config(id, CAPACITY), series);
}

public static Series make(String id, VStream stream) {
return Series.make(new Config(id, DURATION), stream);
public static Series make(String id, BStream series) {
return make(id, IStream.create(series));
}
}


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

plot
.addSeries(Constants.make(
"y=x",
x -> x
))

.addSeries(Constants.make(
"interp",
new NearestInterpolator(
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)
))

VStream mouse = plot.getMouse()::getPosition;
IStream mouse_y = plot.getMouse()::getY;
.addSeries(Constants.make(
"jerk limit",
VStream.create(plot::getMouse).filtered(new VJerkLimit(10.0, 5.0))
))

plot.addSeries(Constants.make("mouse", mouse))
.addSeries(Constants.make("lpf", mouse.filtered(new VLowPassFilter(0.2))))
.addSeries(Constants.make("rate", mouse.filtered(new VRateLimit(1.0))))
.addSeries(Constants.make("jerk", mouse.filtered(new VJerkLimit(1.0, 10.0))));
;

for (; ; ) {
while (plot.isRunning()) {
plot.update();
Thread.sleep(20);
}
Expand Down
84 changes: 77 additions & 7 deletions src/com/stuypulse/stuylib/util/plot/Plot.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,58 @@
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;

import com.stuypulse.stuylib.math.Vector2D;

import org.knowm.xchart.XChartPanel;
import org.knowm.xchart.XYChart;
import org.knowm.xchart.XYChartBuilder;

/**
* A plot contains and manages the window to which any data
* is drawn.
*
* It stores the Series that it is going draw. It also has
* methods that read the mouse's position, which can be used as
* an input stream for a series.
*
* @author Myles Pasetsky ([email protected])
*/
public class Plot {

/** A collection of Series to be graphed */
private List<Series> plots;

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

/** A reference to the XChart library */
private XYChart instance;
private XChartPanel<XYChart> panel;

/** 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 & window settings
*/
public Plot(Settings settings) {
// Setup series
plots = new ArrayList<>();

// Setup window
frame = new JFrame(settings.getTitle());

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

// Create XYChart using settings
instance =
new XYChartBuilder()
.title(settings.getTitle())
Expand All @@ -58,32 +86,74 @@ public Plot(Settings settings) {

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

runOnce = true;
}

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

public MouseTracker getMouse() {
return mouse;
/** @return mouse position */
public Vector2D getMouse() {
return mouse.getPosition();
}

/** @return mouse y position */
public double getMouseY() {
return mouse.getY();
}

/** @return mouse x position */
public double getMouseX() {
return mouse.getX();
}

/**
* Adds series to be graphed
*
* @return reference to self
*/
public Plot addSeries(Series... series) {
for (Series e : series) plots.add(e);
return this;
}

public void update() {

/** allows the series to update the XYChart */
public void updateSeries() {
for (Series plot : plots) {
plot.update(instance);
}

display();
}

private void display() {
/** repaints the screen */
public void display() {
Toolkit.getDefaultToolkit().sync();
panel.revalidate();
panel.repaint();
}

public void update() {
updateSeries();
display();
}

/**
* 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;
}

for (Series e : plots) {
if (e.isPolling()) return true;
}
return false;
}
}
Loading

0 comments on commit 583a674

Please sign in to comment.