package com.limegroup.gnutella.gui.statistics.panes; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.text.NumberFormat; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.JComponent; import com.limegroup.gnutella.gui.GUIMediator; import com.limegroup.gnutella.statistics.NumericalStatistic; import com.limegroup.gnutella.statistics.Statistic; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.IntBuffer; /** * This class handles painting the graph, including updating the data * for the given statistic. It offloads the painting of the axis to * another class. * * @see GraphAxisData * @see GraphBorder */ final class GraphPainter extends JComponent implements ComponentListener, StatisticDisplayer { /** * Variable for the x coordinates for a graph. */ private final int[] X_COORDS = new int[Statistic.HISTORY_LENGTH]; /** * The x and y axis for this graph. */ private GraphBorder _border; /** * <tt>List</tt> of all statistics displayed for this window. */ private final List LIST = new LinkedList(); /** * The current color index to use. */ private static int _curColor = 0; /** * The colors used in the graph sequentially. As each new data item * is added, it is given a different color. */ private final Color[] GRAPH_COLORS = { // TODO: white is problematic on OS X -- put this in themes? // usually play white, but black on OSX - this really should be in // themes CommonUtils.isMacOSX() ? new Color(0,0,0) : new Color(255, 255, 255), new Color( 0, 153, 0), //medium green new Color(255, 102, 255), //medium pink new Color(255, 255, 0), //plain yellow new Color(255, 51, 0), //light red new Color( 51, 153, 204), //medium cyan new Color(255, 153, 0), //medium orange new Color(153, 0, 51), //dark red new Color( 51, 0, 153), //dark blue }; /** * Default graph scale data for the x and y axis. */ private static GraphAxisData DEFAULT_AXIS_DATA = new GraphAxisData(); /** * The actual <tt>GraphAxisData</tt> for this graph instance. */ private final GraphAxisData AXIS_DATA; /** * String for the name column label of the legend. */ private static final String NAME = GUIMediator.getStringResource("STATS_LEGEND_NAME"); /** * String for the current value column label of the legend. */ private static final String CURRENT = GUIMediator.getStringResource("STATS_LEGEND_CURRENT"); /** * String for the average value column label of the legend. */ private static final String AVERAGE = GUIMediator.getStringResource("STATS_LEGEND_AVERAGE"); /** * String for the max value column label of the legend. */ private static final String MAX = GUIMediator.getStringResource("STATS_LEGEND_MAX"); /** * String for the total messages column label of the legend. */ private static final String TOTAL = GUIMediator.getStringResource("STATS_LEGEND_TOTAL"); /** * String for the colorcolumn label of the legend. */ private static final String COLOR = GUIMediator.getStringResource("STATS_LEGEND_COLOR"); /** * Constant <tt>NumberFormat</tt> to display floating point numbers */ private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(); private double _yPixelFactor = 0; /** * Constructs a new <tt>GraphPainter</tt> instance with default settings. */ GraphPainter() { this(DEFAULT_AXIS_DATA); } /** * Creates a new <tt>GraphPainter</tt> with data from the specified * <tt>GraphAxisData</tt>. * * @param data the <tt>GraphAxisData</tt> instance with data for * the graph */ GraphPainter(GraphAxisData data) { AXIS_DATA = data; this.addComponentListener(this); } // implements StatisticDisplayer -- inherit doc comment public void registerStatistic(Statistic stat, String displayName) { StatHandler handler = new StatHandler(stat, GRAPH_COLORS[_curColor], displayName); LIST.add(handler); if(_curColor != (GRAPH_COLORS.length - 1)) _curColor++; else _curColor = 0; if(!(stat instanceof NumericalStatistic) && _border == null) { _border = new GraphBorder(AXIS_DATA); setBorder(_border); } } // implements StatisticDisplayer -- inherit doc comment public void registerDualStatistic(Statistic stat0, Statistic stat1, String displayName) { } // implements StatisticDisplayer -- inherit doc comment public void refresh() { repaint(); } // implements StatisticDisplayer -- inherit doc comment public JComponent getComponent() { return this; } /** * Notifies all <tt>Statistic</tt> instances for this graph that * data should be written out to a file. * * @param write specifies whether or not to write data to a file */ public void setWriteStatsToFile(boolean write) { synchronized(LIST) { Iterator iter = LIST.iterator(); while(iter.hasNext()) { Statistic curStat = ((StatHandler)iter.next()).getStat(); curStat.setWriteStatToFile(write); } } } private static final int NAME_COLUMN_WIDTH = 160; private static final int CURRENT_COLUMN_WIDTH = 54; private static final int AVERAGE_COLUMN_WIDTH = 54; private static final int MAX_COLUMN_WIDTH = 54; private static final int TOTAL_COLUMN_WIDTH = 70; private static final int COLOR_COLUMN_WIDTH = 44; private static final int RECT_WIDTH = NAME_COLUMN_WIDTH + CURRENT_COLUMN_WIDTH + AVERAGE_COLUMN_WIDTH + MAX_COLUMN_WIDTH + TOTAL_COLUMN_WIDTH + COLOR_COLUMN_WIDTH; private static final int COLOR_BOX_WIDTH = 10; /** * Paints the graph, including the legend. */ protected void paintComponent(Graphics g) { paintGraph(g); } private void paintGraph(Graphics g) { super.paintComponent(g); final Color oldColor = g.getColor(); final Font oldFont = g.getFont(); final Font tempFont = new Font(oldFont.getName(), Font.PLAIN, oldFont.getSize() - 2); g.setFont(tempFont); final FontMetrics m = g.getFontMetrics(); final int rowHeight = m.getHeight() + 1; final int rowBase = m.getLeading() / 2 + m.getMaxAscent(); final int yBoxColor = (rowHeight - COLOR_BOX_WIDTH) / 2; final int yOrig = 0; final int xName = getWidth() - RECT_WIDTH - 10; final int xCurrent = xName + NAME_COLUMN_WIDTH; final int xAverage = xCurrent + CURRENT_COLUMN_WIDTH; final int xMax = xAverage + AVERAGE_COLUMN_WIDTH; final int xTotal = xMax + MAX_COLUMN_WIDTH; final int xColor = xTotal + TOTAL_COLUMN_WIDTH; final int margin = 3; final int xBoxColor = xColor +(COLOR_COLUMN_WIDTH - COLOR_BOX_WIDTH) / 2; int y = yOrig; g.drawString(NAME, xName + margin, y + rowBase); g.drawString(CURRENT, xCurrent + margin, y + rowBase); g.drawString(AVERAGE, xAverage + margin, y + rowBase); g.drawString(MAX, xMax + margin, y + rowBase); g.drawString(TOTAL, xTotal + margin, y + rowBase); g.drawString(COLOR, xColor + margin, y + rowBase); y += rowHeight; g.drawLine(xName, y, xName + RECT_WIDTH, y); y++; // loop through all of the statistics, displaying their graphs // and their legends final Iterator iter = LIST.iterator(); while (iter.hasNext()) { final StatHandler handler = (StatHandler)iter.next(); final Statistic stat = handler.getStat(); final int[] Y_COORDS = handler.getData(); // fill up the coords with the correct positions final IntBuffer buf = stat.getStatHistory(); g.setColor(handler.getColor()); // fill up the coords with the correct positions if(!(stat instanceof NumericalStatistic)) { synchronized(buf) { for (int j = 0; j < buf.size(); j++) { int yVal = (int)( buf.get(j) * _yPixelFactor / AXIS_DATA.getYScale()); Y_COORDS[j] = getHeight() - GraphBorder.getOffset() - yVal; } } g.drawPolyline(X_COORDS, Y_COORDS, Y_COORDS.length); } String current; synchronized(buf) { // Set the current value to be the last one in the buffer. current = NUMBER_FORMAT.format((double)buf.get(Y_COORDS.length-1) / AXIS_DATA.getYScale()); } // draw an individual legend entry g.fillRect(xBoxColor, y + yBoxColor, COLOR_BOX_WIDTH, COLOR_BOX_WIDTH); g.setColor(oldColor); g.drawRect(xBoxColor - 1, y + yBoxColor - 1, COLOR_BOX_WIDTH + 1, COLOR_BOX_WIDTH + 1); final String name = handler.getDisplayName(); NUMBER_FORMAT.setMaximumFractionDigits(3); final String average = NUMBER_FORMAT.format(stat.getAverage()); final String max = NUMBER_FORMAT.format(stat.getMax()); NUMBER_FORMAT.setMaximumFractionDigits(1); final String total = NUMBER_FORMAT.format(stat.getTotal()); g.drawString(name, xName + margin, y + rowBase); g.drawString(current, xCurrent + margin, y + rowBase); g.drawString(average, xAverage + margin, y + rowBase); g.drawString(max, xMax + margin, y + rowBase); g.drawString(total, xTotal + margin, y + rowBase); y += rowHeight; } // draw the outside of the legend g.drawRect(xName, yOrig, RECT_WIDTH, y - yOrig); g.drawLine(xCurrent, yOrig, xCurrent, y); g.drawLine(xAverage, yOrig, xAverage, y); g.drawLine(xMax, yOrig, xMax, y); g.drawLine(xTotal, yOrig, xTotal, y); g.drawLine(xColor, yOrig, xColor, y); g.setFont(oldFont); } // implements the ComponentListener interface public void componentResized(ComponentEvent e) { double xPixelFactor = (double)(getWidth() - GraphBorder.getOffset() - 30) / (double)AXIS_DATA.getTotalXValues(); double curCoord = (double)GraphBorder.getOffset(); for(int i = 0; i < X_COORDS.length; i++, curCoord += xPixelFactor) { X_COORDS[i] = (int)curCoord; } _yPixelFactor = (double)(getHeight() - GraphBorder.getOffset() - 60) / (double)AXIS_DATA.getTotalYValues(); } // implements the ComponentListener interface public void componentShown(ComponentEvent e) {} // implements the ComponentListener interface public void componentHidden(ComponentEvent e) {} // implements the ComponentListener interface public void componentMoved(ComponentEvent e) {} }