/* * To change this template, choose Tools | Templates * and open the template in the editor. */ /* * Plot.java * * Created on Dec 3, 2009, 2:32:07 PM */ package granolasdr.plot; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Stroke; import java.nio.DoubleBuffer; import java.util.ArrayList; import java.util.List; /** * * @author matt */ public class Plot extends javax.swing.JPanel { private int height = 0; private int width = 0; private double xAxisMin = Double.MAX_VALUE; private double yAxisMin = Double.MAX_VALUE; private double xAxisMax = -Double.MAX_VALUE; private double yAxisMax = -Double.MAX_VALUE; private int numVGrids = 4; private int numHGrids = 4; private Image offscreen = null; private Image onscreen = null; private Graphics2D bufferGraphics; private boolean drawVGridB = true; private boolean drawHGridB = true; private boolean drawHAxisB = true; private boolean drawVAxisB = true; // private boolean updateLock = false; private Color backgroundColor = Color.white; private Color selectionColor = Color.YELLOW; private Color axisColor = Color.black; private Color gridColor = Color.gray; private List<Series> dataList = new ArrayList<Series>(); private Point selectionStart = null; private Point selectionEnd = null; private boolean highLight = true; private final Object lock = new Object(); /** * Creates new form Plot */ public Plot() { initComponents(); } public void disableHighlight() { highLight = false; } /** * Returns the color of the background * * @return background color * */ public Color getBackgroundColor() { return backgroundColor; } /** * Sets the background color. This method invokes the changes in the awt * thread and is thread safe. * * @param color Color to draw on the background */ public void setBackgroundColor(Color color) { backgroundColor = color; update(); } /** * Returns the color of the selection * * @return background color * */ public Color getSelectionColor() { return selectionColor; } /** * Sets the selection color. This method invokes the changes in the awt * thread and is thread safe. * * @param color Color to draw on the background */ public void setSelectionColor(Color color) { selectionColor = color; update(); } public double[] getYAxis() { double[] returnValue = {0.0, 0.0}; returnValue[0] = yAxisMin; returnValue[1] = yAxisMax; return (returnValue); } public double[] getXAxis() { double[] returnValue = new double[2]; returnValue[0] = xAxisMin; returnValue[1] = xAxisMax; return (returnValue); } public void setYAxis(double max, double min) { yAxisMin = min; yAxisMax = max; } public void setXAxis(double max, double min) { xAxisMin = min; xAxisMax = max; } public void setZoom(ZoomCoordinates zoom) { yAxisMin = zoom.getMinY(); yAxisMax = zoom.getMaxY(); xAxisMin = zoom.getMinX(); xAxisMax = zoom.getMaxX(); } public ZoomCoordinates getZoom() { ZoomCoordinates ret = new ZoomCoordinates(xAxisMin, xAxisMax, yAxisMin, yAxisMax); return ret; } /** * Returns the color of the axis * * @return axis color * */ public Color getAxisColor() { return axisColor; } /** * Sets the axis color. This method invokes the changes in the awt thread * and is thread safe. * * @param color Color to draw on the axis */ public void setAxisColor(Color color) { axisColor = color; update(); } /** * Returns the color of the grid * * @return grid color * */ public Color getGridColor() { return gridColor; } /** * Sets the grid color. This method invokes the changes in the awt thread * and is thread safe. * * @param color Color to draw on the grid */ public void setGridColor(Color color) { gridColor = color; update(); } /** * Gets number of Horizontal Grids. * * @return Number of Horizontal Grids */ public int getNumHGrids() { return numHGrids; } /** * Will it draw the Horizontal Grid * * @return If it will draw horizontal grid */ public boolean isDrawHGrid() { return drawHGridB; } /** * Boolean to draw horizontal grid or not. This method invokes the changes * in the awt thread and is thread safe. * * @param b Whether or not to draw horizontal grid. */ public void setDrawHGrid(boolean b) { drawHGridB = b; update(); } /** * * @return Whether or not it will draw the horizontal axis. */ public boolean isDrawHAxis() { return drawHAxisB; } /** * Set whether or not to draw horizontal axis. This method invokes the * changes in the awt thread and is thread safe. * * @param b Boolean setting to draw horizontal axis or not */ public void setDrawHAxis(boolean b) { drawHAxisB = b; update(); } /** * * @return Whether or not it will draw the vertical axis. */ public boolean isDrawVAxis() { return drawVAxisB; } /** * Set whether or not to draw vertical axis. This method invokes the changes * in the awt thread and is thread safe. * * @param b Boolean setting to draw vertical axis or not */ public void setDrawVAxis(boolean b) { drawVAxisB = b; update(); } /** * Will it draw the vertical grid * * @return If it will draw vertical grid */ public boolean isDrawVGrid() { return drawVGridB; } /** * Boolean to draw vertical grid or not. This method invokes the changes in * the awt thread and is thread safe. * * @param b Whether or not to draw vertical grid. */ public void setDrawVGrid(boolean b) { drawVGridB = b; update(); } /** * Sets the number of horizontal grids to draw. This method invokes the * changes in the awt thread and is thread safe. * * @param num Number of grids to draw */ public void setNumHGrids(int num) { numHGrids = num; update(); } /** * Returns number of vertical grids that will be drawn * * @return Number of vetical grids to be drawn */ public int getNumVGrids() { return numVGrids; } /** * Sets the number of vertical grids to draw. This method invokes the * changes in the awt thread and is thread safe. * * @param num Number of grids to draw */ public void setNumVGrids(int num) { numVGrids = num; update(); } @Override public void paint(Graphics g) { synchronized (lock) { if (onscreen != null) { g.drawImage(onscreen, 0, 0, this); } } // super.paint(g); } private void drawVGrid() { double interval = width / ((double) numVGrids + 1.0); float[] pattern = {0.0f, 0.0f}; pattern[0] = 1; pattern[1] = 4; Stroke oldStroke = bufferGraphics.getStroke(); Stroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, pattern, 0.0f); bufferGraphics.setStroke(stroke); for (int cnt = 0; cnt < numVGrids; cnt++) { int mid = (int) ((cnt + 1) * interval); bufferGraphics.drawLine(mid, 0, mid, height); } bufferGraphics.setStroke(oldStroke); } private void drawVAxisTicks() { double interval = height / ((double) numHGrids + 1.0); int w = width / 2; for (int cnt = 0; cnt < numHGrids; cnt++) { int mid = (int) ((cnt + 1) * interval); bufferGraphics.drawLine(w - 2, mid, w + 2, mid); } } private void drawHAxisTicks() { double interval = width / ((double) numVGrids + 1.0); int h = height / 2; for (int cnt = 0; cnt < numVGrids; cnt++) { int mid = (int) ((cnt + 1) * interval); bufferGraphics.drawLine(mid, h - 2, mid, h + 2); } } private void drawHGrid() { double interval = height / ((double) numHGrids + 1.0); float[] pattern = {0.0f, 0.0f}; pattern[0] = 1; pattern[1] = 4; Stroke oldStroke = bufferGraphics.getStroke(); Stroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, pattern, 0.0f); bufferGraphics.setStroke(stroke); for (int cnt = 0; cnt < numHGrids; cnt++) { int mid = (int) ((cnt + 1) * interval); bufferGraphics.drawLine(0, mid, width, mid); } bufferGraphics.setStroke(oldStroke); } private void drawHAxis() { int h = height / 2; bufferGraphics.drawLine(0, h, width, h); } private void drawVAxis() { int w = width / 2; bufferGraphics.drawLine(w, 0, w, height); } private void drawSelection() { if (selectionStart != null && selectionEnd != null) { Color currpaint = bufferGraphics.getColor(); bufferGraphics.setColor(new Color(selectionColor.getRed(), selectionColor.getGreen(), selectionColor.getBlue(), 100)); int[] xPoints = {0, 0, 0, 0}; int[] yPoints = {0, 0, 0, 0}; xPoints[0] = selectionStart.x; yPoints[0] = selectionStart.y; xPoints[1] = selectionStart.x; yPoints[1] = selectionEnd.y; xPoints[2] = selectionEnd.x; yPoints[2] = selectionEnd.y; xPoints[3] = selectionEnd.x; yPoints[3] = selectionStart.y; bufferGraphics.fillPolygon(xPoints, yPoints, 4); bufferGraphics.setColor(Color.BLACK); bufferGraphics.drawPolygon(xPoints, yPoints, 4); bufferGraphics.setColor(currpaint); } } /** * Add a data series to the plot. This is for standard series data and will * draw the X axis with series value not actual time value. In other words, * this function adds data with not x values for the x axis. * * @param ydata - DoubleBuffer with the y axis data as the values. The y * data can change, just call an update(); * @param color - Color thay you want the axis to be. * @param autoscale - Autoscale the graph or go with current max and min. * @param connectLines - Connect the lines for each point or just draw * points. */ public void addData(DoubleBuffer ydata, int color, boolean autoscale, boolean connectLines) { addData(ydata, Color.getColor("", color), autoscale, connectLines); } public void addData(DoubleBuffer ydata, Color color, boolean autoscale, boolean connectLines) { Series s = new Series(ydata); if (autoscale) { s.findXMaxMin(); s.findYMaxMin(); } s.dataColor = color; s.connect = connectLines; dataList.add(s); update(); } public void clearData() { dataList.clear(); update(); } /** * Add data series to the graph. The data seris are stored in double * buffers. The data in the double buffers can change, just call update() * when you change the data to cause the series to recalculate and the graph * to update. * * @param xdata - DoubleBuffer with the x axis data as the values. The x * data can change, just call an update(); * @param ydata - DoubleBuffer with the y axis data as the values. The y * data can change, just call an update(); * @param color - Color thay you want the axis to be. * @param autoscale - Autoscale the graph or go with current max and min. * @param connectLines - Connect the lines for each point or just draw * points. */ public void addData(DoubleBuffer xdata, DoubleBuffer ydata, int color, boolean autoscale, boolean connectLines) { addData(xdata, ydata, Color.getColor("", color), autoscale, connectLines); } public void addData(DoubleBuffer xdata, DoubleBuffer ydata, Color color, boolean autoscale, boolean connectLines) { Series s = new Series(xdata, ydata); if (autoscale) { s.findXMaxMin(); s.findYMaxMin(); } s.dataColor = color; s.connect = connectLines; dataList.add(s); update(); } private void drawBackground() { // if(offscreen == null){ offscreen = createImage(width, height); // } if (offscreen != null) { bufferGraphics = (Graphics2D) offscreen.getGraphics(); bufferGraphics.clearRect(0, 0, width, height); bufferGraphics.setPaintMode(); bufferGraphics.setColor(backgroundColor); bufferGraphics.fillRect(0, 0, width, height); bufferGraphics.setColor(axisColor); if (drawHAxisB) { drawHAxis(); drawHAxisTicks(); } if (drawVAxisB) { drawVAxis(); drawVAxisTicks(); } bufferGraphics.setColor(gridColor); if (drawHGridB) { drawHGrid(); } if (drawVGridB) { drawVGrid(); } for (Series s : dataList) { s.drawPoints(); } drawSelection(); synchronized (lock) { onscreen = offscreen; } } } /** * Causes all autoscales to be recalculated and then forces an update on the * plot. This function is automatically called by most functions such as * setDrawHGrid(), but has to be called if the underlying data to a double * buffer changes. * */ public synchronized void update() { // updateLock = true; doLayout(); repaint(); // updateLock = false; } /** * Causes all autoscales to be recalculated and min/max to be found and then * forces an update on the plot. Same as update except finds new scales * */ public void updateWithRescale() { xAxisMin = Double.MAX_VALUE; yAxisMin = Double.MAX_VALUE; xAxisMax = -Double.MAX_VALUE; yAxisMax = -Double.MAX_VALUE; for (Series s : dataList) { s.findXMaxMin(); s.findYMaxMin(); } update(); } private Point getPoint(double x, double y) { double w = (x - xAxisMin) / (xAxisMax - xAxisMin) * width; double h = (1.0 - (y - yAxisMin) / (yAxisMax - yAxisMin)) * height; // System.out.println("yMin " + yMin + " yMax " + yMax + " Y " + y + " heigth " + h); return new Point((int) w, (int) h); } private double[] getPValue(int x, int y) { double w = ((double) x / width) * (xAxisMax - xAxisMin) + xAxisMin; double h = (1 - (double) y / height) * (yAxisMax - yAxisMin) + yAxisMin; return new double[]{w, h}; } @Override public synchronized void doLayout() { super.doLayout(); height = getHeight(); width = getWidth(); // System.out.println("Height " + height + " Width " + width); drawBackground(); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { setToolTipText(" "); setOpaque(false); addMouseListener(new java.awt.event.MouseAdapter() { public void mousePressed(java.awt.event.MouseEvent evt) { formMousePressed(evt); } public void mouseReleased(java.awt.event.MouseEvent evt) { formMouseReleased(evt); } }); addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { public void mouseDragged(java.awt.event.MouseEvent evt) { formMouseDragged(evt); } public void mouseMoved(java.awt.event.MouseEvent evt) { formMouseMoved(evt); } }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 400, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 300, Short.MAX_VALUE) ); }// </editor-fold>//GEN-END:initComponents // JPL: methods disabled for the MMP_pistl, feel free to uncoment for other usage private void formMouseMoved(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseMoved if (highLight) { double[] pval = getPValue(evt.getX(), evt.getY()); String x = String.format("%.4f", pval[0]); String y = String.format("%.4f", pval[1]); setToolTipText("(" + x + "," + y + ")"); repaint(); } // System.out.println("Point is (" + pval[0] + "," + pval[1] + ") at (" + evt.getX() + "," + evt.getY() + ")"); }//GEN-LAST:event_formMouseMoved private void formMouseDragged(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseDragged // if (highLight) { // double[] pval = getPValue(evt.getX(), evt.getY()); // String x = String.format("%.4f", pval[0]); // String y = String.format("%.4f", pval[1]); // System.out.println("Mouse was dragged (" + x + " , " + y + ")"); selectionEnd = evt.getPoint(); update(); } }//GEN-LAST:event_formMouseDragged private void formMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMousePressed if (highLight) { // double[] pval = getPValue(evt.getX(), evt.getY()); // String x = String.format("%.4f", pval[0]); // String y = String.format("%.4f", pval[1]); // System.out.println("Mouse was pressed at " + "(" + x + " , " + y + ")"); selectionStart = evt.getPoint(); } }//GEN-LAST:event_formMousePressed private void formMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseReleased if (highLight) { ZoomCoordinates oldZoom = new ZoomCoordinates(xAxisMin, xAxisMax, yAxisMin, yAxisMax); if (evt.getButton() == 1) { if (selectionStart != null && selectionEnd != null) { double[] sval = getPValue(selectionStart.x, selectionStart.y); double[] eval = getPValue(selectionEnd.x, selectionEnd.y); double sx = sval[0]; double sy = sval[1]; double ex = eval[0]; double ey = eval[1]; if (sx < ex) { xAxisMin = sx; xAxisMax = ex; } else { xAxisMin = ex; xAxisMax = sx; } if (sy < ey) { yAxisMin = sy; yAxisMax = ey; } else { yAxisMin = ey; yAxisMax = sy; } selectionStart = null; selectionEnd = null; update(); } } else { selectionStart = null; selectionEnd = null; updateWithRescale(); } ZoomCoordinates newZoom = new ZoomCoordinates(xAxisMin, xAxisMax, yAxisMin, yAxisMax); firePropertyChange("Zoom Event", oldZoom, newZoom); } }//GEN-LAST:event_formMouseReleased // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables private class Series { private DoubleBuffer xData; private DoubleBuffer yData; private int[] xPoints = null; private int[] yPoints = null; private double maxX = -Double.MAX_VALUE; private double maxY = -Double.MAX_VALUE; private double minX = Double.MAX_VALUE; private double minY = Double.MAX_VALUE; private boolean connect = true; private Color dataColor = Color.red; private Series(DoubleBuffer ydata) { yData = ydata; xData = null; xPoints = new int[yData.capacity()]; yPoints = new int[yData.capacity()]; } private Series(DoubleBuffer xdata, DoubleBuffer ydata) { xData = xdata; yData = ydata; xPoints = new int[yData.capacity()]; yPoints = new int[yData.capacity()]; } private void findYMaxMin() { yData.rewind(); maxY = -Double.MAX_VALUE; minY = Double.MAX_VALUE; while (yData.hasRemaining()) { double point = yData.get(); if (point > maxY) { maxY = point; } if (point < minY) { minY = point; } } if (maxY > yAxisMax) { if (maxY > 0) { yAxisMax = maxY + maxY * 0.1; } else { yAxisMax = maxY - maxY * 0.1; } } if (minY < yAxisMin) { if (minY > 0) { yAxisMin = minY - minY * 0.1; } else { yAxisMin = minY + minY * 0.1; } } // System.out.println("yAxisMax is " + maxY + " yAxisMin " + minY); } private void findXMaxMin() { if (xData != null) { xData.rewind(); maxX = -Double.MAX_VALUE; minX = Double.MAX_VALUE; while (xData.hasRemaining()) { double point = xData.get(); if (point > maxX) { maxX = point; } if (point < minX) { minX = point; } } } else { minX = 0; maxX = yData.capacity() - 1; } if (maxX > xAxisMax) { xAxisMax = maxX; } if (minX < xAxisMin) { xAxisMin = minX; } } private void drawPoints() { // JPL: no point in computing values we aren't going to display int startx = 0; int endx = yPoints.length; // if(xData != null && (xAxisMin < (Double.MAX_VALUE / 2.0)) && (xAxisMax > (-Double.MAX_VALUE / 2.0))){ // double start = xData.get(0); // double stop = xData.get(yPoints.length - 1); // if(stop > start){ // startx = (int)(yPoints.length * ((xAxisMin - start) / (stop - start))); // endx = (int)(yPoints.length * ((xAxisMax - start) / (stop - start))); // } // } for (int cnt = startx; cnt < endx; cnt++) { if (xData != null) { Point p = getPoint(xData.get(cnt), yData.get(cnt)); // System.out.println("X " + xData.get(cnt) + " Y " + yData.get(cnt) + " Point " + p); xPoints[cnt] = p.x; yPoints[cnt] = p.y; } else { Point p = getPoint(cnt, yData.get(cnt)); // System.out.println("X " + cnt + " Y " + yData.get(cnt) + " Point " + p); xPoints[cnt] = p.x; yPoints[cnt] = p.y; } } bufferGraphics.setColor(dataColor); Stroke oldStroke = bufferGraphics.getStroke(); Stroke stroke = new BasicStroke(1.5f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f); bufferGraphics.setStroke(stroke); if (connect) { bufferGraphics.drawPolyline(xPoints, yPoints, xPoints.length); } else { for (int cnt = 0; cnt < xPoints.length; cnt++) { bufferGraphics.drawOval(xPoints[cnt], yPoints[cnt], 2, 2); } } bufferGraphics.setStroke(oldStroke); } } }