home *** CD-ROM | disk | FTP | other *** search
/ io Programmo 39 / IOPROG_39.ISO / SOFT / sdkjava40.exe / data1.cab / fg_Samples / Samples / ActiveX / JChart / JChart.java < prev    next >
Encoding:
Java Source  |  2000-05-04  |  53.0 KB  |  1,740 lines

  1. ///////////////////////////////////////////////////////////////////////////
  2. //
  3. //  JChart.java
  4. //
  5. // This bean presents data to the user using a chart interface.
  6. // It uses AFC (UI classes) internally to present data to the user.  
  7. // It utilizes serialization to save and load chart information. 
  8. // A special custom BeanInfo is provided to expose a set of properties, methods, and events. 
  9. // Also, this bean uses a bean-customizer that uses the UITabViewer object
  10. // This bean will be placed on a VB form. VB code will drive the bean to switch between chart types. 
  11. //
  12. //  (C) Copyright 1995 - 1999 Microsoft Corporation.  All rights reserved.
  13. //
  14. ///////////////////////////////////////////////////////////////////////////
  15.  
  16. import java.awt.*;
  17. import java.io.*;
  18. import java.beans.*;
  19. import java.util.*;
  20.  
  21.  
  22. public class JChart extends Canvas implements Serializable
  23. {
  24.     // The chart properties
  25.     private int chartType = BAR_CHART;              // the type of the chart being displayed
  26.     private boolean showLegend = true;              // Should the legend be shown.
  27.     private boolean compareColumns = true;          // Arrange the bars for the columns in a row together.
  28.     private String chartTitle = "";                 // The title of the chart
  29.     private String xAxisTitle = "";                 // The title of the x-axis
  30.     private String yAxisTitle = "";                 // The title of the y-axis
  31.     private String caption = "";                    // The caption of the chart ( a comment below the chart)
  32.     private double [][] data;                       // The array of data values
  33.     private int column;                             // The current column number for editing data.
  34.     private int row;                                // The current row number for entering data.
  35.     private int columnCount;                        // The number of columns
  36.     private int rowCount;                           // The number of rows.
  37.     private String[] columnLabel;                   // The array of column names
  38.     private String[] rowLabel;                      // The array of row names.
  39.     private Color[] chartColors = {Color.red, Color.green, Color.yellow, Color.cyan, Color.pink,
  40.         Color.blue, Color.magenta, Color.orange};   // The colors to display for the chart
  41.     private int rowGap = 0;                         // The gap between two rows
  42.     private int columnGap = 0;                      // The gap between two columns
  43.     private File dataFile = null;                   // The file which contains the Excel data
  44.  
  45.     // constant declaration
  46.  
  47.     // These are the possible values of the chart type
  48.     public static final int BAR_CHART = 1;          // A regular bar chart
  49.     public static final int COLUMN_CHART = 2;       // horizontal bars
  50.     public static final int PIE_CHART = 3;          // A pie chart
  51.     public static final int LINE_GRAPH = 4;         // A line graph
  52.  
  53.     // miscellaneous constants for drawing the charts
  54.     private static final int NOTCH_WIDTH = 5;        // The width of the notches made on the Y-axis
  55.     private static final int NOTCH_HEIGHT = 5;       // The height of the notches made on the X-axis
  56.     private static final int CIRCLE_RADIUS = 3;      // The radius of the circle drawn around each point
  57.     private static final int BAR_WIDTH = 10;         // the preferred widht of a bar
  58.     private static final int BAR_HEIGHT = 200;       // the preferred maximum height of a bar
  59.     private static final int COLUMN_HEIGHT = 10;     // the preferred height of a column
  60.     private static final int COLUMN_WIDTH = 200;     // the preferred maximum width of a column
  61.     private static final int PIE_CHART_RADIUS = 200; // the preferred radius of the pie chart    
  62.  
  63.  
  64.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  65.     // Constructors for JChart and Main method for testing.
  66.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  67.     
  68.     /**
  69.      * Null constructor for the chart.
  70.      */
  71.     public JChart()
  72.     {
  73.         super();
  74.  
  75.         // set the font
  76.         setFont(new Font("Dialog", Font.PLAIN, 12) );
  77.     }
  78.  
  79.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  80.     // Reconstruct the class from an external storage
  81.     // These functions writeObject and readObject are required because this
  82.     // class needs special handling on de-serialization/serialization. 
  83.     // The data file has to be written out to disk on serialization and read
  84.     // from disk on de-serialization
  85.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  86.  
  87.     /**
  88.      * Write the properties of the chart to an external object.
  89.      *
  90.      * @param   ObjectOutput    The object to write the calendar properties to
  91.      */
  92.     private void writeObject(ObjectOutputStream stream) throws IOException
  93.     {
  94.         stream.defaultWriteObject();
  95.     }
  96.     
  97.     /**
  98.      * read the properties of the chart from an external object.
  99.      * If the dataFileName property is "good" then read in the values from
  100.      * disk. Else set the dataFileName as "".
  101.      *
  102.      * @param   ObjectInput     The object to read the properties from
  103.      */
  104.     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
  105.     {
  106.         stream.defaultReadObject();
  107.  
  108.         File oldFile = dataFile;
  109.         dataFile = null;
  110.  
  111.         if( oldFile != null)
  112.         {
  113.             try
  114.             {
  115.                 setDataFileName(oldFile.getAbsolutePath() );
  116.             }
  117.             catch(Exception ex)
  118.             {
  119.                 dataFile = null;
  120.             }
  121.         }
  122.     }
  123.  
  124.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  125.     // Methods for drawing the chart based on its properties.
  126.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  127.     
  128.     /**
  129.      * Resizes the component if so desired. Called from within the bean if its felt
  130.      * that a bigger size may be required. As a result of adding some columns for instance.
  131.      */
  132.     private void adjustSize()
  133.     {
  134.         // the current dimensions
  135.         Dimension current = getSize();
  136.         
  137.         // the desired dimensions
  138.         Dimension desired = getPreferredSize();
  139.  
  140.         // don't do anything if we have more size than we asked for
  141.         if((desired.width <= current.width) && (desired.height <= current.height) )
  142.         {
  143.         }
  144.  
  145.         else
  146.         {
  147.             // first resize the bean
  148.             setSize(desired.width, desired.height);
  149.         }
  150.  
  151.         //now invalidate the parents layout and layout again.
  152.         Component p = getParent();
  153.         if(p != null)
  154.         {
  155.             p.invalidate();
  156.             p.doLayout();
  157.         }
  158.  
  159.         repaint();
  160.     }
  161.  
  162.     /**
  163.      * The preferred size of the chart bean
  164.      * @return  Dimension   the preferred size
  165.      */
  166.     public Dimension getPreferredSize()
  167.     {
  168.         // the font metrics of this component
  169.         FontMetrics fm = getFontMetrics(getFont());
  170.  
  171.         // the preferred width of the rectangle
  172.         int width = 0;
  173.  
  174.         // the preferred height of the rectangle
  175.         int height = 0;
  176.  
  177.         // add 4 lines for the title and caption
  178.         height +=4*fm.getHeight();
  179.  
  180.         // add the width of the title and the caption
  181.         width = Math.max(fm.stringWidth(caption), fm.stringWidth(chartTitle) );
  182.  
  183.         // the width of the graph
  184.         int graph_width = 0;
  185.  
  186.         // the height of the graph
  187.         int graph_height = 0;
  188.  
  189.         switch(chartType)
  190.         {
  191.         case LINE_GRAPH:
  192.         case BAR_CHART:
  193.             // the width from writing the title
  194.             graph_width += fm.stringWidth(yAxisTitle) + NOTCH_WIDTH + 4 + 
  195.                 fm.stringWidth( new Double(getMax()).toString() );
  196.  
  197.             // the height of drawing the title
  198.             graph_height += fm.getHeight() + NOTCH_HEIGHT + 4;
  199.  
  200.             // width of the graph bars
  201.             if(compareColumns)
  202.             {
  203.                 graph_width += rowCount* (columnCount*(BAR_WIDTH+columnGap) + rowGap);
  204.             }
  205.             else
  206.             {
  207.                 graph_width += columnCount *(rowCount*(BAR_WIDTH+rowGap) + columnGap);
  208.             }
  209.  
  210.             // height of the graph bars
  211.             graph_height += BAR_HEIGHT;
  212.             break;
  213.  
  214.         case COLUMN_CHART:
  215.             // the width from writing the title
  216.             graph_width = fm.stringWidth(yAxisTitle) + NOTCH_WIDTH + 4 +
  217.                 fm.stringWidth( new Double(getMax()).toString() );
  218.  
  219.             // the height of drawing the title
  220.             graph_height = fm.getHeight() + NOTCH_HEIGHT + 4;
  221.  
  222.             // the height of the graph columns
  223.             if(compareColumns)
  224.             {
  225.                 graph_height += rowCount* (columnCount*(COLUMN_HEIGHT+columnGap) + rowGap);
  226.             }
  227.             else
  228.             {
  229.                 graph_height += columnCount *(rowCount*(COLUMN_HEIGHT+rowGap) + columnGap);
  230.             }
  231.  
  232.             // width of the graph columns
  233.             graph_width += COLUMN_WIDTH;
  234.             break;
  235.         
  236.         case PIE_CHART:
  237.             // the height from writing the pie-chart label
  238.             graph_height = fm.getHeight();
  239.  
  240.             // the width of the chart
  241.             graph_width += PIE_CHART_RADIUS;
  242.  
  243.             // the height of the pie chart
  244.             graph_height += PIE_CHART_RADIUS;
  245.             break;
  246.         }
  247.  
  248.         height += graph_height;
  249.  
  250.         width = Math.max(width, graph_width);
  251.  
  252.         // throw in an additional third of the width for the legend.
  253.         if(showLegend)
  254.         {
  255.             width += width/3;
  256.         }
  257.  
  258.         // the width should be atleast 200
  259.         width = Math.max(200, width);
  260.  
  261.         return new Dimension(width, height);
  262.     }
  263.  
  264.     /**
  265.      * The minimum size of the chart bean
  266.      * @return  Dimension   the minimum size
  267.      */
  268.     public Dimension getMinimumSize()
  269.     {
  270.         return new Dimension(200,200);
  271.     }
  272.  
  273.     /**
  274.      * The paint method draws the chart depending on its type. and displays other
  275.      * useful information specified by the user like captions, titles and a legend.
  276.      * @param   Graphics    The graphics context for draeing this image.
  277.      */
  278.     public void paint(Graphics g)
  279.     {
  280.         //set the color as black
  281.         g.setColor(Color.black);
  282.  
  283.         // no point doing anything if the data hasn't been entered yet.
  284.         if(columnCount == 0 || rowCount == 0)
  285.         {
  286.             g.drawString("Enter some data", 20,20);
  287.             return;
  288.         }
  289.  
  290.         // if a pie-chart has to be drawn then the x and the y-axis don't have to be drawn
  291.         // but we still need to draw the legend, title and the caption.
  292.         int x = 0;                          // The x cood of the leftmost point
  293.         int y = 0;                          // The y cood of the topmost point.
  294.         int width = getSize().width;           // The width of my drawable area
  295.         int height = getSize().height;         // The height of my drawable area
  296.         FontMetrics fm = g.getFontMetrics();// The font metrics of  my drawable area.
  297.     
  298.         // Draw the title centered at the top of the canvas
  299.         drawCenteredText(g, x,y, width, fm.getHeight(), chartTitle);
  300.  
  301.         // Draw the caption at the bottom.
  302.         drawCenteredText(g, x, y + height - fm.getHeight(), width, fm.getHeight(), caption);
  303.  
  304.         // we want one blank line below the title and and one blank line above the caption
  305.         // the remaining drawing area has shrunk vertically so update y  and height
  306.         height -= 4*fm.getHeight();
  307.         y += 2*fm.getHeight();
  308.  
  309.         // clip out the remaining drawing area
  310.         g.setClip(x, y, width,height);
  311.  
  312.         // Draw the legend if so desired.
  313.         if(showLegend)
  314.         {
  315.             drawLegend(g);
  316.         }
  317.  
  318.         // draw the appropriate chart
  319.         switch(chartType)
  320.         {
  321.         case PIE_CHART:
  322.             drawPieChart(g);
  323.             break;
  324.  
  325.         case BAR_CHART:
  326.             drawBarChart(g);
  327.             break;
  328.  
  329.         case LINE_GRAPH:
  330.             drawLineGraph(g);
  331.             break;
  332.  
  333.         case COLUMN_CHART:
  334.             drawColumnChart(g);
  335.             break;
  336.  
  337.         default:
  338.             g.drawString("Invalid chart type",20,20);
  339.             break;
  340.         }
  341.  
  342.  
  343.     }
  344.  
  345.     /**
  346.      * Draw a text message centered in the middle of the specified area
  347.      * @param   Graphics    The graphics context to draw in
  348.      * @param   int         The top left x cood
  349.      * @param   int         The top left y cood
  350.      * @param   int         The width of the rectangle.
  351.      * @param   int         The height of the rectangle. (should be greater than the height of a character)
  352.      * @param   String      The string to be drawn
  353.      */
  354.     private void drawCenteredText(Graphics g, int x, int y, int width, int height, String str)
  355.     {
  356.         int strWidth = (g.getFontMetrics()).stringWidth(str); // the width of the string
  357.         int strDescent = (g.getFontMetrics()).getDescent();     // The amount which the string can descend
  358.         int strHeight = (g.getFontMetrics()).getHeight();       // the height of a character
  359.  
  360.         g.drawString(str, x+width/2 - strWidth/2, y + height/2 + strHeight/2 - strDescent);
  361.     }
  362.  
  363.     /**
  364.      * Draw a legend in the provided graphics context.
  365.      * <p>
  366.      * The legend will be drawn in the right corner of the area. Using as much space is
  367.      * needed to write the labels but no more than a quarter of the width.
  368.      * @param   Graphics    The graphics context to draw in
  369.      */
  370.     private void drawLegend(Graphics g)
  371.     {
  372.         // the available drawing area
  373.         Rectangle rect = g.getClipBounds();
  374.  
  375.         // get the current color
  376.         Color oldColor = g.getColor();
  377.  
  378.         // the fontmetrics of this graphics object for calculating string widths and height.
  379.         FontMetrics fm = g.getFontMetrics();
  380.  
  381.         int labelCount = compareColumns ? columnCount: rowCount;    // the number of labels to display
  382.         String[] labelNames = compareColumns ? columnLabel : rowLabel;    // The names on the legend
  383.  
  384.         // the desired height of this rectangle.
  385.         int height = fm.getHeight()*labelCount;
  386.  
  387.         // the size of the boxes painted in the legend
  388.         int boxSize = fm.getAscent();
  389.  
  390.         // the padding between the boxes and the  text describing them
  391.         int pad = 2 + fm.getLeading();
  392.  
  393.         // the desired width of this rectangle
  394.         int width = 0;
  395.  
  396.         // first find the widest string
  397.         for(int i=0; i<labelCount; i++)
  398.         {
  399.             width = Math.max(width, fm.stringWidth(labelNames[i]) );
  400.         }
  401.  
  402.         // now add the width of the boxes and padding.
  403.         width = pad + boxSize + pad +width + pad;
  404.  
  405.         // we must occupy no more than a quarter of the total width.
  406.         width = Math.min(width, rect.width/4 );
  407.  
  408.         // the height ofcourse can't exceed the given height
  409.         height = Math.min(height, rect.height);
  410.  
  411.         for(int i=0; i<labelCount; i++)
  412.         {
  413.             // draw the box of the appropriate color
  414.             g.setColor(chartColors[i % chartColors.length]);
  415.             g.fillRect( rect.x+rect.width-width+pad, rect.y + pad + i*fm.getHeight(), boxSize, boxSize);
  416.  
  417.             // Write the label corresponding to this color
  418.             g.drawString(labelNames[i], rect.x+rect.width-width +pad+boxSize+pad,
  419.                 rect.y+pad+boxSize+i*fm.getHeight());
  420.         }
  421.  
  422.         // draw a white rectangle around the legend
  423.         g.setColor(Color.white);
  424.         g.draw3DRect(rect.x+rect.width-width, rect.y, width-1, height-1, true);
  425.  
  426.         // now clip off this region so that noone else overwrites the legend
  427.         g.setClip(rect.x, rect.y, rect.width-width-pad, rect.height);
  428.  
  429.         // and restore the color
  430.         g.setColor(oldColor);
  431.     }
  432.     
  433.     /**
  434.      * Draws a pie chart for the current column or row.
  435.      * @param   Graphics    The context for drawing the chart
  436.      */
  437.     public void drawPieChart(Graphics g)
  438.     {
  439.         Rectangle rect = g.getClipBounds();     // The available drawing area.
  440.  
  441.         String label;                           // The label of the row or column to
  442.                                                 // which the displayed data belongs.
  443.         double[] dataArray;                      // The array of data :-)
  444.  
  445.         double total = 0;                        // the total of all the data.
  446.  
  447.         if(compareColumns)
  448.         {
  449.             // The "0" row doesn't have any value 
  450.             if(row == 0)
  451.                 return;
  452.  
  453.             label = rowLabel[row-1];
  454.  
  455.             dataArray = data[row-1];
  456.         }
  457.  
  458.         else    // compare rows
  459.         {
  460.             // The "0" column has no data
  461.             if(column == 0)
  462.                 return;
  463.  
  464.             label = columnLabel[column-1];
  465.  
  466.             dataArray = new double[rowCount];
  467.             
  468.             for(int i=0; i<rowCount; i++)
  469.             {
  470.                 dataArray[i] = data[i][column-1];
  471.             }
  472.         }
  473.  
  474.         // determine the height of a line
  475.         int charHeight = (g.getFontMetrics()).getHeight();
  476.  
  477.         // write the label of this chart at the bottom of the drawing area.
  478.         drawCenteredText(g,rect.x, rect.y + rect.height - charHeight, rect.width, charHeight, label);
  479.  
  480.         // the remaining drawing area shrinks in size
  481.         rect.height -= charHeight;
  482.  
  483.         // calculate the total of the values
  484.         for(int i=0; i<dataArray.length; i++)
  485.         {
  486.             total += dataArray[i];
  487.         }
  488.  
  489.         // find the center of the piechart
  490.         int centerX = rect.x + rect.width/2;
  491.         int centerY = rect.y + rect.height/2;
  492.  
  493.         // we want the pie-chart to be a perfect circle
  494.         int radius = Math.min(rect.width/2, rect.height/2);
  495.  
  496.         // the angle at which the next arc starts
  497.         int startAngle = 0;
  498.  
  499.         // now draw a filled arc for each of the data items.
  500.         for(int i=0; i<dataArray.length; i++)
  501.         {   
  502.             // calculate the arc angle for this data
  503.             double arcAngle = 360.0 * dataArray[i] / total;
  504.  
  505.             // we round the arc angle to the nearest integer.
  506.             int roundedArcAngle = (int)Math.rint(arcAngle);
  507.  
  508.             // set the color
  509.             g.setColor(chartColors[i % chartColors.length]);
  510.  
  511.             // fill the arc
  512.             g.fillArc(centerX-radius, centerY -radius, 2*radius, 2*radius, startAngle, roundedArcAngle);
  513.  
  514.  
  515.             // now increment the starting angle for the next arc.
  516.             startAngle += roundedArcAngle;
  517.         }
  518.  
  519.     }
  520.  
  521.  
  522.     /**
  523.      * This method draws a bar chart
  524.      * @param   Graphics    The graphics context to draw the chart
  525.      */
  526.     private void drawBarChart(Graphics g)
  527.     {
  528.         //draw the y axis title
  529.         drawYAxisTitle(g);
  530.  
  531.         // draw the x-axis title
  532.         drawXAxisTitle(g);
  533.  
  534.         // now draw the axis
  535.         drawAxis(g);
  536.  
  537.         // get the bounds
  538.         Rectangle rect = g.getClipBounds();
  539.  
  540.         // the number of rows or columns of data.
  541.         int groupCount = compareColumns ? rowCount : columnCount;
  542.  
  543.         // the number of items within a group of data
  544.         int groupSize = compareColumns ? columnCount : rowCount;
  545.  
  546.         // the width taken up by a group of data
  547.         int groupWidth = rect.width / groupCount;
  548.  
  549.         // get the gaps within and between groups
  550.         int interGroupGap = compareColumns ? getRowGap() : getColumnGap();
  551.         int intraGroupGap = compareColumns ? getColumnGap() : getRowGap();
  552.  
  553.         // the width of each bar of datum
  554.         int barWidth = (groupWidth - interGroupGap - groupSize*intraGroupGap) / groupSize;
  555.  
  556.         // get the maximum displayed value.
  557.         double max = getMax();
  558.  
  559.         // now draw the groups of data one by one.
  560.         for(int i=0; i<groupCount; i++)
  561.         {
  562.             //within each group a number of bars have to be drawn
  563.             for(int j=0; j<groupSize; j++)
  564.             {
  565.                 // this data value.
  566.                 double value;
  567.  
  568.                 if(compareColumns)
  569.                     value = data[i][j];
  570.                 else
  571.                     value = data[j][i];
  572.  
  573.                 // the height of this bar of datum
  574.                 int barHeight =(int) Math.rint( (rect.height * value ) / max);
  575.  
  576.                 //now change the color to the appropriate one for this bar
  577.                 g.setColor(chartColors[ j % chartColors.length ]);
  578.  
  579.                 //now draw this bar leaving appropriate widths between and within groups.
  580.                 g.fillRect(rect.x + i*groupWidth + j*(barWidth+intraGroupGap), rect.y+rect.height-barHeight,
  581.                     barWidth, barHeight);
  582.             }
  583.         }
  584.         
  585.     }
  586.  
  587.     /**
  588.      * This function will draw a line chart of the supplied data
  589.      * @param Graphics  The graphics context for drawing the chart
  590.      */
  591.     private void drawLineGraph(Graphics g)
  592.     {
  593.         // write the name of the Y Axis
  594.         drawYAxisTitle(g);
  595.  
  596.         // write the name of the X Axis
  597.         drawXAxisTitle(g);
  598.  
  599.         // draw the axes.
  600.         drawAxis(g);
  601.  
  602.         //the bounds this chart has to be drawn in
  603.         Rectangle rect = g.getClipBounds();
  604.  
  605.         // the maximum data value
  606.         double max = getMax();
  607.  
  608.         // the number of rows or columns of data.
  609.         int groupCount = compareColumns ? rowCount : columnCount;
  610.  
  611.         // the number of items within a group of data
  612.         int groupSize = compareColumns ? columnCount : rowCount;
  613.  
  614.         // the width taken up by a group of data
  615.         int groupWidth = rect.width / groupCount;
  616.  
  617.         // for each item within a group a line has to be drawn across all the groups
  618.         for(int j=0; j<groupSize; j++)
  619.         {
  620.             //set the color for this line
  621.             g.setColor(chartColors[j% chartColors.length]);
  622.  
  623.             // a -ve value suggests that there was no point before this
  624.             int last_height = -1;
  625.  
  626.             // draw a line for this data across each group
  627.             for(int i=0; i<groupCount; i++)
  628.             {
  629.                 // the data value
  630.                 double value = compareColumns ? data[i][j] : data[j][i];
  631.  
  632.                 // the height of this data value.
  633.                 int height = (int) Math.rint( (rect.height * value) / max );
  634.  
  635.                 // draw a circle around this value.
  636.                 g.drawOval(rect.x + i*groupWidth + (groupWidth/2) - CIRCLE_RADIUS, 
  637.                     rect.y + rect.height - height - CIRCLE_RADIUS, 2*CIRCLE_RADIUS, 2 * CIRCLE_RADIUS);
  638.  
  639.                 // if there was a data value before this then draw a line
  640.                 if(last_height >= 0)
  641.                     g.drawLine(rect.x + i*groupWidth - (groupWidth/2), rect.y + rect.height - last_height, 
  642.                         rect.x + i*groupWidth + (groupWidth/2), rect.y + rect.height - height);
  643.                 
  644.                 //update the last height for the next iteration.
  645.                 last_height = height;
  646.             }
  647.         }
  648.     }
  649.  
  650.     /**
  651.      * This function draws a column chart i.e. horizontal columns for each data value.
  652.      * @param   Graphics    The graphics context for drawing this.
  653.      */
  654.     private void drawColumnChart(Graphics g)
  655.     {
  656.         // write the name of the Y Axis
  657.         drawYAxisTitle(g);
  658.  
  659.         // write the name of the X Axis
  660.         drawXAxisTitle(g);
  661.  
  662.         // draw the axis for the column graph
  663.         drawInvertedAxis(g);
  664.  
  665.         // get the bounds
  666.         Rectangle rect = g.getClipBounds();
  667.  
  668.         // the number of rows or columns of data.
  669.         int groupCount = compareColumns ? rowCount : columnCount;
  670.  
  671.         // the number of items within a group of data
  672.         int groupSize = compareColumns ? columnCount : rowCount;
  673.  
  674.         // the height taken up by a group of data
  675.         int groupHeight = rect.height / groupCount;
  676.  
  677.         // get the gaps within and between groups
  678.         int interGroupGap = compareColumns ? getRowGap() : getColumnGap();
  679.         int intraGroupGap = compareColumns ? getColumnGap() : getRowGap();
  680.  
  681.         // the height of each column of datum
  682.         int columnHeight = (groupHeight - interGroupGap - groupSize*intraGroupGap) / groupSize;
  683.  
  684.         // get the maximum displayed value.
  685.         double max = getMax();
  686.  
  687.         // now draw the groups of data one by one.
  688.         for(int i=0; i<groupCount; i++)
  689.         {
  690.             //within each group a number of columns have to be drawn
  691.             for(int j=0; j<groupSize; j++)
  692.             {
  693.                 // this data value.
  694.                 double value;
  695.  
  696.                 if(compareColumns)
  697.                     value = data[i][j];
  698.                 else
  699.                     value = data[j][i];
  700.  
  701.                 // the width of this column of datum
  702.                 int columnWidth =(int) Math.rint( (rect.width * value ) / max);
  703.  
  704.                 //now change the color to the appropriate one for this bar
  705.                 g.setColor(chartColors[ j % chartColors.length ]);
  706.  
  707.                 //now draw this bar leaving appropriate widths between and within groups.
  708.                 g.fillRect(rect.x, rect.y + i*groupHeight + j*(columnHeight+intraGroupGap),
  709.                     columnWidth, columnHeight);                   
  710.             }
  711.         }
  712.  
  713.  
  714.     }
  715.  
  716.     /**
  717.      * This function draws the X-Axis title and
  718.      * clips the remaining drawing area.
  719.      * @param   Graphics    the graphics context of the calling chart
  720.      */
  721.     private void drawXAxisTitle(Graphics g)
  722.     {
  723.         Rectangle rect = g.getClipBounds();
  724.     
  725.         // the height of a text string
  726.         int charHeight = (g.getFontMetrics()).getHeight();
  727.  
  728.         // Now write the text
  729.         drawCenteredText(g, rect.x, rect.y + rect.height - charHeight, rect.width, charHeight, xAxisTitle);
  730.  
  731.         //now update the region which I can write to
  732.         // give an extra 2 pixels of padding
  733.         rect.height -= (2 + charHeight);
  734.  
  735.         //now clip off this region
  736.         g.setClip(rect.x, rect.y, rect.width, rect.height);
  737.     }
  738.  
  739.     /**
  740.      * This function draws the Y-Axis title and
  741.      * clips the remaining drawing area.
  742.      * @param   Graphics    the graphics context of the calling chart
  743.      */
  744.     private void drawYAxisTitle(Graphics g)
  745.     {
  746.         Rectangle rect = g.getClipBounds();
  747.     
  748.         // the widht of the axis title
  749.         int stringWidth = (g.getFontMetrics()).stringWidth(yAxisTitle);
  750.  
  751.         // write the text
  752.         drawCenteredText(g, rect.x, rect.y, stringWidth, rect.height, yAxisTitle);
  753.  
  754.         //now update the region which I can write to
  755.         // give an extra 2 pixels of padding
  756.         rect.x += 2 + stringWidth;
  757.         rect.width -= (2 + stringWidth);
  758.  
  759.         //now clip off the available region
  760.         g.setClip(rect.x, rect.y, rect.width, rect.height);
  761.     }
  762.  
  763.     /**
  764.      * This routine draws the axis. The x-axis corresponds to the labels on the rows or columns
  765.      * and the y-axis corresponds to the actual values.
  766.      * <p>
  767.      * This routine also marks the notches on the x-axis and puts numbers and labels.
  768.      * @param   Graphics    The graphics context in which to draw the axis
  769.      */
  770.     private void drawAxis(Graphics g)
  771.     {
  772.         // Get the area I can draw in.
  773.         Rectangle rect = g.getClipBounds();
  774.  
  775.         // get the font metrics
  776.         FontMetrics fm = g.getFontMetrics();
  777.  
  778.         // Get the maximum value to display
  779.         double max = getMax();
  780.  
  781.         // The extra padding to be given around the numbers
  782.         int pad = 2;
  783.  
  784.         // the width occupied on the y-axis
  785.         int width = pad + fm.stringWidth(new Double(max).toString()) + pad + NOTCH_WIDTH;
  786.  
  787.         // the height occupied on the x axis
  788.         int height = pad + fm.getHeight();
  789.  
  790.         // now draw the lines (at last :-)
  791.  
  792.         // the y-axis
  793.         g.drawLine(rect.x+width-1, rect.y, rect.x+width-1, rect.y + rect.height - height);
  794.         
  795.         // the x-axis
  796.         g.drawLine(rect.x+width-1, rect.y + rect.height - height, rect.x+rect.width-1, rect.y + rect.height - height);
  797.  
  798.  
  799.         // mark the notch on the y-axis.
  800.         g.drawLine(rect.x+width-1, rect.y, rect.x+width-NOTCH_WIDTH, rect.y);
  801.  
  802.         //mark the max number on the y-axis
  803.         drawCenteredText(g, rect.x, rect.y, width-NOTCH_WIDTH, fm.getHeight(), new Double(max).toString());
  804.  
  805.         // the number of groups of rows or columns (depending on compareColumns or compareRows)
  806.         int groupCount = compareColumns ? rowCount : columnCount;
  807.  
  808.         // the width of a group of rows or columns.
  809.         // i.e. the spacing between two notches on the x-axis
  810.         int groupWidth = (rect.width - width)/ groupCount;
  811.  
  812.         // mark the notches on the x-axis and draw the labels as well while you are at it.
  813.         for(int i=0; i<groupCount; i++)
  814.         {
  815.             // draw the notch
  816.             g.drawLine(rect.x+width+ (i+1)*groupWidth-1, rect.y+rect.height-height, 
  817.                        rect.x+width+ (i+1)*groupWidth-1, rect.y+rect.height-height + NOTCH_HEIGHT);
  818.  
  819.             // write the column or row name.
  820.             drawCenteredText(g, rect.x+width + i*groupWidth, rect.y+rect.height - height,
  821.                 groupWidth, height, compareColumns ? rowLabel[i] : columnLabel[i]);
  822.         }
  823.  
  824.  
  825.         // now before quitting update the clipping rectangle so nobody else overwrites on this stuff
  826.         g.setClip(rect.x+width, rect.y, rect.width-width, rect.height - height);
  827.  
  828.  
  829.     }
  830.  
  831.     /**
  832.      * This routine draws the inverted axis. The y-axis corresponds to the labels on the rows or columns
  833.      * and the x-axis corresponds to the actual values.
  834.      * <p>
  835.      * This routine also marks the notches on the y-axis and puts numbers and labels.
  836.      * @param   Graphics    The graphics context in which to draw the axis
  837.      */
  838.     private void drawInvertedAxis(Graphics g)
  839.     {
  840.         // Get the area I can draw in.
  841.         Rectangle rect = g.getClipBounds();
  842.  
  843.         // get the font metrics
  844.         FontMetrics fm = g.getFontMetrics();
  845.  
  846.         // The extra padding to be given around the numbers
  847.         int pad = 2;
  848.  
  849.         // The height occupied on the x-axis
  850.         int height = NOTCH_HEIGHT + pad + fm.getHeight() + pad;
  851.  
  852.         // the number of groups of rows or columns (depending on compareColumns or compareRows)
  853.         int groupCount = compareColumns ? rowCount : columnCount;
  854.  
  855.         // the arrays of labels
  856.         String[] labels = compareColumns ? rowLabel : columnLabel;
  857.  
  858.         // the height of a group of rows or columns.
  859.         // i.e. the spacing between two notches on the y-axis
  860.         int groupHeight = (rect.height - height)/ groupCount;
  861.  
  862.         // the maximum width of a label
  863.         int maxLabelWidth = 0;
  864.  
  865.         // find the maximum label width by going over all labels
  866.         for(int i=0; i<labels.length; i++)
  867.         {
  868.             maxLabelWidth = Math.max( maxLabelWidth, fm.stringWidth(labels[i]) );
  869.         }
  870.  
  871.         // the width occupied on the y-axis
  872.         int width = pad + maxLabelWidth + pad + NOTCH_WIDTH;
  873.  
  874.         // now draw the lines (at last :-)
  875.  
  876.         // the y-axis
  877.         g.drawLine(rect.x+width-1, rect.y, rect.x+width-1, rect.y + rect.height - height);
  878.         
  879.         // the x-axis
  880.         g.drawLine(rect.x+width-1, rect.y + rect.height - height, rect.x+rect.width-1, rect.y + rect.height - height);
  881.  
  882.  
  883.         // mark the notch on the x-axis.
  884.         g.drawLine(rect.x+rect.width-1, rect.y+rect.height-height, rect.x+rect.width-1, 
  885.             rect.y+rect.height-height+NOTCH_HEIGHT);
  886.  
  887.         // the maximum datavalue in the form of a string
  888.         String max = new Double(getMax()).toString();
  889.  
  890.         //mark the max number on the x-axis
  891.         g.drawString(max, rect.x + rect.width - fm.stringWidth(max), rect.y + rect.height - pad);
  892.         
  893.         // mark the notches on the y-axis and draw the labels as well while you are at it.
  894.         for(int i=0; i<groupCount; i++)
  895.         {
  896.             // draw the notch
  897.             g.drawLine(rect.x+width-1, rect.y+i*groupHeight, rect.x+width-NOTCH_WIDTH, rect.y+i*groupHeight);
  898.  
  899.             // write the column or row name.
  900.             drawCenteredText(g, rect.x, rect.y+i*groupHeight, width, groupHeight, labels[i]);
  901.         }
  902.  
  903.  
  904.         // now before quitting update the clipping rectangle so nobody else overwrites on this stuff
  905.         g.setClip(rect.x+width, rect.y, rect.width-width, rect.height - height);
  906.  
  907.     }
  908.  
  909.     
  910.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  911.     // Event handlers
  912.     //
  913.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  914.  
  915.  
  916.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  917.     // Accessor methods for the properties.
  918.     //
  919.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  920.  
  921.     //
  922.     // chartType
  923.     //
  924.     /**
  925.      * Get the type of the chart to be displayed.
  926.      * @return  int The chart type
  927.      */
  928.     public int getChartType()
  929.     {
  930.         return chartType;
  931.     }
  932.  
  933.     /**
  934.      * Set the type of the chart to be displayed/
  935.      * @param   int The chart type
  936.      */
  937.     public void setChartType(int chartType) throws PropertyVetoException
  938.     {
  939.         switch(chartType)
  940.         {
  941.         case BAR_CHART:
  942.         case PIE_CHART:
  943.         case LINE_GRAPH:
  944.         case COLUMN_CHART:
  945.             this.chartType = chartType;
  946.             break;
  947.  
  948.         default:
  949.             throw new PropertyVetoException("Unknown chart type",
  950.                 new PropertyChangeEvent(this, "chartType", new Integer(this.chartType), new Integer(chartType)));
  951.             
  952.         }
  953.  
  954.         // we need to resize
  955.         adjustSize();
  956.     }
  957.  
  958.     //
  959.     // showLegend
  960.     //
  961.  
  962.     /**
  963.      * Get whether the legend is to be shown or not.
  964.      * @return  boolean to show the legend or not.
  965.      */
  966.     public boolean getShowLegend()
  967.     {
  968.         return showLegend;
  969.     }
  970.  
  971.     /**
  972.      * Set whether or not the legend explaining the correspondence between chart colors and
  973.      * columns/rows should be shown.
  974.      * @param   boolean show the legend
  975.      */
  976.     public void setShowLegend(boolean showLegend)
  977.     {
  978.         this.showLegend = showLegend;
  979.  
  980.         // we need to resize
  981.         adjustSize();
  982.     }
  983.  
  984.     //
  985.     // compareColumns
  986.     //
  987.     
  988.     /**
  989.      * Get whether the chart is comparing columns or rows
  990.      * @return  boolean compares columns or rows.
  991.      */
  992.     public boolean getCompareColumns()
  993.     {
  994.         return compareColumns;
  995.     }
  996.  
  997.     /**
  998.      * Set whether the chart is comparing columns or rows
  999.      * @param  boolean compares columns (true) or rows (false)
  1000.      */
  1001.     public void setCompareColumns(boolean compareColumns)
  1002.     {
  1003.         this.compareColumns = compareColumns;
  1004.  
  1005.         // we need to resize
  1006.         adjustSize();
  1007.     }
  1008.  
  1009.     //
  1010.     // chartTitle
  1011.     //
  1012.  
  1013.     /**
  1014.      * Gets the chart title
  1015.      * @return  String  The title of the chart
  1016.      */
  1017.     public String getChartTitle()
  1018.     {
  1019.         return chartTitle;
  1020.     }
  1021.     
  1022.     /**
  1023.      * Sets the chart title
  1024.      * @param   String  The title of the chart
  1025.      */
  1026.     public void setChartTitle(String title)
  1027.     {
  1028.         chartTitle = new String(title);
  1029.  
  1030.         // we need to resize
  1031.         adjustSize();
  1032.     }
  1033.  
  1034.     //
  1035.     // xAxisTitle
  1036.     //
  1037.  
  1038.     /**
  1039.      * Sets the x-axis title
  1040.      * @param   String  The x-axis label on the chart
  1041.      */
  1042.     public void setXAxisTitle(String title)
  1043.     {
  1044.         xAxisTitle = title;
  1045.  
  1046.         repaint();
  1047.     }
  1048.  
  1049.  
  1050.     /**
  1051.      * Gets the X-Axis title
  1052.      * @return  String  The x-axis of the chart
  1053.      */
  1054.     public String getXAxisTitle()
  1055.     {
  1056.         return xAxisTitle;
  1057.     }
  1058.  
  1059.     //
  1060.     // yAxisTitle
  1061.     //
  1062.  
  1063.     /**
  1064.      * Sets the Y-axis title
  1065.      * @param   String  The y-axis label on the chart
  1066.      */
  1067.     public void setYAxisTitle(String title)
  1068.     {
  1069.         yAxisTitle = new String(title);
  1070.  
  1071.         // we need to resize the chart
  1072.         adjustSize();
  1073.     }
  1074.  
  1075.  
  1076.     /**
  1077.      * Gets the Y-Axis title
  1078.      * @return  String  The y-axis of the chart
  1079.      */
  1080.     public String getYAxisTitle()
  1081.     {
  1082.         return yAxisTitle;
  1083.     }
  1084.  
  1085.     //
  1086.     // caption
  1087.     //
  1088.  
  1089.     /**
  1090.      * Gets the caption of the chart
  1091.      * @return   String  The caption of the chart
  1092.      */
  1093.     public String getCaption()
  1094.     {
  1095.         return caption;
  1096.     }
  1097.  
  1098.     /**
  1099.      * Sets the caption of the chart
  1100.      * @param   String  The caption of the chart
  1101.      */
  1102.     public void setCaption(String cap)
  1103.     {
  1104.         caption = new String(cap);
  1105.  
  1106.         // resize the chart
  1107.         adjustSize();
  1108.     }
  1109.  
  1110.     //
  1111.     // column
  1112.     //
  1113.  
  1114.     /**
  1115.      * Get the current column number being edited
  1116.      * @return  int the column number
  1117.      */
  1118.     public int getColumn()
  1119.     {
  1120.         return column;
  1121.     }
  1122.  
  1123.     /**
  1124.      * Set the current column number being edited
  1125.      * @param   int the new column number
  1126.      */
  1127.     public void setColumn(int new_col) throws PropertyVetoException
  1128.     {
  1129.         if(new_col > columnCount || new_col < 0)
  1130.             throw new PropertyVetoException("Column number out of range !",
  1131.                 new PropertyChangeEvent(this, "column", new Integer(column), new Integer(new_col)));
  1132.  
  1133.         column = new_col;
  1134.     }
  1135.  
  1136.     //
  1137.     // row
  1138.     //
  1139.  
  1140.     /**
  1141.      * Get the current row number being edited
  1142.      * @return  int the row number
  1143.      */
  1144.     public int getRow()
  1145.     {
  1146.         return row;
  1147.     }
  1148.  
  1149.     /**
  1150.      * Set the current row number being edited
  1151.      * @param   int the new row number
  1152.      */
  1153.     public void setRow(int newRow) throws PropertyVetoException
  1154.     {
  1155.         if(newRow > rowCount || newRow < 0)
  1156.             throw new PropertyVetoException("Row number out of range !",
  1157.                 new PropertyChangeEvent(this, "row", new Integer(row), new Integer(newRow)));
  1158.  
  1159.         row = newRow;
  1160.     }
  1161.  
  1162.  
  1163.     //
  1164.     // columnCount
  1165.     //
  1166.  
  1167.     /**
  1168.      * Get the number of columns
  1169.      * @return  int     The number of columns
  1170.      */
  1171.     public int getColumnCount()
  1172.     {
  1173.         return columnCount;
  1174.     }
  1175.  
  1176.     /**
  1177.      * Set the column count
  1178.      * @param   int the new number of columns
  1179.      */
  1180.     public void setColumnCount(int newCount) throws PropertyVetoException
  1181.     {
  1182.         // The number of columns has to be non-negative. It may be zero.
  1183.         if(newCount < 0)
  1184.             throw new PropertyVetoException("The number of columns can't be negative !",
  1185.                 new PropertyChangeEvent(this, "columnCount", new Integer(columnCount), new Integer(newCount)));
  1186.  
  1187.  
  1188.         // the data array has to be updated row by row.
  1189.         for(int i=0; i<rowCount; i++)
  1190.         {
  1191.             // construct a new row of data containing the new number of columns
  1192.             double []newData = new double[newCount];
  1193.  
  1194.             // copy the original values into it.
  1195.             for(int j=0; j<columnCount && j<newCount; j++)
  1196.             {
  1197.                 newData[j] = data[i][j];
  1198.             }
  1199.  
  1200.             // replace the old row of data
  1201.             data[i] = newData;
  1202.         }
  1203.  
  1204.         // now create new  column labels.
  1205.         String[] newColumnLabel = new String[newCount];
  1206.  
  1207.         // now initialize this list with the previous labels and put default names for new columns
  1208.         for(int i=0; i<newCount; i++)
  1209.         {
  1210.             if(i<columnCount)
  1211.                 newColumnLabel[i] = columnLabel[i];
  1212.  
  1213.             else
  1214.                 newColumnLabel[i] = "C" + new Integer(i+1).toString();
  1215.         }
  1216.  
  1217.         // now update the column labels
  1218.         columnLabel = newColumnLabel;
  1219.         
  1220.         // update the count
  1221.         columnCount = newCount;
  1222.  
  1223.         // we need to resize
  1224.         adjustSize();
  1225.     }
  1226.  
  1227.     //
  1228.     // rowCount
  1229.     //
  1230.  
  1231.     /**
  1232.      * Get the number of rows of data
  1233.      * @return  int the number of rows of data.
  1234.      */
  1235.     public int getRowCount()
  1236.     {
  1237.         return rowCount;
  1238.     }
  1239.  
  1240.     /**
  1241.      * Set the number of rows of data. This number should be non-negative.
  1242.      * If this is less than the original number of rows then last extra rows will be lost
  1243.      * If this is more, then additional rows containing null values will be introduced.
  1244.      * @param   int the number of rows of data.
  1245.      */
  1246.     public void setRowCount(int newCount) throws PropertyVetoException
  1247.     {
  1248.         // do nothing if nothing needs to be done.
  1249.         if(newCount == rowCount)
  1250.             return;
  1251.  
  1252.         // The number of rows has to be non-negative. It may be zero.
  1253.         if(newCount < 0)
  1254.             throw new PropertyVetoException("The number of rows can't be negative !",
  1255.                 new PropertyChangeEvent(this, "rowCount", new Integer(rowCount), new Integer(newCount)));
  1256.  
  1257.         // The array of rows has to be reallocated. Though the rows themselves are not changed.
  1258.         double [][] newData = new double[newCount][];
  1259.  
  1260.         for(int i=0; i<newCount; i++)
  1261.         {
  1262.             // If this row of data already exists then just pick it up
  1263.             if(i<rowCount)
  1264.                 newData[i] = data[i];
  1265.  
  1266.             // else create a row of null values
  1267.             else
  1268.                 newData[i] = new double[columnCount];
  1269.         }
  1270.     
  1271.         // now update the data array
  1272.         data = newData;
  1273.  
  1274.         // now create new row labels.
  1275.         String[] newRowLabel = new String[newCount];
  1276.  
  1277.         // now initialize this list with the previous labels and put default names for new rows
  1278.         for(int i=0; i<newCount; i++)
  1279.         {
  1280.             if(i<rowCount)
  1281.                 newRowLabel[i] = rowLabel[i];
  1282.  
  1283.             else
  1284.                 newRowLabel[i] = "R" + new Integer(i+1).toString();
  1285.         }
  1286.  
  1287.         // now update the row labels
  1288.         rowLabel = newRowLabel;
  1289.  
  1290.         // and update the number of rows.
  1291.         rowCount = newCount;
  1292.         
  1293.         // we need to resize this chart
  1294.         adjustSize();
  1295.     }
  1296.  
  1297.     //
  1298.     // data
  1299.     //
  1300.  
  1301.  
  1302.     /**
  1303.      * Get the data entry corresponding to the current values of colum and row.
  1304.      * @return  double   the data value
  1305.      */
  1306.     public double getData()
  1307.     {
  1308.         // zero value of row or column is meaningless but allowed because
  1309.         // initially this value is possible.
  1310.         if(row == 0 || column == 0)
  1311.             return 0;
  1312.  
  1313.         return data[row-1][column-1];
  1314.     }
  1315.  
  1316.     /**
  1317.      * Set the value of the data element pointed to by the current value of row and column
  1318.      * @param   double   the new data value
  1319.      */
  1320.     public void setData(double newData)
  1321.     {
  1322.         // zero value of row or column is meaningless but allowed because
  1323.         // initially this value is possible.
  1324.          if(row == 0 || column == 0)
  1325.             return;
  1326.          
  1327.          data[row-1][column-1] = newData;
  1328.  
  1329.          repaint();
  1330.     }
  1331.  
  1332.     /**
  1333.      * Get the i,j th entry of the data i.e. i'th row j'th column
  1334.      * @param   int     the row number 1..
  1335.      * @param   int     the column number 1..
  1336.      * @return   double   the i,j th data entry
  1337.      */
  1338.     public double getData(int i, int j)
  1339.     {
  1340.         // zero value of row or column is meaningless but allowed because
  1341.         // initially this value is possible.
  1342.          if(i == 0 || j == 0)
  1343.             return 0;
  1344.  
  1345.         return data[i-1][j-1];
  1346.     }
  1347.  
  1348.     /**
  1349.      * Set the i,j th entry of the data.
  1350.      * @param   int     the row number
  1351.      * @param   int     the column number
  1352.      * @param   double   the new data value
  1353.      */
  1354.     public void setData(int i, int j, double newData)
  1355.     {
  1356.         // zero value of row or column is meaningless but allowed because
  1357.         // initially this value is possible.
  1358.         if(i == 0 || j == 0)
  1359.             return;
  1360.  
  1361.        data[i-1][j-1] = newData;
  1362.  
  1363.        repaint();
  1364.     }
  1365.  
  1366.     //
  1367.     // columnLabel
  1368.     //
  1369.  
  1370.     /**
  1371.      * Get the label of the column pointed to by the current column number.
  1372.      * @return   String The label of this column
  1373.      */
  1374.     public String getColumnLabel()
  1375.     {
  1376.         if(column == 0)
  1377.             return "";
  1378.  
  1379.         return columnLabel[column-1];
  1380.     }
  1381.  
  1382.     /**
  1383.      * Set the label of the column pointed to by the current column number.
  1384.      * @param   String  The column label
  1385.      */
  1386.     public void setColumnLabel(String newLabel)
  1387.     {
  1388.         if(column == 0)
  1389.             return ;
  1390.  
  1391.         columnLabel[column-1] = new String(newLabel);
  1392.  
  1393.         repaint();
  1394.     }
  1395.  
  1396.     /**
  1397.      * Get the label of the i'th column
  1398.      * @param   int The column number
  1399.      * @return  String The label of this column
  1400.      */
  1401.     public String getColumnLabel(int i)
  1402.     {
  1403.         if(i == 0)
  1404.             return "";
  1405.  
  1406.         return columnLabel[i-1];
  1407.     }
  1408.  
  1409.     /**
  1410.      * Set the label of the i'th column
  1411.      * @param   int     The column number
  1412.      * @param   String  The column label
  1413.      */
  1414.     public void setColumnLabel(int i, String newLabel)
  1415.     {
  1416.         if(i == 0)
  1417.             return ;
  1418.  
  1419.         columnLabel[i-1] = new String(newLabel);
  1420.  
  1421.         repaint();
  1422.     }
  1423.  
  1424.     //
  1425.     // rowLabel
  1426.     //
  1427.  
  1428.     /**
  1429.      * Get the label of the row pointed to by the current row number.
  1430.      * @return   String The label of this row
  1431.      */
  1432.     public String getRowLabel()
  1433.     {
  1434.         if(row == 0)
  1435.             return "";
  1436.  
  1437.         return rowLabel[row-1];
  1438.     }
  1439.  
  1440.     /**
  1441.      * Set the label of the row pointed to by the current row number.
  1442.      * @param   String  The row label
  1443.      */
  1444.     public void setRowLabel(String newLabel)
  1445.     {
  1446.         if(row == 0)
  1447.             return ;
  1448.  
  1449.         rowLabel[row-1] = new String(newLabel);
  1450.  
  1451.         repaint();
  1452.     }
  1453.  
  1454.     /**
  1455.      * Get the label of the i'th row.
  1456.      * @param   int The row number
  1457.      * @return  String The label of this row
  1458.      */
  1459.     public String getRowLabel(int i)
  1460.     {
  1461.         if(i == 0)
  1462.             return "";
  1463.  
  1464.         return rowLabel[i-1];
  1465.     }
  1466.  
  1467.     /**
  1468.      * Set the label of the i'th row
  1469.      * @param   int The row number
  1470.      * @param   String  The row label
  1471.      */
  1472.     public void setRowLabel(int i, String newLabel)
  1473.     {
  1474.         if(i == 0)
  1475.             return ;
  1476.  
  1477.         rowLabel[i-1] = new String(newLabel);
  1478.  
  1479.         repaint();
  1480.     }
  1481.  
  1482.     //
  1483.     // rowGap
  1484.     //
  1485.  
  1486.     /**
  1487.      * Set the gap between two rows of data
  1488.      * @param   int gap
  1489.      */
  1490.     public void setRowGap(int gap) throws PropertyVetoException
  1491.     {
  1492.         if(gap <0)
  1493.             throw new PropertyVetoException("The gap can't be negative !",
  1494.                 new PropertyChangeEvent(this, "rowGap", new Integer(rowGap), new Integer(gap) ) );
  1495.  
  1496.         rowGap = gap;
  1497.  
  1498.         // resize the graph
  1499.         adjustSize();
  1500.     }
  1501.  
  1502.     /**
  1503.      * Get the gap between two rows of data
  1504.      * @param   int gap
  1505.      */
  1506.     public int getRowGap()
  1507.     {
  1508.         return rowGap;
  1509.     }
  1510.  
  1511.     //
  1512.     // columnGap
  1513.     //
  1514.  
  1515.     /**
  1516.      * Set the gap between two columns of data
  1517.      * @param   int gap
  1518.      */
  1519.     public void setColumnGap(int gap) throws PropertyVetoException
  1520.     {
  1521.         if(gap <0)
  1522.             throw new PropertyVetoException("The gap can't be negative !",
  1523.                 new PropertyChangeEvent(this, "columnGap", new Integer(columnGap), new Integer(gap) ) );
  1524.  
  1525.         columnGap = gap;
  1526.  
  1527.         // we might need more space
  1528.         adjustSize();
  1529.     }
  1530.  
  1531.     /**
  1532.      * Get the gap between two columns of data
  1533.      * @param   int gap
  1534.      */
  1535.     public int getColumnGap()
  1536.     {
  1537.         return columnGap;
  1538.     }
  1539.  
  1540.  
  1541.     //
  1542.     // font
  1543.     //
  1544.     
  1545.     /**
  1546.      * Set the font of the chart.
  1547.      * <p>
  1548.      * This has to be overridden because if the font changes the chart has to
  1549.      * be resized
  1550.      * @param   Font    the new font
  1551.      */
  1552.     public void setFont(Font f)
  1553.     {
  1554.         super.setFont(f);
  1555.  
  1556.         // resize the chart
  1557.         adjustSize();
  1558.     }
  1559.  
  1560.     //
  1561.     // max
  1562.     //
  1563.  
  1564.     /**
  1565.      * The maximum data-value to be displayed. This is made as the nearest largest number which has 
  1566.      * one non-zero digit followed by all zeroes.
  1567.      * <p>
  1568.      * Example: If the maximum data value is 568 this returns 600
  1569.      *
  1570.      * @return  double  The largest value to be marked on the axis.
  1571.      */
  1572.     public double getMax()
  1573.     {
  1574.         // find the maximum value.
  1575.         double max = 0;
  1576.  
  1577.         for(int i=0; i<rowCount; i++)
  1578.         {
  1579.             for(int j=0; j<columnCount; j++)
  1580.             {
  1581.                 max = Math.max(max, data[i][j]);
  1582.             }
  1583.         }
  1584.  
  1585.         if(max == 0)
  1586.             return 1;
  1587.  
  1588.         // The largest power of 10 which is less than max
  1589.         int powerOf10 = (int) Math.pow(10, (int)logBase10(max));
  1590.  
  1591.         // if max is of the desired form then return rigth away
  1592.         if( (max % powerOf10) == 0 )
  1593.             return max;
  1594.  
  1595.         // Now drop all but the leftmost digits in max
  1596.         max = (int) (max / powerOf10 );
  1597.  
  1598.         // add one to this unless max itself is of the desired form
  1599.         max ++;
  1600.  
  1601.         // and now blow it back to its original length
  1602.         max *= powerOf10;
  1603.  
  1604.         return max;
  1605.  
  1606.     }
  1607.  
  1608.     /**
  1609.      * The data file name specifies the name of an Excel data file in text format.
  1610.      * This file will be used to pick up the chart data.
  1611.      * @param   String  dataFileName
  1612.      */
  1613.     public void setDataFileName(String fileName) throws PropertyVetoException
  1614.     {
  1615.         if( fileName.equals("") )
  1616.         {
  1617.             return;
  1618.         }
  1619.         File testFile = new File(fileName);
  1620.         FileReader reader;
  1621.  
  1622.         try
  1623.         {
  1624.             reader = new FileReader(testFile);
  1625.         }
  1626.         catch(FileNotFoundException ex)
  1627.         {
  1628.             throw new PropertyVetoException("The specified file was not found",
  1629.                 new PropertyChangeEvent(this, "dataFileName", getDataFileName(), fileName ) );
  1630.         }
  1631.  
  1632.         StreamTokenizer data = new StreamTokenizer( reader );
  1633.  
  1634.         data.eolIsSignificant(true);
  1635.         data.ordinaryChar('/');
  1636.  
  1637.         boolean EOFReached = false;
  1638.         int lastChar;
  1639.  
  1640.         int rowsRead = 0;
  1641.         int colsRead = 0;
  1642.         int maxCols = 0;
  1643.         Vector dataStore = new Vector();
  1644.  
  1645.         try
  1646.         {
  1647.             while( !EOFReached )
  1648.             {
  1649.                 switch((lastChar = data.nextToken()))
  1650.                 {
  1651.                 case StreamTokenizer.TT_EOF:
  1652.                     EOFReached = true;
  1653.                     break;
  1654.  
  1655.                 case StreamTokenizer.TT_EOL:
  1656.                     rowsRead ++;
  1657.                     
  1658.                     // this condition should occur only on the first line
  1659.                     if( maxCols == 0)
  1660.                     {
  1661.                         maxCols = colsRead;
  1662.                     }
  1663.  
  1664.                     if( colsRead != maxCols )
  1665.                     {
  1666.                         throw new PropertyVetoException("Wrong number of columns in row "+rowsRead,
  1667.                             new PropertyChangeEvent(this, "dataFileName", getDataFileName(), fileName ) );
  1668.                     }
  1669.                     colsRead = 0;
  1670.                     break;
  1671.  
  1672.                 case StreamTokenizer.TT_NUMBER:
  1673.                     dataStore.addElement( new Double(data.nval) );
  1674.                     colsRead++;
  1675.                     break;
  1676.  
  1677.                 default:
  1678.                     throw new PropertyVetoException("The file has a bad character "+lastChar,
  1679.                         new PropertyChangeEvent(this, "dataFileName", getDataFileName(), fileName ) );
  1680.  
  1681.                 }
  1682.             }
  1683.  
  1684.             reader.close();
  1685.         }
  1686.         catch(IOException ex)
  1687.         {
  1688.             throw new PropertyVetoException("I/O Exception reading in file",
  1689.                 new PropertyChangeEvent(this, "dataFileName", getDataFileName(), fileName ) );
  1690.         }
  1691.  
  1692.  
  1693.         // if there are not errors in this file then finally except it.
  1694.  
  1695.         // put the data values in the chart.        
  1696.         setColumnCount(maxCols);
  1697.         setRowCount(rowsRead);
  1698.         for(int i=0; i<rowsRead; i++)
  1699.         {
  1700.             for(int j=0; j<maxCols; j++)
  1701.             {
  1702.                 setData(i+1, j+1, ((Double) dataStore.elementAt(j+i*maxCols)).doubleValue() );               
  1703.             }
  1704.         }
  1705.         
  1706.         dataFile = testFile;
  1707.     }
  1708.  
  1709.     /**
  1710.      * The data file name specifies the name of an Excel data file in text format.
  1711.      * @param   String  dataFileName
  1712.      */
  1713.      public String getDataFileName()
  1714.      {
  1715.          if( dataFile != null )
  1716.          {
  1717.              return dataFile.getAbsolutePath();
  1718.          }
  1719.  
  1720.          else
  1721.          {
  1722.              return "";
  1723.          }
  1724.      }
  1725.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  1726.     // Utility functions
  1727.     // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  1728.  
  1729.     /** 
  1730.      * Calculate the logarithm of a number to the base 10
  1731.      * @param   double   number
  1732.      * @return  double   the log to base 10
  1733.      */
  1734.     private double logBase10(double number)
  1735.     {
  1736.         return  Math.log(number) / Math.log(10.0);
  1737.     }
  1738.  
  1739.  
  1740. }