From fe96ab9f44ba9d7a64366b2aacb5db9f39795cd6 Mon Sep 17 00:00:00 2001 From: Henning Pohl Date: Fri, 23 Oct 2015 18:18:42 +0200 Subject: [PATCH] Added functionality to plot multiple signals at the same time --- app/src/processing/app/SerialPlotter.java | 120 ++++++++++++++++------ app/src/processing/app/Theme.java | 13 +++ build/shared/lib/theme/theme.txt | 10 ++ 3 files changed, 109 insertions(+), 34 deletions(-) diff --git a/app/src/processing/app/SerialPlotter.java b/app/src/processing/app/SerialPlotter.java index 0ff7ff2d4..c5d2621da 100644 --- a/app/src/processing/app/SerialPlotter.java +++ b/app/src/processing/app/SerialPlotter.java @@ -23,6 +23,7 @@ import processing.app.helpers.CircularBuffer; import processing.app.helpers.Ticks; import processing.app.legacy.PApplet; +import java.util.ArrayList; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; @@ -35,24 +36,73 @@ import static processing.app.I18n.tr; public class SerialPlotter extends AbstractMonitor { private final StringBuffer messageBuffer; - private CircularBuffer buffer; private JComboBox serialRates; private Serial serial; private int serialRate; + private ArrayList graphs; + private final static int BUFFER_CAPACITY = 500; + + private static class Graph { + public CircularBuffer buffer; + private Color color; + + public Graph(int id) { + buffer = new CircularBuffer(BUFFER_CAPACITY); + color = Theme.getColorCycleColor("plotting.graphcolor", id); + } + + public void paint(Graphics2D g, float xstep, double minY, + double maxY, double rangeY, double height) { + g.setColor(color); + g.setStroke(new BasicStroke(1.0f)); + + for (int i = 0; i < buffer.size() - 1; ++i) { + g.drawLine( + (int) (i * xstep), (int) transformY(buffer.get(i), minY, rangeY, height), + (int) ((i + 1) * xstep), (int) transformY(buffer.get(i + 1), minY, rangeY, height) + ); + } + } + + private float transformY(double rawY, double minY, double rangeY, double height) { + return (float) (5 + (height - 10) * (1.0 - (rawY - minY) / rangeY)); + } + } + private class GraphPanel extends JPanel { private double minY, maxY, rangeY; private Rectangle bounds; private int xOffset; private final Font font; - private final Color graphColor; + private final Color bgColor; public GraphPanel() { font = Theme.getFont("console.font"); - graphColor = Theme.getColor("header.bgcolor"); + bgColor = Theme.getColor("plotting.bgcolor"); xOffset = 20; } + private Ticks computeBounds() { + minY = Double.POSITIVE_INFINITY; + maxY = Double.NEGATIVE_INFINITY; + for(Graph g : graphs) { + double bMin = g.buffer.min() / 2.0; + double bMax = g.buffer.max() * 2.0; + minY = bMin < minY ? bMin : minY; + maxY = bMax > maxY ? bMax : maxY; + } + + Ticks ticks = new Ticks(minY, maxY, 3); + minY = Math.min(minY, ticks.getTick(0)); + maxY = Math.max(maxY, ticks.getTick(ticks.getTickCount() - 1)); + rangeY = maxY - minY; + minY -= 0.05 * rangeY; + maxY += 0.05 * rangeY; + rangeY = maxY - minY; + return ticks; + } + @Override public void paintComponent(Graphics g1) { Graphics2D g = (Graphics2D) g1; @@ -61,20 +111,12 @@ public class SerialPlotter extends AbstractMonitor { super.paintComponent(g); bounds = g.getClipBounds(); - setBackground(Color.WHITE); - if (buffer.isEmpty()) { + setBackground(bgColor); + if (graphs.isEmpty()) { return; } - minY = buffer.min() / 2; - maxY = buffer.max() * 2; - Ticks ticks = new Ticks(minY, maxY, 3); - minY = Math.min(minY, ticks.getTick(0)); - maxY = Math.max(maxY, ticks.getTick(ticks.getTickCount() - 1)); - rangeY = maxY - minY; - minY -= 0.05 * rangeY; - maxY += 0.05 * rangeY; - rangeY = maxY - minY; + Ticks ticks = computeBounds(); g.setStroke(new BasicStroke(1.0f)); FontMetrics fm = g.getFontMetrics(); @@ -92,19 +134,21 @@ public class SerialPlotter extends AbstractMonitor { g.drawLine(bounds.x + xOffset, bounds.y + 5, bounds.x + xOffset, bounds.y + bounds.height - 10); g.setTransform(AffineTransform.getTranslateInstance(xOffset, 0)); - float xstep = (float) (bounds.width - xOffset) / (float) buffer.capacity(); + float xstep = (float) (bounds.width - xOffset) / (float) BUFFER_CAPACITY; + int legendLength = graphs.size() * 10 + (graphs.size() - 1) * 3; - g.setColor(graphColor); - g.setStroke(new BasicStroke(0.75f)); - - for (int i = 0; i < buffer.size() - 1; ++i) { - g.drawLine( - (int) (i * xstep), (int) transformY(buffer.get(i)), - (int) ((i + 1) * xstep), (int) transformY(buffer.get(i + 1)) - ); + for(int i = 0; i < graphs.size(); ++i) { + graphs.get(i).paint(g, xstep, minY, maxY, rangeY, bounds.height); + if(graphs.size() > 1) { + g.fillRect(bounds.width - (xOffset + legendLength + 10) + i * 13, 10, 10, 10); + } } } + private float transformY(double rawY) { + return (float) (5 + (bounds.height - 10) * (1.0 - (rawY - minY) / rangeY)); + } + @Override public Dimension getMinimumSize() { return new Dimension(200, 100); @@ -114,10 +158,6 @@ public class SerialPlotter extends AbstractMonitor { public Dimension getPreferredSize() { return new Dimension(500, 250); } - - private float transformY(double rawY) { - return (float) (5 + (bounds.height - 10) * (1.0 - (rawY - minY) / rangeY)); - } } public SerialPlotter(BoardPort port) { @@ -140,12 +180,12 @@ public class SerialPlotter extends AbstractMonitor { }); messageBuffer = new StringBuffer(); + graphs = new ArrayList(); } protected void onCreateWindow(Container mainPane) { mainPane.setLayout(new BorderLayout()); - buffer = new CircularBuffer(500); GraphPanel graphPanel = new GraphPanel(); mainPane.add(graphPanel, BorderLayout.CENTER); @@ -182,14 +222,26 @@ public class SerialPlotter extends AbstractMonitor { } String line = messageBuffer.substring(0, linebreak); - line = line.trim(); messageBuffer.delete(0, linebreak + 1); - try { - double value = Double.valueOf(line); - buffer.add(value); - } catch (NumberFormatException e) { - // ignore + line = line.trim(); + String[] parts = line.split("[, ]+"); + if(parts.length == 0) { + continue; + } + + int validParts = 0; + for(int i = 0; i < parts.length; ++i) { + try { + double value = Double.valueOf(parts[i]); + if(i >= graphs.size()) { + graphs.add(new Graph(validParts)); + } + graphs.get(validParts).buffer.add(value); + validParts++; + } catch (NumberFormatException e) { + // ignore + } } } diff --git a/app/src/processing/app/Theme.java b/app/src/processing/app/Theme.java index 5118ac40a..893050def 100644 --- a/app/src/processing/app/Theme.java +++ b/app/src/processing/app/Theme.java @@ -94,6 +94,19 @@ public class Theme { set(key, String.valueOf(value)); } + static public Color getColorCycleColor(String name, int i) { + int cycleSize = getInteger(name + ".size"); + name = String.format("%s.%02d", name, i % cycleSize); + return PreferencesHelper.parseColor(get(name)); + } + + static public void setColorCycleColor(String name, int i, Color color) { + name = String.format("%s.%02d", name, i); + PreferencesHelper.putColor(table, name, color); + int cycleSize = getInteger(name + ".size"); + setInteger(name + ".size", (i + 1) > cycleSize ? (i + 1) : cycleSize); + } + static public Color getColor(String name) { return PreferencesHelper.parseColor(get(name)); } diff --git a/build/shared/lib/theme/theme.txt b/build/shared/lib/theme/theme.txt index e583d6f25..67ac3d33b 100644 --- a/build/shared/lib/theme/theme.txt +++ b/build/shared/lib/theme/theme.txt @@ -37,6 +37,16 @@ buttons.bgcolor = #006468 buttons.status.font = SansSerif,plain,12 buttons.status.color = #ffffff +# GUI - PLOTTING +# color cycle created via colorbrewer2.org +plotting.bgcolor = #ffffff +plotting.color = #ffffff +plotting.graphcolor.size = 4 +plotting.graphcolor.00 = #2c7bb6 +plotting.graphcolor.01 = #fdae61 +plotting.graphcolor.02 = #d7191c +plotting.graphcolor.03 = #abd9e9 + # GUI - LINESTATUS linestatus.color = #ffffff linestatus.bgcolor = #006468