/** * Jeff Rupp * Master's Thesis * 2005 * * This class is a simple container of a description of a line to draw * * To use set up the data points, pick a color, and label * The canvas that uses this class will set the max/min as required * * The x,y data is saved to file as each point is added. The file name is set when the * x axis label is set to something non-zero in length. * */ package jdr.utils; import java.awt.*; import java.util.*; import java.io.*; import org.apache.log4j.*; public class LineInfo { private static Logger m_logger = Logger.getLogger(LineInfo.class); // vector of FloatPoint's X,Y private SortedSet m_points = Collections.synchronizedSortedSet(new TreeSet()); // user can provide a min/max for X & Y axis to get several lines on the same scale private float m_minX = Float.MAX_VALUE; private float m_minXext = Float.MAX_VALUE; private float m_maxX = Float.MIN_VALUE; private float m_maxXext = Float.MIN_VALUE; private float m_minY = Float.MAX_VALUE; private float m_minYext = Float.MAX_VALUE; private float m_maxY = Float.MIN_VALUE; private float m_maxYext = Float.MIN_VALUE; private static FloatPoint s_labelLocationOffset = new FloatPoint(0,0); private static final float s_xlabelOffsetInc = 160; private static final float s_ylabelOffsetInc = 35; private FloatPoint m_xLabelLocationOffset = null; private FloatPoint m_xLabelLocation = new FloatPoint(0, 0); private FloatPoint m_yLabelLocationOffset = null; private FloatPoint m_yLabelLocation = new FloatPoint(0, 0); private String m_xLabel = " "; private String m_yLabel = " "; private java.text.DecimalFormat m_yFormat = new java.text.DecimalFormat("###.000E00"); private Color m_color = Color.blue; // each instance of this class will get a different default color private static final int COLOR_BASE = 0x557733; private static final int COLOR_INC = 0x203030; private static int s_lineColorBase = COLOR_BASE; private int m_thisLineColor = 0; private boolean m_doDashedLine = false; private Stroke m_thisLineStroke; private static int s_strokeIndex = 0; private Stroke [] m_baseStrokeArray = null; private java.lang.Object m_baseStrokeArrayLock = new java.lang.Object(); private boolean m_drawInBlackAndWhite = false; private Component m_containingComponent = null; private java.lang.Object m_fileLock = new java.lang.Object(); private BufferedOutputStream m_dataFile = null; private int m_recordedPointCount = 0; /** * contains points, and draw method for a 2D line */ public LineInfo() { this(false); } public LineInfo(boolean blackAndWhite) { m_drawInBlackAndWhite = blackAndWhite; m_thisLineColor = s_lineColorBase; s_lineColorBase += COLOR_INC; if(m_drawInBlackAndWhite) { m_doDashedLine = true; m_color = Color.black; } else { // switch to dashed line when color wraps m_doDashedLine = (s_lineColorBase < m_thisLineColor); m_color = new Color(m_thisLineColor); } m_xLabelLocationOffset = new FloatPoint(s_labelLocationOffset.getX(), 0.0F); m_yLabelLocationOffset = new FloatPoint(0.0F, s_labelLocationOffset.getY()); s_labelLocationOffset = new FloatPoint(s_labelLocationOffset.getX()+s_xlabelOffsetInc, s_labelLocationOffset.getY()+s_ylabelOffsetInc); setXLabelBase(new FloatPoint(0,0)); setYLabelBase(new FloatPoint(0,0)); // populate the array of strokes synchronized(m_baseStrokeArrayLock) { if(m_baseStrokeArray == null) { m_baseStrokeArray = new Stroke[6]; float [] dashPattern = new float[1]; dashPattern[0] = 2.0F; m_baseStrokeArray[0] = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); dashPattern = new float[2]; dashPattern[0] = 3.0F; dashPattern[1] = 3.0F; m_baseStrokeArray[1] = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); dashPattern = new float[2]; dashPattern[0] = 6.0F; dashPattern[1] = 6.0F; m_baseStrokeArray[2] = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); dashPattern = new float[2]; dashPattern[0] = 2.0F; dashPattern[1] = 6.0F; m_baseStrokeArray[3] = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); dashPattern = new float[3]; dashPattern[0] = 2.0F; dashPattern[1] = 2.0F; dashPattern[2] = 6.0F; m_baseStrokeArray[4] = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); dashPattern = new float[3]; dashPattern[0] = 2.0F; dashPattern[1] = 6.0F; dashPattern[2] = 6.0F; m_baseStrokeArray[5] = new BasicStroke(2.0F, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0F, dashPattern, 2.0F); } m_thisLineStroke = m_baseStrokeArray[s_strokeIndex % m_baseStrokeArray.length]; ++s_strokeIndex; } } /** * user calls this method to get the line onto a graph * * @param graphics the graphics context to draw to * @param fullBounds the rectangle to restrict the labels to * @param trimmedBounds the rectangle to restrict the lines to. */ public void drawLine(Graphics2D graphics, Rectangle fullBounds, Rectangle trimmedBounds) { // if the min/max aren't set scale the points to occupy the whole screen double minx = m_minX; double maxx = m_maxX; double miny = m_minY; double maxy = m_maxY; double x = 0.0; double y = 0.0; findMinMax(); if((m_minXext != Float.MAX_VALUE) && (m_maxXext != Float.MIN_VALUE)) { minx = m_minXext; maxx = m_maxXext; } if((m_minYext != Float.MAX_VALUE) && (m_maxYext != Float.MIN_VALUE)) { miny = m_minYext; maxy = m_maxYext; } String logStr = "Drawing line, fullBounds (" + fullBounds.width+", " + fullBounds.height+") "+ " trimmedBounds (" + trimmedBounds.width+", " + trimmedBounds.height+") \r\n"; // draw the axis labels // X axis graphics.setColor(m_color); if((m_xLabel.length() > 0) && (m_xLabelLocation != null)) { float xlblx = m_xLabelLocation.getX() + 5; float xlbly = fullBounds.height - DataGraph.LABEL_HEIGHT + 15 + m_xLabelLocation.getY(); logStr = logStr + "x("+xlblx+","+xlbly+"): "+m_xLabel; graphics.drawString(m_xLabel + " ("+minx+", "+maxx+")", xlblx, xlbly); } else { logStr = logStr + " x label not configured, len: "+m_xLabel.length()+ " location: "+m_xLabelLocation; } // Y axis if((m_yLabel.length() > 0) && (m_yLabelLocation != null)) { float ylblx = fullBounds.width - DataGraph.LABEL_WIDTH + 5 + m_yLabelLocation.getX(); float ylbly = m_yLabelLocation.getY() + 50; logStr = logStr + " y("+ylblx+","+ylbly+"): "+m_yLabel; int yOffset = 0; if(m_drawInBlackAndWhite) { Stroke currentStroke = graphics.getStroke(); if(m_doDashedLine) { graphics.setStroke(m_thisLineStroke); } // draw the line type in the key graphics.drawLine((int)ylblx, (int)ylbly, (int)ylblx+60, (int)ylbly); yOffset = 10; graphics.setStroke(currentStroke); } graphics.drawString(m_yLabel, ylblx, ylbly + yOffset); graphics.drawString("("+m_yFormat.format(miny)+",", ylblx, ylbly + 10 + yOffset); graphics.drawString(m_yFormat.format(maxy)+")", ylblx, ylbly + 20 + yOffset); } else { logStr = logStr + " y label not configured, len: "+m_yLabel.length()+ " location: "+m_yLabelLocation; } m_logger.debug(logStr); // scale the min and max to the graphable area double xScale = (maxx - minx) / (trimmedBounds.width * 0.95); double yScale = (maxy - miny) / (trimmedBounds.height * 0.95); xScale = (xScale == 0)?1:xScale; yScale = (yScale == 0)?1:yScale; m_logger.debug("minx: "+minx+" maxx: "+maxx+" miny: "+miny+" maxy: "+maxy); m_logger.debug("xScale: "+xScale+ " yScale: "+yScale); m_logger.debug("scaled minx: "+minx/xScale+" scaled maxx: "+maxx/xScale+ " scaled miny: "+miny/yScale+" scaled maxy: "+maxy/yScale); m_logger.debug("width: "+trimmedBounds.width+ " height: "+trimmedBounds.height); Stroke currentStroke = graphics.getStroke(); if(m_doDashedLine) { graphics.setStroke(m_thisLineStroke); } // draw the line int[] xPoints = new int[0]; int[] yPoints = new int[0]; synchronized(m_points) { // since we have the points stored in a sorted set, based on X we just draw // create arrays of x points and y points so we can use poly line xPoints = new int[m_points.size()]; yPoints = new int[m_points.size()]; int ptCnt = 0; Iterator iter = m_points.iterator(); while(iter.hasNext()) { FloatPoint pt = ((FloatPoint)iter.next()); xPoints[ptCnt] = (int)((pt.getX() - minx) / xScale); // have to flip the Y values since 0,0 is upper left corner yPoints[ptCnt] = trimmedBounds.height - (int)((pt.getY() - miny) / yScale); //m_logger.info("x: "+xPoints[ptCnt] + " y: "+yPoints[ptCnt]); // put a circle at each point graphics.fillOval(xPoints[ptCnt]-3, yPoints[ptCnt]-3, 6, 6); ++ptCnt; } } graphics.drawPolyline(xPoints, yPoints, xPoints.length); if(m_doDashedLine) { graphics.setStroke(currentStroke); } } /** * add a single point to the line */ public void addPoint(FloatPoint point) { int numPointsTotal = 0; synchronized(m_points) { m_points.add(point); numPointsTotal = m_points.size(); float x; float y; x = point.getX(); y = point.getY(); if(x < m_minX) { m_minX = x; } if(x > m_maxX) { m_maxX = x; } if(y < m_minY) { m_minY = y; } if(y > m_maxY) { m_maxY = y; } //m_logger.info("added point x: "+x+" y: "+y+" minx: "+m_minX+" maxx: "+m_maxX+" miny: "+m_minY+" maxy: "+m_maxY); } synchronized(m_fileLock) { if(m_dataFile != null) { try { if(m_recordedPointCount == 0) // haven't recorded any points yet { synchronized(m_points) { Iterator iter = m_points.iterator(); while(iter.hasNext()) { FloatPoint pt = ((FloatPoint)iter.next()); String writeValue = Float.toString(pt.getX()) +", "+ Float.toString(pt.getY()) +"\n"; m_dataFile.write(writeValue.getBytes()); ++m_recordedPointCount; } } } else { String writeValue = Float.toString(point.getX()) +", "+ Float.toString(point.getY()) +"\n"; m_dataFile.write(writeValue.getBytes()); ++m_recordedPointCount; } } catch(IOException ioex) { javax.swing.JOptionPane.showMessageDialog(null, "Could not write data point: "+ point.toString(), "Could Not Write Data", javax.swing.JOptionPane.ERROR_MESSAGE); } } } // need to be able to call repaint on add a point... if(m_containingComponent != null) { m_containingComponent.repaint(); if(m_containingComponent instanceof DataGraph) { ((DataGraph)m_containingComponent).reDraw(); } } } /** * clear out all the points */ public void clearPoints() { synchronized(m_fileLock) { if(m_dataFile != null) { try { m_dataFile.close(); } catch(IOException ioex) { javax.swing.JOptionPane.showMessageDialog(null, "Could not close file: "+ m_dataFile.toString(), "Could Not close Data File", javax.swing.JOptionPane.ERROR_MESSAGE); } } m_dataFile = null; m_recordedPointCount = 0; } synchronized(m_points) { m_points.clear(); m_minX = Float.MAX_VALUE; m_maxX = Float.MIN_VALUE; m_minY = Float.MAX_VALUE; m_maxY = Float.MIN_VALUE; } } /** * set all the points in one call * * @param points a vector of type FloatPoint */ public void setPoints(SortedSet points) { synchronized(m_points) { m_points = points; findMinMax(); } } public void setPoints(Collection points) { synchronized(m_points) { m_points.clear(); m_points.addAll(points); findMinMax(); } } /** * find the minimum and maximum X & Y values */ protected void findMinMax() { synchronized(m_points) { float x; float y; m_minX = Float.MAX_VALUE; m_maxX = Float.MIN_VALUE; m_minY = Float.MAX_VALUE; m_maxY = Float.MIN_VALUE; // iterate through all points and determine min/max Iterator iter = m_points.iterator(); while(iter.hasNext()) { FloatPoint pt = (FloatPoint) iter.next(); x = pt.getX(); y = pt.getY(); if(x < m_minX) { m_minX = x; } if(x > m_maxX) { m_maxX = x; } if(y < m_minY) { m_minY = y; } if(y > m_maxY) { m_maxY = y; } } } } /** * remove a single point from the line */ public void removePoint(FloatPoint point) { synchronized(m_points) { m_points.remove(point); double x; double y; x = point.getX(); y = point.getY(); if((x == m_minX) || (x == m_maxX) || (y == m_minY) || (y == m_maxY)) { findMinMax(); } } } /** * lock this graph to the given min and max for the X axis */ public void setXAxisMinMax(float min, float max) { m_minXext = min; m_maxXext = max; } public float getXMin() { return m_minX; } public float getXMax() { return m_maxX; } /** * lock this graph to the given min and max for the Y axis */ public void setYAxisMinMax(float min, float max) { m_minYext = min; m_maxYext = max; } public float getYMin() { return m_minY; } public float getYMax() { return m_maxY; } /** * establish an alternative color for the line and labels */ public void setColor(java.awt.Color color) { m_color = color; } public Color getColor() { return m_color; } /** * establish the base location for the X axis label */ public void setXLabelBase(FloatPoint base) { m_xLabelLocation.setLocation(base.getX() + m_xLabelLocationOffset.getX(), base.getY() + m_xLabelLocationOffset.getY()); } /** * establish the base location for the Y axis label */ public void setYLabelBase(FloatPoint base) { m_yLabelLocation.setLocation(base.getX() + m_yLabelLocationOffset.getX(), base.getY() + m_yLabelLocationOffset.getY()); } /** * set the labels for the X and Y axis */ public void setLabels(String xlbl, String ylbl) { m_xLabel = xlbl; m_yLabel = ylbl; if(m_xLabel.length() > 0) { synchronized(m_fileLock) { // if x label length is non-zero for the first time, start the data file if(m_dataFile == null) { String filename = ""; try { String currentPath = System.getProperty("user.dir", "./"); currentPath = currentPath + File.separator + "dataFiles"; File currentDir = new File(currentPath); if(!currentDir.exists()) { currentDir.mkdir(); } File newFile = File.createTempFile(m_xLabel+"_"+m_yLabel+"_", ".csv", currentDir); filename = newFile.getAbsolutePath(); m_logger.debug("Created data file: " + filename); FileOutputStream fos = new FileOutputStream(newFile); m_dataFile = new BufferedOutputStream(fos); m_recordedPointCount = 0; } catch(IOException ioex) { javax.swing.JOptionPane.showMessageDialog(null, "Could not open file: "+ filename, "Could Not Open Data File", javax.swing.JOptionPane.ERROR_MESSAGE); } } } } } public String getXLabel() { return m_xLabel; } public String getYLabel() { return m_yLabel; } public void setLineTypeDashed(boolean dashLine) { m_doDashedLine = dashLine; } public boolean getLineTypeIsDashed() { return m_doDashedLine; } public void setContainingComponent(Component comp) { m_containingComponent = comp; } public void SetModeToBlackWhite(boolean blackWhite) { m_drawInBlackAndWhite = blackWhite; if(m_drawInBlackAndWhite) { m_doDashedLine = true; m_color = Color.black; } else { // switch to dashed line when color wraps m_doDashedLine = (s_lineColorBase < m_thisLineColor); m_color = new Color(m_thisLineColor); } } public void Closing() { synchronized(m_fileLock) { if(m_dataFile != null) { try { m_dataFile.close(); m_logger.debug("Closing data file"); } catch(IOException ioex) { } } } } } // end class definition