home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgLangD.iso / VCAFE.3.0A / Main.bin / JChart.java < prev    next >
Text File  |  1998-12-04  |  38KB  |  1,118 lines

  1. package com.symantec.itools.swing;
  2.  
  3. import com.sun.java.swing.*;
  4. import com.sun.java.swing.event.*;
  5. import com.sun.java.swing.table.*;
  6. import com.sun.java.swing.border.*;
  7. import java.beans.*;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.util.*;
  11. import java.text.NumberFormat;
  12. import java.text.DecimalFormat;
  13.  
  14. // ===================================================================================
  15. // = Revision History
  16. // ===================================================================================
  17.  
  18. //  09/25/98    MSH Added resource bundle for localization
  19. //  09/29/98    MSH    Added properties yAxisMin, yAxisMax
  20. //  09/30/98    MSH Re-wrote calibrateGraph() to improve support for negative numbers,
  21. //                  and to add support for yAxisMin, yAxisMax
  22. //  10/21/98    MSH Worked around JIT problem in form designer #67321
  23. //  12/04/98    VJ  Changes to handle formatted decimal data in getSeriesValue()
  24.  
  25.  
  26. /**
  27.  * Simple charting component.  Creates a chart capable of displaying one or more series
  28.  * of data in the form of a bar, line, scatter, pie, or area chart.
  29.  *
  30.  * The chart uses a standard TableModel interface as the data model type. The TableModel can
  31.  * use several types including: Integer, Double, Long, String, and DataElem
  32.  *
  33.  * @version 1.0, July 28, 1998
  34.  *
  35.  * @author  Michael Hopkins, Symantec
  36.  */
  37. public class JChart extends com.sun.java.swing.JComponent implements GraphStyle, TableModelListener
  38. {
  39.     /*** component parameters ***/
  40.     protected TableModel model;
  41.     protected int        graphStyle;      // the style of the graph
  42.     protected boolean    showLegend;      // whether legend should be displayed
  43.     protected boolean    showGrid;        // true if a grid should be drawn on the graph
  44.     protected String     title;           // title of the graph
  45.     protected int        precision;       // number of digits of precision to display for data
  46.     protected int        numTicksY;       // the number of ticks on the y axis
  47.     protected double     yAxisMin;        // the preferred starting point of the y axis
  48.     protected double     yAxisMax;        // the preferred maximum value on the y axis
  49.  
  50.     /*** internal data members ***/
  51.     protected double  graphMin;           // the smallest value in the data set
  52.     protected double  graphMax;           // the largest value in the data set
  53.     protected double  graphUpper;         // the upper bounds of the graph on the y axis
  54.     protected double  graphLower;         // the lower bounds of the graph on the y axis
  55.     protected int     graphCols;          // the size of the largest series (number of entries)
  56.     protected int     gridIncrementV;     // the number of pixels between major vertical subdivisions on the graph
  57.     protected int     gridIncrementH;     // the number of pixels between major horizontal subdivisions on the graph
  58.     protected double  gridStepV;          // the value per vertical tick on the y axis
  59.     protected int     labelYInset;        // additional inset on Y axis for labels
  60.     protected int     titleBorder;        // the space above the graph occupied by the title
  61.     protected int     legendBorder;       // the border
  62.     protected Insets  graphBorder;        // the border around the graph
  63.  
  64.     protected boolean debug;
  65.  
  66.     public static final double AUTO_CALCULATE_MIN = Double.NEGATIVE_INFINITY;
  67.     public static final double AUTO_CALCULATE_MAX = Double.POSITIVE_INFINITY;
  68.  
  69.     public JChart()
  70.     {
  71.         model = new  DefaultTableModel();
  72.         graphStyle = DEFAULT_STYLE;
  73.         showLegend = true;
  74.         showGrid = true;
  75.         title = "";
  76.         graphBorder = new Insets( 10, 5, 5, 5 );
  77.         precision = 0;
  78.         numTicksY = 5;
  79.  
  80.         graphMin = Double.MAX_VALUE;
  81.         graphMax = 0;
  82.         graphCols = 0;
  83.         gridIncrementV = 0;
  84.         gridIncrementH = 0;
  85.         gridStepV = 0;
  86.         labelYInset = 0;
  87.         legendBorder = 0;
  88.         yAxisMin = 0.0;
  89.         yAxisMax = AUTO_CALCULATE_MAX;
  90.         titleBorder = 0;
  91.  
  92.         debug = false;
  93.     }
  94.  
  95.     /**
  96.      * Gets the current model used by the chart
  97.      * @return the model
  98.      * @see #setModel
  99.      */
  100.     public TableModel getModel()
  101.     {
  102.         return model;
  103.     }
  104.  
  105.     /**
  106.      * Sets the current model used by the chart. The model is a TableModel derrivative, and can
  107.      *  be composed of objects of type Integer, Long, Double, String, or DataElem
  108.      * @param newModel the TabelModel to be used by the chart
  109.      * @exception IllegalArgumentException
  110.      * if the specified model is null
  111.      * @see #getModel
  112.      */
  113.     public void setModel( TableModel newModel )
  114.     {
  115.         TableModel oldModel = model;
  116.  
  117.         if (newModel == null)
  118.             throw new IllegalArgumentException("Cannot set a null TableModel");
  119.         if (newModel != oldModel)
  120.         {
  121.            if (oldModel != null)
  122.                 oldModel.removeTableModelListener(this);
  123.             model = newModel;
  124.             newModel.addTableModelListener(this);
  125.         }
  126.         graphCols = model.getRowCount();
  127.         graphMax = seriesMax();
  128.         graphMin = seriesMin();
  129.         repaint();
  130.     }
  131.  
  132.     /**
  133.      * Responds to notification of changes in the model data
  134.      * @param e the event to process
  135.      */
  136.     public void tableChanged(TableModelEvent e)
  137.     {
  138.         graphCols = model.getRowCount();
  139.         graphMax = seriesMax();
  140.         graphMin = seriesMin();
  141.         repaint();
  142.     }
  143.  
  144.     /**
  145.      * Get the preferred size of the component
  146.      * @return the dimension of the component
  147.      */
  148.     public Dimension getPreferredSize( )
  149.     {
  150.         Dimension d = new Dimension( 200, 150 );
  151.         return d;
  152.     }
  153.  
  154.     /**
  155.      * Get the minimum size of the component
  156.      * @return the minimum dimension of the component
  157.      */
  158.     public Dimension getMinimumSize()
  159.     {
  160.        Dimension d = new Dimension( 100, 75 );
  161.        return d;
  162.     }
  163.  
  164.     /**
  165.      * Sets the border of the component
  166.      */
  167.     public void setBorder( Border border )
  168.     {
  169.         super.setBorder( border );
  170.  
  171.         Insets i = getInsets();
  172.         graphBorder.top = 10 + i.top;
  173.         graphBorder.bottom = 5 + i.bottom;
  174.         graphBorder.left = 5 + i.left;
  175.         graphBorder.right = 5 + i.right;
  176.  
  177.         repaint();
  178.     }
  179.  
  180.   /**
  181.      * Draws the contents of the chart component
  182.      * @param g the graphics object to be referenced for drawing
  183.      */
  184.     public void paintComponent(java.awt.Graphics g)
  185.     {
  186.         TableModel model = getModel();
  187.         Dimension d = getSize();
  188.  
  189.         FontMetrics fm = g.getFontMetrics();
  190.  
  191.         if ( title.length() != 0 )
  192.             titleBorder = fm.getHeight() + 4;
  193.         else
  194.             titleBorder = 0;
  195.  
  196.         Insets i = getInsets();
  197.         graphBorder.top    = ((g.getFontMetrics().getHeight())/2) + i.top + titleBorder;
  198.         graphBorder.bottom = 5  + i.bottom;
  199.         graphBorder.left   = 5  + i.left;
  200.         graphBorder.right  = 5  + i.right;
  201.  
  202.         Color oldColor = g.getColor();
  203.         g.setColor( getBackground() );
  204.  
  205.         if ( graphStyle != PIE_STYLE )
  206.             g.fillRect( graphBorder.left + labelYInset, graphBorder.top,
  207.                         d.width - graphBorder.left - labelYInset - graphBorder.right - legendBorder ,
  208.                         d.height - graphBorder.bottom - graphBorder.top );
  209.         g.setColor( oldColor );
  210.  
  211.         calcLabelInset( g );        // calculations the position and dimensions of the legend
  212.         if ( showLegend == true && graphStyle != PIE_STYLE )
  213.             drawLegend( g );
  214.         drawTitle( g );
  215.         calibrateGraph( );          // determines the scale of the horizontal and vertical graph subdivisions
  216.  
  217.         drawGrid( g );
  218.                 // plot data
  219.         if ( graphStyle < PIE_STYLE ) // draw all types except pie charts and areas
  220.         {
  221.            int count = getNumSeries();      // the number of series to draw
  222.            for ( int k = 0; k < count; k++ )// draw each series
  223.            {
  224.               int tickPosH = graphBorder.left + labelYInset;    // the horizontal location of the series marker
  225.               int startPos = tickPosH;
  226.               g.setColor( getSeriesColor( k ));                 // draw in the current series color
  227.               int lastValue = 0;
  228.  
  229.               for ( int j = 0; j < getSeriesSize( k ); j++ )
  230.               {
  231.                 double  data  = getSeriesValue( k, j ) - graphLower;
  232.                 int     plotV = (int)(((data / gridStepV) * gridIncrementV) + .5 );
  233.  
  234.                 if ( count > 0 )
  235.                 {
  236.                     switch ( graphStyle )
  237.                     {
  238.                         case LINE_STYLE:
  239.                             if ( j > 0 )
  240.                                g.drawLine( startPos + (j-1) * gridIncrementH, d.height - lastValue - graphBorder.bottom  + 1,
  241.                                            startPos + j * gridIncrementH, d.height - plotV - graphBorder.bottom  +1 );
  242.  
  243.                         case SCATTER_STYLE:
  244.                             g.fillOval( tickPosH - 2,  d.height - plotV - graphBorder.bottom , 4, 4 );
  245.                             break;
  246.                         case BAR_STYLE:
  247.                             g.fillRect( tickPosH + k * (gridIncrementH / count)+2, d.height - plotV - graphBorder.bottom ,
  248.                                         (gridIncrementH / count) - 2, plotV );
  249.                             break;
  250.                         default:
  251.                             return;
  252.                     }
  253.                     lastValue = plotV;
  254.                     tickPosH += gridIncrementH;
  255.                 }
  256.               }
  257.            }
  258.         }
  259.         else
  260.             if ( graphStyle == AREA_STYLE )
  261.                 drawAreaGraph( g );
  262.             else
  263.                 drawPieGraph( g );
  264.  
  265.         if ( graphStyle != PIE_STYLE )
  266.             drawAxis( g );
  267.  
  268.         if ( Beans.isDesignTime() ) // work around for JIT problem
  269.             drawTitle( g );         // applet will work fine in browser
  270.     }
  271.  
  272.     /********** Property Accessor Routines **********/
  273.  
  274.     /**
  275.      * Gets the new graph style.
  276.      * @return the new border style, one of DEFAULT_STYLE, LINE_STYLE,
  277.      *    BAR_STYLE, PIE_STYLE, AREA_STYLE, or SCATTER_STYLE
  278.      * @see #setStyle
  279.      * @see GraphStyle#DEFAULT_STYLE
  280.      * @see GraphStyle#LINE_STYLE
  281.      * @see GraphStyle#BAR_STYLE
  282.      * @see GraphStyle#PIE_STYLE
  283.      * @see GraphStyle#AREA_STYLE
  284.      * @see GraphStyle#SCATTER_STYLE
  285.      */
  286.     public int getStyle()
  287.     {
  288.         return graphStyle;
  289.     }
  290.  
  291.     /**
  292.      * Sets the new graph style.
  293.      * @param style the new border style, one of DEFAULT_STYLE, LINE_STYLE,
  294.      *    BAR_STYLE, PIE_STYLE, AREA_STYLE, or SCATTER_STYLE
  295.      * @see #getStyle
  296.      * @see GraphStyle#DEFAULT_STYLE
  297.      * @see GraphStyle#LINE_STYLE
  298.      * @see GraphStyle#BAR_STYLE
  299.      * @see GraphStyle#PIE_STYLE
  300.      * @see GraphStyle#AREA_STYLE
  301.      * @see GraphStyle#SCATTER_STYLE
  302.      */
  303.     public void setStyle( int style )
  304.     {
  305.         if ( style <= AREA_STYLE == style >= DEFAULT_STYLE )
  306.         {
  307.             graphStyle = style;
  308.             repaint();
  309.         }
  310.     }
  311.  
  312.     /**
  313.      * returns whether or not the graph is to display a legend
  314.      * @return true if graph displays a legend
  315.      * @see #setShowLegend
  316.      */
  317.     public boolean isShowLegend()
  318.     {
  319.         return showLegend;
  320.     }
  321.  
  322.     /**
  323.      * Sets whether the graph is to display a legend or not
  324.      * @param b true if graph should display a legend
  325.      * @see #isShowLegend
  326.      */
  327.     public void setShowLegend( boolean b )
  328.     {
  329.         showLegend = b;
  330.         repaint();
  331.         if ( b == false )
  332.             legendBorder = 0;
  333.     }
  334.  
  335.     /**
  336.      * returns whether or not a grid is displayed as an overlay of the data area
  337.      * @return true if a grid is displayed
  338.      * @see #getShowGrid
  339.      */
  340.     public boolean isShowGrid( )
  341.     {
  342.         return showGrid;
  343.     }
  344.  
  345.    /**
  346.      * returns whether or not a grid is displayed as an overlay of the data area
  347.      * @return true if a grid is displayed
  348.      * @see #setShowGrid
  349.      * @deprecated use isShowGrid instead
  350.      */
  351.     public boolean getShowGrid( )
  352.     {
  353.         return isShowGrid();
  354.     }
  355.  
  356.     /**
  357.      * sets whether or not a grid should be displayed as an overlay of the data area
  358.      * @param show true if a grid should be displayed
  359.      * @see #getShowGrid
  360.      */
  361.     public void setShowGrid( boolean show )
  362.     {
  363.         showGrid = show;
  364.         repaint();
  365.     }
  366.  
  367.     /**
  368.      * Gets the title of the graph
  369.      * @return String title of the graph
  370.      * @see #setTitle
  371.      */
  372.     public String getTitle( )
  373.     {
  374.         return title;
  375.     }
  376.  
  377.     /**
  378.      * Sets the title of the graph
  379.      * @param s title of the graph
  380.      * @see #getTitle
  381.      */
  382.     public void setTitle( String s )
  383.     {
  384.         title = s;
  385.         repaint();
  386.     }
  387.  
  388.     /**
  389.      * gets border insets of the graph
  390.      * @return border of the graph
  391.      * @see #setGraphBorder
  392.      */
  393.     public Insets getGraphBorder( )
  394.     {
  395.         return graphBorder;
  396.     }
  397.  
  398.     /**
  399.      * sets the border insets of the graph
  400.      * @param i graph insets to use
  401.      * @see #getGraphBorder
  402.      */
  403.     public void setGraphBorder( Insets i )
  404.     {
  405.         graphBorder = i;
  406.     }
  407.  
  408.     /**
  409.      * gets the precision displayed by the chart. Precision is defined
  410.      * as the number of digits after the decimal point that are to be displayed.
  411.      * specifying zero indicates that the number is to be treated as an integer.
  412.      * @return int the number of places displayed after the decimal
  413.      * @see #setPrecision
  414.      */
  415.      public int getPrecision(  )
  416.      {
  417.         return precision;
  418.      }
  419.  
  420.     /**
  421.      * sets the precision to be displayed by the chart. Precision is defined
  422.      * as the number of digits after the decimal point that are to be displayed.
  423.      * specifying zero indicates that the number is to be treated as an integer.
  424.      * @param places the number of places to display after the decimal
  425.      * @see #getPrecision
  426.      */
  427.      public void setPrecision( int places )
  428.      {
  429.         precision = places;
  430.         repaint();
  431.      }
  432.  
  433.     /**
  434.      * gets the number of ticks or major subdivisions  displayed on the y axis
  435.      * @return int the number of major subdivisions of the y axis
  436.      * @see #setNumYTicks
  437.      */
  438.     public int getNumYTicks(  )
  439.     {
  440.         return numTicksY;
  441.     }
  442.  
  443.     /**
  444.      * sets the number of ticks or major subdivisions to be displayed on the y axis
  445.      * @param ticks the number of major subdivisions to use for the y axis
  446.      * @see #getNumYTicks
  447.      */
  448.     public void setNumYTicks( int ticks )
  449.     {
  450.         numTicksY = ticks;
  451.         repaint();
  452.     }
  453.  
  454.     /**
  455.      * gets the minimum or origin value on the y axis
  456.      * @return the minimum or origin value on the y axis
  457.      * @see #setYAxisMin
  458.      */
  459.     public double getYAxisMin()
  460.     {
  461.       return yAxisMin;
  462.     }
  463.  
  464.     /**
  465.      * sets the minimum or origin value on the y axis
  466.      * @param min the minimum or origin value on the y axis
  467.      * @see #getYAxisMin
  468.      */
  469.     public void setYAxisMin( double min )
  470.     {
  471.         yAxisMin = min;
  472.         repaint();  // calls calibrate graph
  473.     }
  474.  
  475.     /**
  476.      * gets the maximum on the y axis
  477.      * @return the maximum value on the y axis
  478.      * @see #setYAxisMax
  479.      */
  480.     public double getYAxisMax()
  481.     {
  482.        return yAxisMax;
  483.     }
  484.  
  485.    /**
  486.      * sets the maximum on the y axis
  487.      * @param max the maximum value on the y axis
  488.      * @see #getYAxisMax
  489.      */
  490.     public void setYAxisMax( double max )
  491.     {
  492.        yAxisMax = max;
  493.        repaint();  // calls calibrate graph
  494.     }
  495.  
  496.     /********** Data Acessor Routines **********/
  497.  
  498.     /**
  499.      * Returns the number of data series that are used in the graph
  500.      * @return number of series
  501.      */
  502.     public int getNumSeries( )
  503.     {
  504.         return getModel().getColumnCount();
  505.     }
  506.  
  507.     /**
  508.      * Gets the size (number of elements) of the series referenced by index i
  509.      * @param i the index of the series
  510.      * @return the size of the series
  511.      * @exception ArrayIndexOutOfBoundsException
  512.      */
  513.     public int getSeriesSize( int i )
  514.     {
  515.        return getModel().getRowCount();
  516.     }
  517.  
  518.     /**
  519.      * retrieves a series entry value
  520.      * @param series the number of the series to edit
  521.      * @param index  the index of the item to edit
  522.      * @exception ArrayIndexOutOfBoundsException
  523.      */
  524.     public double getSeriesValue( int series, int index )
  525.     {
  526.         Object obj = getModel().getValueAt(index, series);
  527.  
  528.         if ( obj != null )
  529.         {
  530.             try
  531.             {
  532.                 if ( obj instanceof DataElem )
  533.                     return ((DataElem)obj).getData();
  534.                 else if ( obj instanceof Double )
  535.                     return ((Double) obj).doubleValue();
  536.                 else if ( obj instanceof Integer )
  537.                     return (double)((Integer) obj).intValue();
  538.                 else if ( obj instanceof Long )
  539.                     return (double)((Long)obj).intValue();
  540.                 else if ( obj instanceof String ) {
  541.                     //return (new Double((String)obj)).doubleValue();
  542.                     DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getNumberInstance();
  543.                     return decimalFormat.parse((String)obj).doubleValue();
  544.                 }
  545.             }
  546.             catch ( Exception e )
  547.             {
  548.                 System.out.println( "Warning- Non-numeric detected in chart data: " + obj );
  549.             }
  550.         }
  551.         return 0;
  552.     }
  553.  
  554.     /**
  555.      * gets the name of a series associated with a given index
  556.      * @param series index of the series to use
  557.      * @return name of the requested series
  558.      */
  559.     public String getSeriesName( int series )
  560.     {
  561.         return getModel().getColumnName( series );
  562.     }
  563.  
  564.     /**
  565.      * gets the default color of a particular series
  566.      * @param series index of the series to use
  567.      * @return color associated with the requested series
  568.      */
  569.     public Color getSeriesColor( int series )
  570.     {
  571.         return getSeriesColor( series, 0 );
  572.     }
  573.     /**
  574.      * gets the color of a particular data element within a series
  575.      * @param series number of the series to use
  576.      * @param index associated with the data element within the series
  577.      * @return color associated with the requested series
  578.      */
  579.     public Color getSeriesColor( int series, int index )
  580.     {
  581.         Object obj = getModel().getValueAt(index, series);
  582.  
  583.         if ( obj != null )
  584.         {
  585.             if ( obj instanceof DataElem )
  586.                 return ((DataElem)obj).getColor();
  587.         }
  588.         Vector colors = DataElem.graphColors;
  589.         if ( graphStyle != PIE_STYLE )
  590.             return (java.awt.Color) colors.elementAt( series % colors.size());
  591.         return ( java.awt.Color ) colors.elementAt( ((series * graphCols) + index) % colors.size() );
  592.     }
  593.  
  594.     /**
  595.      * gets the label of a data element associated with a given
  596.      * index of a specific series
  597.      * @param series number of the series to use
  598.      * @param index of the data element to use
  599.      * @return name of the requested series
  600.      * @see #getSeriesName
  601.      */
  602.     public String getDataLabel( int series, int index )
  603.     {
  604.         Object obj = getModel().getValueAt(index, series);
  605.  
  606.         if ( obj != null )
  607.         {
  608.             if ( obj instanceof DataElem )
  609.                 return ((DataElem)obj).getLabel();
  610.         }
  611.         return "";
  612.     }
  613.  
  614.     /********** Data Statistical Routines **********/
  615.  
  616.     /**
  617.      * finds the highest value (max) in all series
  618.      * @return max value in given series
  619.      * @exception ArrayIndexOutOfBoundsException
  620.      * @see #seriesMax( int num )
  621.      */
  622.     protected double seriesMax(  )
  623.     {
  624.         double maxVal = 0;
  625.         for ( int i = 0; i < getNumSeries(); i++ )
  626.             maxVal = Math.max( maxVal, seriesMax( i ));
  627.         return maxVal;
  628.     }
  629.  
  630.     /**
  631.      * finds the highest value (max) in a series
  632.      * @param num the series in which to find the maximum value
  633.      * @return max value in given series
  634.      * @exception ArrayIndexOutOfBoundsException
  635.      */
  636.     protected double seriesMax( int num )
  637.     {
  638.         double maxVal = 0;
  639.         for ( int i = 0; i < getSeriesSize( num ); i++ )
  640.            maxVal = Math.max( maxVal, getSeriesValue( num, i ));
  641.         return maxVal;
  642.     }
  643.  
  644.      /**
  645.      * finds the lowest value (min) in all series
  646.      * @return min value in given series
  647.      * @exception ArrayIndexOutOfBoundsException
  648.      * @see #seriesMin( int num )
  649.      */
  650.     protected double seriesMin(  )
  651.     {
  652.         double minVal = Double.MAX_VALUE;
  653.         for ( int i = 0; i < getNumSeries(); i++ )
  654.             minVal = Math.min( minVal, seriesMin( i ));
  655.         return minVal;
  656.     }
  657.  
  658.     /**
  659.      * finds the lowest value (min) in a series
  660.      * @param num the series in which to find the minimum value
  661.      * @return min value in given series
  662.      * @exception ArrayIndexOutOfBoundsException
  663.      */
  664.     protected double seriesMin( int num )
  665.     {
  666.         double minVal = getSeriesValue( num, 0 );
  667.         for ( int i = 1; i < getSeriesSize( num ); i++ )
  668.            minVal = Math.min( minVal, getSeriesValue( num, i ));
  669.         return minVal;
  670.     }
  671.  
  672.     /**
  673.      * finds the maximum of the summation of all series entries at a given index
  674.      * @return maximum graph summation
  675.      */
  676.     protected double seriesSumMax( )
  677.     {
  678.         double max = 0;
  679.  
  680.         for ( int i = 0; i < graphCols; i++ )
  681.         {
  682.             double tempMax = 0;
  683.             for ( int j = 0; j < getNumSeries(); j++ )
  684.             {
  685.                 if ( i < getSeriesSize( j ) )
  686.                     tempMax += getSeriesValue( j, i );
  687.             }
  688.             max = Math.max( max, tempMax );
  689.         }
  690.         return max;
  691.     }
  692.  
  693.     /**
  694.      * finds the sumation of all data points in a given series
  695.      * @param num the series in which to find the sumation
  696.      * @return sumation of a given series
  697.      */
  698.     protected double seriesSum( int num )
  699.     {
  700.         double sum = getSeriesValue( num, 0 );
  701.         for ( int i = 1; i < getSeriesSize( num ); i++ )
  702.             sum += getSeriesValue( num, i );
  703.         return sum;
  704.     }
  705.  
  706.     /********** Graphics utility routines  **********/
  707.  
  708.     protected String truncDataStr( String s )
  709.     {
  710.         StringBuffer buf = new StringBuffer( s );
  711.  
  712.         int i = s.indexOf( '.' );
  713.         if ( i > 0 )
  714.             if ( precision == 0 )
  715.                 buf.setLength( i );
  716.             else if ( buf.length() >= i + 1+ precision )
  717.                 buf.setLength( i + 1 + precision );
  718.  
  719.         return buf.toString();
  720.     }
  721.  
  722.     protected void calcLabelInset( java.awt.Graphics g )
  723.     {
  724.         FontMetrics fm = g.getFontMetrics();
  725.         Insets i = getInsets();
  726.  
  727.         if ( graphStyle != AREA_STYLE )
  728.            labelYInset = fm.stringWidth( truncDataStr( String.valueOf( graphMax ))) - 1;
  729.         else
  730.             labelYInset = fm.stringWidth( truncDataStr( String.valueOf( seriesSumMax() ))) - 1;
  731.     }
  732.  
  733.     /**
  734.      * Calculates the interval between tick marks on the horizontal axis
  735.      */
  736.     protected void calcTickInterval()
  737.     {
  738.         if ( graphStyle == BAR_STYLE )
  739.             gridIncrementH =  (getSize().width - graphBorder.right - graphBorder.left - labelYInset - legendBorder )/
  740.                     (graphCols > 0 ? graphCols : 1);
  741.         else
  742.             gridIncrementH = (getSize().width - graphBorder.right - graphBorder.left - labelYInset - legendBorder )/
  743.                     (graphCols-1 > 0 ? graphCols-1 : 1);
  744.     }
  745.  
  746.  
  747.     protected void calibrateGraph( )     // find y axis calibration
  748.     {
  749.         calcTickInterval();
  750.  
  751.         if ( yAxisMax != AUTO_CALCULATE_MAX )
  752.             graphUpper = yAxisMax;
  753.         else
  754.         {
  755.             if ( graphStyle != AREA_STYLE )
  756.                 graphUpper = seriesMax();
  757.             else
  758.                 graphUpper = seriesSumMax();
  759.         }
  760.  
  761.         debugStr( "graphUpper: " + graphUpper );
  762.  
  763.         if ( precision == 0 )
  764.             gridStepV = Math.ceil(graphUpper/numTicksY);
  765.         else
  766.             gridStepV = graphUpper / numTicksY;
  767.  
  768.         debugStr( "gridStepV: " + gridStepV );
  769.  
  770.         if ( yAxisMax == AUTO_CALCULATE_MAX )
  771.             graphUpper = gridStepV * numTicksY;
  772.  
  773.         // if the user has provided an upper and a lower limit, use them
  774.         if ( yAxisMin != AUTO_CALCULATE_MIN )
  775.             graphLower = yAxisMin;
  776.         else
  777.         {
  778.             graphLower = seriesMin();
  779.             graphLower = Math.floor( graphLower/gridStepV ) * gridStepV;
  780.             if ( graphLower > 0 && (graphLower / ( seriesMax() - seriesMin()) <= .2 ))
  781.                 graphLower = 0;
  782.         }
  783.  
  784.         //gridStepV = (graphUpper - graphLower)/numTicksY;
  785.         Dimension d = getSize();
  786.         gridIncrementV= (int)(((d.height - graphBorder.bottom - graphBorder.top ) / numTicksY)+.5);
  787.      }
  788.  
  789.     protected void drawAreaGraph( java.awt.Graphics g )
  790.     {
  791.         Dimension d = getSize();
  792.         int    plotV = 0;
  793.         double lastValue = 0;
  794.         Vector polyArray = new Vector( getNumSeries() );
  795.  
  796.         int tickPosH = graphBorder.left + labelYInset;
  797.         for ( int i = 0; i < graphCols; i++ )
  798.         {
  799.             for ( int j = 0; j < getNumSeries(); j++ )
  800.             {
  801.                 if ( i == 0 )   // we only want to create a polygon once for each series
  802.                 {
  803.                     Polygon poly = new Polygon();
  804.                     poly.addPoint( graphBorder.left + labelYInset, d.height - graphBorder.bottom );
  805.                     polyArray.addElement((Object) poly);
  806.                 }
  807.                 if ( i < getSeriesSize( j ) )
  808.                 {
  809.                     double data  = getSeriesValue( j, i ) - graphLower;
  810.                     plotV = (int)(((data / gridStepV) * gridIncrementV) + .5 );
  811.                     plotV += lastValue;
  812.                 }
  813.  
  814.                 Polygon p = (Polygon) polyArray.elementAt( j );
  815.                 p.addPoint( tickPosH,  d.height - plotV - graphBorder.bottom );
  816.                 lastValue = plotV;
  817.             }
  818.             tickPosH += gridIncrementH;
  819.             plotV = 0;
  820.             lastValue = 0;
  821.         }
  822.  
  823.         for ( int k = getNumSeries()-1; k >= 0; k-- )
  824.         {
  825.             Polygon tempPoly = (Polygon) polyArray.elementAt( k );
  826.             tempPoly.addPoint( graphBorder.left + labelYInset + (graphCols-1) * gridIncrementH, d.height - graphBorder.bottom );
  827.             g.setColor( getSeriesColor( k ));
  828.             g.fillPolygon( tempPoly );
  829.         }
  830.     }
  831.  
  832.     protected void drawPieGraph( java.awt.Graphics g )
  833.     {
  834.         Dimension d = getSize();
  835.         Rectangle graphArea = new Rectangle( d.width - graphBorder.left - graphBorder.right,
  836.                                              d.height - graphBorder.top - graphBorder.bottom );
  837.         int minDimension = Math.min( graphArea.width, graphArea.height );
  838.  
  839.         if ( minDimension <= 0 )
  840.             return;
  841.  
  842.         Rectangle cell = new Rectangle( minDimension, minDimension );
  843.  
  844.         int num = getNumSeries();
  845.         
  846.         if ( num < 1 )
  847.             return;
  848.             
  849.         int rows = 1;
  850.         int cols = num;
  851.         int pad  = 30;
  852.         int rem  = 0;
  853.         int first= num;
  854.         FontMetrics fm = g.getFontMetrics();
  855.         int vLabel = fm.getHeight() + 10;
  856.         boolean done = false;
  857.  
  858.         cell.width = (int)Math.floor((graphArea.width - pad * ( num - 1 )) / (double) num); // fit cells horizontally
  859.         cell.height= (int)Math.floor((graphArea.height- pad * ( rows - 1 ))  / (double) rows - vLabel);
  860.         cell.width = cell.height = Math.min( cell.width, cell.height );
  861.         cell.height += vLabel;
  862.  
  863.         while ( !done )     // figure out how to tile the pie charts
  864.         {
  865.             if ( (rows+1) * cell.height + rows * pad < graphArea.height ) // is there room for another row?
  866.             {
  867.                 rows++;                     // create a new row
  868.                 cols = num / rows;          // compute average row count
  869.                 rem = num - (cols * rows);  // compute remainder
  870.                 if ( rem > 1  )
  871.                 {
  872.                     cols += 1;
  873.                     first = cols;
  874.                 }
  875.                 else
  876.                     first = cols + rem;
  877.  
  878.                 cell.width = (int)Math.floor((graphArea.width - pad * ( first - 1 )) / (double) first); // fit cells horizontally
  879.                 cell.height= (int)Math.floor((graphArea.height- pad * ( rows - 1 ))  / (double) rows - vLabel);
  880.                 cell.width = cell.height = Math.min( cell.width, cell.height );
  881.                 cell.height += vLabel;
  882.             }
  883.             else
  884.                 done = true;
  885.         }
  886.  
  887.         int n = 1;
  888.         int last = 0;
  889.         int hSpace = ((graphArea.width - ( cols * cell.width + ( cols - 1 ) * pad ))/cols)/2;
  890.         int vSpace = ((graphArea.height - ( rows * cell.height + ( rows - 1 ) * pad ))/rows)/2;
  891.         for ( int y = 0; y < rows; y++ )
  892.         {
  893.             for ( int x = 0; x < first; x++ )
  894.             {
  895.                 if ( n > num )
  896.                     break;
  897.  
  898.                 if ( y != 0 && x >= cols )
  899.                     continue;
  900.                 cell.setLocation( graphArea.x + graphBorder.left + ( cell.width + ((x != 0 ) ? pad : 0 )) *x + hSpace,
  901.                                   graphArea.y + graphBorder.top + (cell.height + ((y != 0 ) ? pad : 0 )) * y + vSpace );
  902.                 drawPieSeries( g, n-1, cell, last, vLabel );
  903.                 n++;
  904.             }
  905.             last = 0;
  906.         }
  907.     }
  908.  
  909.     /**
  910.      * Draws the legend of the graph
  911.      */
  912.     protected void drawLegend( java.awt.Graphics g )
  913.     {
  914.         int         maxStrLen = 0;                // the length of the longest series name string
  915.         Rectangle   lRect;                        // the rectangle of the legend
  916.         FontMetrics fm = g.getFontMetrics();      // the font metrics of the Graphics object
  917.         int         lnHeight = fm.getHeight();    // the height of a single line of text in the legend
  918.         int         boxHeight;                    // the height of the legend box including text str
  919.         int         count = getNumSeries();       // the series count
  920.         Dimension   d = getSize();                // the dimension of the canvas
  921.         ResourceBundle conn = ResourceBundle.getBundle("com.symantec.itools.swing.JChartResourceBundle");
  922.  
  923.         if ( count <= 0 )
  924.             return;
  925.  
  926.         for ( int i = 0; i < count; i++ )        // find the length of the longest series name
  927.         {
  928.             String name = getSeriesName( i );
  929.  
  930.             if ( name == "" || name == " " )
  931.                 name = conn.getString(JChartResourceBundle.JCHART_SERIES_KEY) + " " + (i+1);
  932.             maxStrLen = Math.max( fm.stringWidth( name ), maxStrLen );
  933.         }
  934.         maxStrLen = Math.max( maxStrLen, fm.stringWidth("Legend "));
  935.  
  936.         legendBorder = maxStrLen + 22;
  937.  
  938.         boxHeight = count * (lnHeight + 4) + 12;
  939.  
  940.         while ( boxHeight + lnHeight + 4 > d.height )
  941.         {
  942.             boxHeight -= (lnHeight + 4);
  943.             count--;
  944.         }
  945.  
  946.         int yPos = (d.height / 2) - (boxHeight/2) - 4;// the upper left corner of the legend (centered)
  947.  
  948.         lRect = new Rectangle( d.width - graphBorder.right - legendBorder + fm.getHeight()/2 + 2, yPos +4,
  949.                                legendBorder - fm.getHeight()/2 - 4, boxHeight );
  950.         g.drawString( conn.getString(JChartResourceBundle.JCHART_LEGEND_KEY), lRect.x + (lRect.width - fm.stringWidth( "Legend" ))/2, lRect.y - 5 );
  951.         g.drawRect( lRect.x, lRect.y, lRect.width, lRect.height - 10 );
  952.  
  953.         int ovalSize = fm.getHeight()/4;
  954.         ovalSize = ovalSize < 4 ? 4 : ovalSize;
  955.         for ( int j = 0; j < count; j++ )
  956.         {
  957.             yPos += lnHeight + 4;
  958.             g.setColor( getSeriesColor( j ));
  959.             g.fillOval( lRect.x + 6 - (ovalSize/2), yPos - (lnHeight / 2 ), ovalSize, ovalSize );
  960.             g.setColor( Color.black );
  961.             String s = getSeriesName( j );
  962.             if ( s == "" || s == " " )
  963.                 s = "Series " + (j+1);
  964.             g.drawString( s, lRect.x + 10, yPos );
  965.         }
  966.     }
  967.  
  968.     protected void drawPieSeries( java.awt.Graphics g, int seriesNum, Rectangle frame, int startAngle, int vLabel )
  969.     {
  970.         double total = seriesSum( seriesNum );
  971.         double err = 0;
  972.  
  973.         if ( getNumSeries() <= 0 || total == 0 )
  974.             return;
  975.  
  976.         Font oldFont = g.getFont();
  977.         int  size = oldFont.getSize();
  978.         int  style= oldFont.getStyle();
  979.         String fnt= oldFont.getName();
  980.  
  981.         Font newFont = new Font( fnt, graphStyle, 9 );
  982.         g.setFont( newFont );
  983.  
  984.         for ( int i = 0; i < getSeriesSize( seriesNum ); i++ )
  985.         {
  986.             double data = getSeriesValue( seriesNum, i );
  987.             double percent = data / total;
  988.             int arc = (int)((percent * 360)+.5);
  989.             err += (percent * 360) - arc;
  990.  
  991.             if ( arc >= 1 )
  992.             {
  993.                 arc++;  err--;
  994.             }
  995.             else if ( arc <= -1 )
  996.             {
  997.                 arc--; err++;
  998.             }
  999.  
  1000.             g.setColor( (java.awt.Color) getSeriesColor( seriesNum, i ));
  1001.             //g.setColor( (java.awt.Color) graphColors.elementAt( (( seriesNum * graphCols ) + i ) % graphColors.size()));
  1002.             g.fillArc( frame.x, frame.y, frame.width, frame.height - vLabel, startAngle, arc );
  1003.             if ( arc > 3 )  // do not label segments less than 3 degrees
  1004.             {
  1005.                g.setColor( Color.black );
  1006.  
  1007.                int tic = startAngle + arc / 2;
  1008.                double x = Math.cos((double)tic*(Math.PI/180)) * ((frame.width/2)+10);
  1009.                double y = Math.sin((double)tic*(Math.PI/180)) * ((frame.width/2)+10);
  1010.  
  1011.                g.drawString( getDataLabel( seriesNum, i ), (int)x + frame.width/2 + frame.x - 2,  frame.width/2 + frame.y - (int)y + 5 );
  1012.             }
  1013.             startAngle += arc;
  1014.         }
  1015.  
  1016.         g.setFont( oldFont );
  1017.         g.setColor( Color.black );
  1018.         g.drawOval( frame.x, frame.y, frame.width, frame.height - vLabel );
  1019.  
  1020.         FontMetrics fm = g.getFontMetrics();
  1021.         g.drawString( getSeriesName( seriesNum ), (frame.width - fm.stringWidth( getSeriesName( seriesNum )))/2 + frame.x,
  1022.                       frame.y + frame.height );
  1023.     }
  1024.  
  1025.     protected void drawTitle( java.awt.Graphics g )
  1026.     {
  1027.         Dimension    d = getSize();
  1028.         FontMetrics fm = g.getFontMetrics();
  1029.  
  1030.         g.drawString( title, (d.width - graphBorder.right - graphBorder.right - legendBorder - labelYInset - fm.stringWidth( title ))/2
  1031.                         + graphBorder.left + labelYInset, titleBorder );
  1032.     }
  1033.  
  1034.     protected void drawGrid( java.awt.Graphics g )
  1035.     {
  1036.        if ( showGrid &&  graphStyle != PIE_STYLE )
  1037.        {
  1038.             Dimension    d = getSize();                // the dimension of the canvas
  1039.             Color oldColor = g.getColor();
  1040.             g.setColor( Color.lightGray );
  1041.  
  1042.             for ( int i = 1; i <= numTicksY; i++ )
  1043.                 g.drawLine( graphBorder.left + labelYInset + 2, d.height - graphBorder.bottom - gridIncrementV * i,
  1044.                     d.width - graphBorder.right - legendBorder, d.height - graphBorder.bottom - gridIncrementV * i);
  1045.  
  1046.  
  1047.  
  1048.             int tickPosH = graphBorder.left + labelYInset;
  1049.             for ( int i = 1; i <= (graphStyle == BAR_STYLE ? graphCols : graphCols-1 ); i++ )
  1050.             {
  1051.                 g.drawLine( tickPosH + gridIncrementH, d.height - graphBorder.bottom - 2, tickPosH + gridIncrementH, d.height - graphBorder.bottom + 2 );
  1052.                 g.drawLine( tickPosH + gridIncrementH, d.height - graphBorder.bottom - 3, tickPosH + gridIncrementH, graphBorder.top  );
  1053.                 tickPosH += gridIncrementH;
  1054.             }
  1055.             g.setColor( oldColor );
  1056.         }
  1057.     }
  1058.  
  1059.     protected void drawAxis( java.awt.Graphics g )
  1060.     {
  1061.         Dimension   d = getSize();                // the dimension of the canvas
  1062.  
  1063.         g.setColor( Color.black );
  1064.  
  1065.         g.drawLine( graphBorder.left + labelYInset, graphBorder.top, graphBorder.left + labelYInset, d.height - graphBorder.bottom + 3 ); // y axis
  1066.         g.drawLine( graphBorder.left + labelYInset -3, d.height - graphBorder.bottom, d.width - graphBorder.right - legendBorder, d.height - graphBorder.bottom );
  1067.  
  1068.         drawYAxisTicks( g  );
  1069.  
  1070.         // draw ticks evenly distributed on x axis for series values
  1071.         int tickPosH = graphBorder.left + labelYInset;
  1072.         for ( int i = 1; i <= (graphStyle != BAR_STYLE ? graphCols-1 : graphCols ); i++ )
  1073.         {
  1074.             g.drawLine( tickPosH + gridIncrementH, d.height - graphBorder.bottom - 2, tickPosH + gridIncrementH, d.height - graphBorder.bottom + 2 );
  1075.             tickPosH += gridIncrementH;
  1076.         }
  1077.     }
  1078.  
  1079.     protected void drawYAxisTicks( java.awt.Graphics g )
  1080.     {
  1081.         g.setColor( Color.black );
  1082.         FontMetrics fm = g.getFontMetrics();
  1083.         Dimension    d = getSize(); // the dimension of the canvas
  1084.  
  1085.         if ( graphCols > 0 )
  1086.             for ( int i = 0; i <= numTicksY; i++ )
  1087.             {
  1088.                 g.drawLine( graphBorder.left + labelYInset - 2, d.height - graphBorder.bottom - gridIncrementV * i,
  1089.                     graphBorder.left + labelYInset + 2, d.height - graphBorder.bottom - gridIncrementV * i );
  1090.  
  1091.                 String s = truncDataStr( String.valueOf( gridStepV * i + (int)graphLower ));
  1092.                 g.drawString( s, graphBorder.left + labelYInset - fm.stringWidth( s ) - 2,
  1093.                           d.height - graphBorder.bottom - gridIncrementV * i + 3 );
  1094.             }
  1095.     }
  1096.  
  1097.     /********** Utility Routines **********/
  1098.     void debugStr( String msg )
  1099.     {
  1100.         if ( debug )
  1101.             System.out.println( msg );
  1102.     }
  1103.  
  1104.     String styleString(  )
  1105.     {
  1106.         if ( graphStyle == LINE_STYLE )
  1107.             return "line";
  1108.         else if ( graphStyle == BAR_STYLE )
  1109.             return "bar";
  1110.         else if ( graphStyle == SCATTER_STYLE )
  1111.             return "scatter";
  1112.         else if ( graphStyle == AREA_STYLE )
  1113.             return "area";
  1114.         else if ( graphStyle == PIE_STYLE )
  1115.             return "pie";
  1116.         return "other";
  1117.     }
  1118. }