From 4cb72ceb9b2317ea2bc2ae107f5cfd67600cf2d6 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Tue, 8 Sep 2015 16:25:09 +0200 Subject: [PATCH] Code cleanup SerialPlotter now uses min/2 and max*2 instead of plain min and max Ticks works also when min == max Fixes #3767 --- app/src/processing/app/SerialPlotter.java | 160 ++++++++---------- .../app/helpers/CircularBuffer.java | 53 +++--- app/src/processing/app/helpers/Ticks.java | 39 +++-- 3 files changed, 121 insertions(+), 131 deletions(-) diff --git a/app/src/processing/app/SerialPlotter.java b/app/src/processing/app/SerialPlotter.java index 8497fcaf3..0ff7ff2d4 100644 --- a/app/src/processing/app/SerialPlotter.java +++ b/app/src/processing/app/SerialPlotter.java @@ -19,23 +19,24 @@ package processing.app; import cc.arduino.packages.BoardPort; +import processing.app.helpers.CircularBuffer; +import processing.app.helpers.Ticks; import processing.app.legacy.PApplet; -import processing.app.helpers.*; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + import static processing.app.I18n.tr; -import java.awt.*; -import java.awt.event.*; -import java.awt.geom.*; -import javax.swing.*; -import javax.swing.border.*; - public class SerialPlotter extends AbstractMonitor { - private StringBuffer messageBuffer; - private CircularBuffer buffer; - private GraphPanel graphPanel; - private JComboBox serialRates; + private final StringBuffer messageBuffer; + private CircularBuffer buffer; + private JComboBox serialRates; private Serial serial; private int serialRate; @@ -43,30 +44,30 @@ public class SerialPlotter extends AbstractMonitor { private double minY, maxY, rangeY; private Rectangle bounds; private int xOffset; - private Font font; - private Color graphColor; - + private final Font font; + private final Color graphColor; + public GraphPanel() { font = Theme.getFont("console.font"); graphColor = Theme.getColor("header.bgcolor"); xOffset = 20; } - + @Override public void paintComponent(Graphics g1) { - Graphics2D g = (Graphics2D)g1; + Graphics2D g = (Graphics2D) g1; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setFont(font); super.paintComponent(g); - + bounds = g.getClipBounds(); setBackground(Color.WHITE); - if(buffer.isEmpty()) { + if (buffer.isEmpty()) { return; } - - minY = buffer.min(); - maxY = buffer.max(); + + 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)); @@ -77,45 +78,45 @@ public class SerialPlotter extends AbstractMonitor { g.setStroke(new BasicStroke(1.0f)); FontMetrics fm = g.getFontMetrics(); - for(int i = 0; i < ticks.getTickCount(); ++i) { + for (int i = 0; i < ticks.getTickCount(); ++i) { double tick = ticks.getTick(i); Rectangle2D fRect = fm.getStringBounds(String.valueOf(tick), g); - xOffset = Math.max(xOffset, (int)fRect.getWidth() + 15); + xOffset = Math.max(xOffset, (int) fRect.getWidth() + 15); // draw tick - g.drawLine(xOffset - 5, (int)transformY(tick), xOffset + 2, (int)transformY(tick)); + g.drawLine(xOffset - 5, (int) transformY(tick), xOffset + 2, (int) transformY(tick)); // draw tick label - g.drawString(String.valueOf(tick), xOffset - (int)fRect.getWidth() - 10, transformY(tick) - (float)fRect.getHeight() * 0.5f + fm.getAscent()); + g.drawString(String.valueOf(tick), xOffset - (int) fRect.getWidth() - 10, transformY(tick) - (float) fRect.getHeight() * 0.5f + fm.getAscent()); } 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(); - + + g.setTransform(AffineTransform.getTranslateInstance(xOffset, 0)); + float xstep = (float) (bounds.width - xOffset) / (float) buffer.capacity(); + g.setColor(graphColor); g.setStroke(new BasicStroke(0.75f)); - - for(int i = 0; i < buffer.size() - 1; ++i) { + + 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)) + (int) (i * xstep), (int) transformY(buffer.get(i)), + (int) ((i + 1) * xstep), (int) transformY(buffer.get(i + 1)) ); } } - + @Override public Dimension getMinimumSize() { return new Dimension(200, 100); } - + @Override public Dimension getPreferredSize() { return new Dimension(500, 250); } - + private float transformY(double rawY) { - return (float)(5 + (bounds.height - 10) * (1.0 - (rawY - minY) / rangeY)); + return (float) (5 + (bounds.height - 10) * (1.0 - (rawY - minY) / rangeY)); } } @@ -124,93 +125,80 @@ public class SerialPlotter extends AbstractMonitor { serialRate = PreferencesData.getInteger("serial.debug_rate"); serialRates.setSelectedItem(serialRate + " " + tr("baud")); - onSerialRateChange(new ActionListener() { - public void actionPerformed(ActionEvent event) { - String wholeString = (String) serialRates.getSelectedItem(); - String rateString = wholeString.substring(0, wholeString.indexOf(' ')); - serialRate = Integer.parseInt(rateString); - PreferencesData.set("serial.debug_rate", rateString); - try { - close(); - Thread.sleep(100); // Wait for serial port to properly close - open(); - } catch (InterruptedException e) { - // noop - } catch (Exception e) { - System.err.println(e); - } + onSerialRateChange(event -> { + String wholeString = (String) serialRates.getSelectedItem(); + String rateString = wholeString.substring(0, wholeString.indexOf(' ')); + serialRate = Integer.parseInt(rateString); + PreferencesData.set("serial.debug_rate", rateString); + try { + close(); + Thread.sleep(100); // Wait for serial port to properly close + open(); + } catch (Exception e) { + // ignore } }); messageBuffer = new StringBuffer(); } - + protected void onCreateWindow(Container mainPane) { mainPane.setLayout(new BorderLayout()); - - Font consoleFont = Theme.getFont("console.font"); - Font editorFont = PreferencesData.getFont("editor.font"); - Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize()); - + buffer = new CircularBuffer(500); - graphPanel = new GraphPanel(); - + GraphPanel graphPanel = new GraphPanel(); + mainPane.add(graphPanel, BorderLayout.CENTER); - + JPanel pane = new JPanel(); pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS)); pane.setBorder(new EmptyBorder(4, 4, 4, 4)); - - serialRates = new JComboBox(); - for (int i = 0; i < serialRateStrings.length; i++) - serialRates.addItem(serialRateStrings[i] + " " + tr("baud")); - + + serialRates = new JComboBox<>(); + for (String serialRateString : serialRateStrings) serialRates.addItem(serialRateString + " " + tr("baud")); + serialRates.setMaximumSize(serialRates.getMinimumSize()); pane.add(Box.createRigidArea(new Dimension(8, 0))); pane.add(serialRates); - + mainPane.add(pane, BorderLayout.SOUTH); } - - protected void onEnableWindow(boolean enable) - { - serialRates.setEnabled(enable); + + protected void onEnableWindow(boolean enable) { + serialRates.setEnabled(enable); } - - public void onSerialRateChange(ActionListener listener) { + + private void onSerialRateChange(ActionListener listener) { serialRates.addActionListener(listener); } - + public void message(final String s) { messageBuffer.append(s); - while(true) { + while (true) { int linebreak = messageBuffer.indexOf("\n"); - if(linebreak == -1) { + if (linebreak == -1) { break; } - + 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) { - continue; // ignore lines that can't be cast to a number + } catch (NumberFormatException e) { + // ignore } } - SwingUtilities.invokeLater(new Runnable() { - public void run() { - SerialPlotter.this.repaint(); - }}); + SwingUtilities.invokeLater(SerialPlotter.this::repaint); } - + public void open() throws Exception { super.open(); - + if (serial != null) return; serial = new Serial(getBoardPort().getAddress(), serialRate) { diff --git a/app/src/processing/app/helpers/CircularBuffer.java b/app/src/processing/app/helpers/CircularBuffer.java index 623982204..8396c8fd2 100644 --- a/app/src/processing/app/helpers/CircularBuffer.java +++ b/app/src/processing/app/helpers/CircularBuffer.java @@ -3,77 +3,74 @@ package processing.app.helpers; import java.util.NoSuchElementException; public class CircularBuffer { - private double[] elements; + + private final double[] elements; private int start = -1; private int end = -1; - private int capacity; - + private final int capacity; + public void add(double num) { end = (end + 1) % capacity; elements[end] = num; - if(start == end || start == -1) { + if (start == end || start == -1) { start = (start + 1) % capacity; } } - + public double get(int index) { - if(index >= capacity) { + if (index >= capacity) { throw new IndexOutOfBoundsException(); } - if(index >= size()) { + if (index >= size()) { throw new IndexOutOfBoundsException(); } - + return elements[(start + index) % capacity]; } - + public boolean isEmpty() { return start == -1 && end == -1; } - - public void clear() { - start = end = -1; - } - + public CircularBuffer(int capacity) { this.capacity = capacity; elements = new double[capacity]; } - + public double min() { - if(size() == 0) { + if (size() == 0) { throw new NoSuchElementException(); } - + double out = get(0); - for(int i = 1; i < size(); ++i) { + for (int i = 1; i < size(); ++i) { out = Math.min(out, get(i)); } - + return out; } - + public double max() { - if(size() == 0) { + if (size() == 0) { throw new NoSuchElementException(); } - + double out = get(0); - for(int i = 1; i < size(); ++i) { + for (int i = 1; i < size(); ++i) { out = Math.max(out, get(i)); } - + return out; } - + public int size() { - if(end == -1) { + if (end == -1) { return 0; } - + return (end - start + capacity) % capacity + 1; } - + public int capacity() { return capacity; } diff --git a/app/src/processing/app/helpers/Ticks.java b/app/src/processing/app/helpers/Ticks.java index a4b32a2c3..e9e9e842a 100644 --- a/app/src/processing/app/helpers/Ticks.java +++ b/app/src/processing/app/helpers/Ticks.java @@ -1,45 +1,50 @@ package processing.app.helpers; public class Ticks { + + private final int tickCount; + private final double[] ticks; private double tickMin; private double tickMax; private double tickStep; - private int tickCount; - - private double[] ticks; - - public Ticks(double min, double max, int tickCount) { + + public Ticks(double min, double max, int tickCount) { double range = max - min; - double exp = Math.floor(Math.log10(range / (tickCount - 1))); + double exp; + if (range == 0.0) { + exp = 0; + } else { + exp = Math.floor(Math.log10(range / (tickCount - 1))); + } double scale = Math.pow(10, exp); - + double rawTickStep = (range / (tickCount - 1)) / scale; - for(double potentialStep : new double[] {1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0}) { - if(potentialStep < rawTickStep) { + for (double potentialStep : new double[]{1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0}) { + if (potentialStep < rawTickStep) { continue; } - + tickStep = potentialStep * scale; tickMin = tickStep * Math.floor(min / tickStep); tickMax = tickMin + tickStep * (tickCount - 1); - if(tickMax >= max) { + if (tickMax >= max) { break; } } - - tickCount -= (int)Math.floor((tickMax - max) / tickStep); + + tickCount -= (int) Math.floor((tickMax - max) / tickStep); this.tickCount = tickCount; - + ticks = new double[tickCount]; - for(int i = 0; i < tickCount; ++i) { + for (int i = 0; i < tickCount; ++i) { ticks[i] = tickMin + i * tickStep; } } - + public double getTick(int i) { return ticks[i]; } - + public int getTickCount() { return tickCount; }