Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jplot/Graph.java


1   /*
2    * JPLOT  -- Java Plotting Interface for any programme or
3    *           as an independent GUI
4    * 
5    * Copyright (C) 1999 Jan van der Lee
6    *
7    * This program is free software; you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation; either version 2
10   * of the License, or (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Public License
18   * along with this program; if not, write to the Free Software
19   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
20   * 02111-1307, USA.
21   *
22   * Send bugs, suggestions or queries to <jplot@cig.ensmp.fr>
23   * The latest releases are found at 
24   * http://www.cig.ensmp.fr/~vanderlee/jplot.html
25   *
26   * Initally developed for use by the Centre d'Informatique Geologique 
27   * Ecole des Mines de Paris, Fontainebleau, France.
28   */
29  
30  package jplot;
31  
32  import javax.swing.*;
33  import java.awt.*;
34  import java.awt.image.*;
35  import java.text.*;
36  import java.math.*;
37  import java.awt.geom.*;
38  import java.awt.event.*;
39  import javax.swing.border.*;
40  import java.util.*;
41  import java.awt.print.*;
42  
43  /**
44   * The <code>Graph</code> class builds a panel which displays a
45   * graph according to the data stored in the {@link DataArray}.
46   * Extends JPanel since the graph is returned in a panel (it can
47   * be used in other applications or embedded in a frame. Implements
48   * Printable, so we can print the graph, and ComponentListener, needed
49   * to listen to a resize event.
50   *
51   * This class should be subclassed in order to build a specific
52   * graph type (i.e. a 2D graph, piper diagram or 3D, later)
53   *
54   * @version 24/08/99
55   * @author  J. van der Lee
56   */
57  public abstract class Graph extends JPanel implements Printable {
58  
59    // vector of DataArray objects, containing
60    // the arrays of datapoints forming the graph:
61    Vector data;
62  
63    // axes and tic-label stuff:
64    final protected int X = GraphPars.X_AXIS;
65    final protected int Y = GraphPars.Y_AXIS;
66    final protected int Na = GraphPars.NAXES;
67    protected double[] axisLength = new double [Na];
68    protected double leftMargin;
69    protected double rightMargin;
70    protected double bottomMargin;
71    protected double topMargin;
72  
73    // variables used to calculate tic-seperation distances:
74    protected String[][] ticLabel = new String[Na][];
75    protected double[] ticLength = new double [Na];
76    protected double[] diff = new double [Na];
77    protected double[] inv = new double [Na];
78    protected int[] numberOfTics = new int [Na];
79  
80    // separation between tic-labels and the axis:
81    protected double sep[] = new double [Na];
82  
83    // legend stuff:
84    protected boolean legendActive = false;
85    protected boolean somethingActive = false;
86    protected final float xSep=5.0f;
87    protected final float ySep=3.0f;
88  
89    // this class contains all the parameters
90    // which are used to plot the graph.
91    protected GraphPars gp;
92  
93    // legend:
94    protected double ldy;    // distance between two legend labels
95    protected double keyLen; // length of the key (drawing style, e.g. a line)
96    protected double legendWidth;
97    protected double legendHeight;
98  
99    // size of the graph panel
100   protected Dimension panelSize;
101   protected Dimension panelSizeForPrinter;
102 
103   // dimension of the panel:
104   protected int width;
105   protected int height;
106 
107   // colors of the label drag'n drop box:
108   private final Color boxColor = Color.gray;
109 
110   // the value of Ln(10), need it a few times for logscales:
111   private final static double LNTEN = Math.log(10.0);
112 
113   protected Line2D.Double line = new Line2D.Double();
114   protected Rectangle2D.Double rect = new Rectangle2D.Double();
115 
116   // coordinates used for dragging and zooming.
117   protected int x1, y1, boxWidth, boxHeight;
118   protected int xStart, yStart;
119 
120   protected BufferedImage bi;
121   private boolean initializing;
122 
123   // variables used by a piper diagram
124   protected double piperSep;           // separation gap between rectangles
125   protected double normalSep;        // (vertical) normal of the gap
126   protected double triangleBottom;   // length of bottom of a triangle
127   protected double triangleSide;     // length of the sides of a triangle
128   protected double triangleHeight;   // height of a triangle
129   protected double borderWidth;      // white space between panel and border
130   protected double arc;              // angle of the triangle (lower corners)
131   protected double tan, sin, cos;    // geometrical coefficients
132 
133   private JPlot jplot;
134 
135   /**
136    * Main constructor.
137    * @param gp objects containing the parameters used to draw this graph.
138    * @param d dimension of the current graph.
139    */
140   public Graph(JPlot jp, GraphPars gp) {
141     jplot = jp;
142     if (JPlot.debug) System.out.print("Initializing Graph(gp)...");
143     initializing = true;
144     setLayout(new FlowLayout(FlowLayout.RIGHT));
145     setBorder(new BevelBorder(BevelBorder.LOWERED));
146     addMouseListener(new GraphMouseListener());
147     addMouseMotionListener(new DragListener());
148     this.gp = gp;
149     height = gp.getPanelHeight();
150     width = gp.getPanelWidth();
151     leftMargin = gp.getLeftMargin();
152     rightMargin = gp.getRightMargin();
153     bottomMargin = gp.getBottomMargin();
154     topMargin = gp.getTopMargin();
155     panelSize = gp.getDimension();
156     keyLen = 25.0f;
157     for (int k=0; k<Na; k++) {
158       ticLabel[k] = new String [gp.getMaxNumberOfTics()];
159     }
160     bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);    
161     if (JPlot.debug) System.out.println("done");
162   }
163   
164   /** 
165    * Get the minimum size of this component.
166    * This is the panel size defined by width and height.
167    * @return the minimum size.
168    */
169   public Dimension getMinimumSize() {
170     return new Dimension(width,height);
171   }  
172   
173   /** 
174    * Get the preferred size of this component.
175    * This is the panel size defined by width and height
176    * @return the preferred size.
177    */
178   public Dimension getPreferredSize() {
179     return new Dimension(width,height);
180   }
181   
182   /**
183    * Sets the new location of the top-left corner (x,y) and the size
184    * in terms of the width (w) and height (h).
185    * @param x the new x-coordinate.
186    * @param y the new y-coordinate.
187    * @param w the new width.
188    * @param h the new height.
189    */
190   /*
191   public void setBounds(int x, int y, int w, int h) {
192     width = w;
193     height = h;
194     super.setBounds(x,y,width,height);
195     //updateGraph();
196     //System.out.println("@@@@@@@@@@@@@ resized");
197   }
198   */
199 
200   /** 
201    * Set the size of the plot.
202    * @param w the width, in pixels.
203    * @param h the height, in pixels.
204    */
205   /*
206   public void setSize(int w, int h) {
207     width = w;
208     height = h;
209     super.setSize(width,height);
210   }
211   */
212 
213   /*
214    * This method is called as the user is zooming and releases the
215    * mousebutton. It rescales the plot according to the selected
216    * box boundaries.
217    * @param x The final x position.
218    * @param y The final y position.
219    */
220   private synchronized void finishZoom(int x1, int y1, int x2, int y2) {
221     //Graphics g = getGraphics();
222   }
223 
224   /*
225    * @param x x position.
226    * @param y y position.
227    */
228   private synchronized void zoomRect(int x, int y) {
229     Graphics g = getGraphics();
230   }
231 
232   /**
233    * Returns the log10 of a double value.
234    * @param x value 
235    * @return log10(x)
236    */
237   public static double log10(double x) {
238     return Math.log(x)/LNTEN;
239   }
240 
241   /**
242    * Calculation of the separation length between tics.
243    * Some smart rounding algorithms are needed to get the
244    * scaling properly in case of data going to 10.3 or so...
245    * Usefull stuff stolen from the Gnuplot sources, thank you guys!!
246    */
247   protected double calculateTicSep(double min, double max) {
248     double xnorm, tic, posns;
249     double lrange = log10(Math.abs(min-max));
250     double fl = Math.floor(lrange);
251     xnorm = Math.pow(10.0,lrange-fl);
252     posns = gp.getMaxNumberOfTics()/xnorm;
253 
254     if (posns > 40) tic = 0.05;       // eg 0, .05, .10, ...
255     else if (posns > 20) tic = 0.1;   // eg 0, .1, .2, ...
256     else if (posns > 10) tic = 0.2;   // eg 0,0.2,0.4,...
257     else if (posns > 4) tic = 0.5;    // 0,0.5,1,
258     else if (posns > 1) tic = 1;      // 0,1,2,....
259     else if (posns > 0.5) tic = 2;    // 0, 2, 4, 6
260     else tic = Math.ceil(xnorm);
261     tic *= Math.pow(10.0,fl);
262     /*
263     if (JPlot.debug) {
264       System.out.print("calculated tic-length = " + tic);
265       System.out.println(" for min = " + min + ", max = " + max);
266     }
267     */
268     return tic;
269   }                
270 
271   /**
272    * Return the number of digits required to display the
273    * given number.  If more than 15 digits are required, 
274    * 15 is returned). 
275    */
276   public static int getNumDigits(double num) {
277     int numDigits = 0;
278     if (num == 0.0) return 0;
279     while (numDigits <= 15 && Math.abs(Math.floor(num)/num-1.0) > 1e-10) {
280     //while (numDigits <= 15 && num != Math.floor(num)) {
281       num *= 10.0;
282       numDigits++;
283     }
284     return numDigits;
285   }
286 
287   /**
288    * formats a double precision number such that it is
289    * correctly rounded for output. Kind of pre-processor for
290    * all number for output.
291    * @param num number to be formatted
292    * @param n number of digits (accuracy) after the decimal point.
293    * @return the formatted number in a string. 
294    */ 
295   public static String formatNumber(double num, int n) {
296     int maxFloat = 6;
297     if (num != 0.0) {
298       int exp = (int)Math.floor(log10(num));
299       int x = Math.abs(exp) + n;
300       BigDecimal bd = new BigDecimal(num);
301       if (x > maxFloat) {
302   if (exp > 0) bd = bd.movePointLeft(exp);
303   else bd = bd.movePointRight(Math.abs(exp));
304       }
305       else {
306   if (exp < 0) n = x; 
307   else n = x - Math.abs(exp);
308       }
309       bd = bd.setScale(n,BigDecimal.ROUND_HALF_EVEN);
310       int nn = getNumDigits(bd.doubleValue());
311       if (nn < n) bd = bd.setScale(nn);
312       String res = bd.toString();
313       if (x > maxFloat) res += "e" + exp;
314       return res;
315     }
316     else return "0";
317   }
318 
319   /**
320    * Determine the axis labels, if needed. These are numbers
321    * which are translated in Strings. The length is evalulated
322    * and used to set the left and bottom margins.
323    */
324   abstract void makeTicLabels();
325 
326   /**
327    * This method updates the margins as a function of the label 
328    * widths and the axes texts. Also sets the default positions
329    * of the text labels.
330    */
331   protected void updateMargins() {
332     double xlHeight = 0.0;
333     double ylWidth = 0.0;
334     double tHeight = 0.0;
335 
336     if (JPlot.debug) System.out.print("updating margins...");
337 
338     // we have to find the height of the X- and Y labels and title
339     // in order to get the margins right:
340     for (Enumeration e=(gp.getLabels()).elements(); e.hasMoreElements();) {
341       GraphLabel gl = (GraphLabel) e.nextElement();
342       if (gl.equals(GraphLabel.XLABEL)) xlHeight = gl.getHeight()+ySep;
343       else if (gl.equals(GraphLabel.YLABEL)) ylWidth = gl.getWidth()+xSep;
344       else if (gl.equals(GraphLabel.TITLE)) tHeight = gl.getHeight()+ySep;
345       if (gl.equals(GraphLabel.PIPER_X1)) xlHeight = gl.getHeight()-3.0;
346     }
347 
348     double maxYLabel = 0.0;
349 
350     // initialize the margins with the default (minimum) values:
351     borderWidth = gp.getBoxOffset();
352     leftMargin = gp.getLeftMargin() + borderWidth;
353     bottomMargin = gp.getBottomMargin() + xlHeight + borderWidth;
354     rightMargin = gp.getRightMargin() + borderWidth;
355     topMargin = gp.getTopMargin() + tHeight + borderWidth;
356 
357     // add some extra space to the bottom-margin for the tic-labels:
358     if (gp.drawTicLabels(X)) bottomMargin += ySep*2;
359 
360     // add some extra space to the left-margin in order to account
361     // for the y-labels (take the label with the maximum length):
362     if (gp.drawTicLabels(Y)) {
363       FontMetrics fm = getFontMetrics(gp.getTicFont(Y));
364       for (int i=0; i<numberOfTics[Y]; i++) {
365   double yl = fm.stringWidth(ticLabel[Y][i]);
366   if (yl > maxYLabel) maxYLabel = yl;
367   yl += xSep*2 + borderWidth;
368   if (yl > leftMargin) leftMargin = yl;
369       }
370     }
371     leftMargin += ylWidth;
372 
373     // axes lengths must be something like this:
374     axisLength[X] = width - leftMargin - rightMargin;
375     axisLength[Y] = height - topMargin - bottomMargin;
376 
377     // ... except if someone has fixed the axes ratio:
378     double r = axisLength[Y]/axisLength[X];
379     if (gp.useAxesRatio()) {
380       
381       // at this stage, both axes are at maximum length. Let 'ratio' 
382       // be the ratio specified by the user. If r > ratio, then we
383       // must adapt the Y-axis-length:
384       if (r > gp.getAxesRatio()) {
385   axisLength[Y] = gp.getAxesRatio()*axisLength[X];
386   topMargin = height - bottomMargin - axisLength[Y];
387       }
388       else if (r < gp.getAxesRatio()) {
389   axisLength[X] = gp.getAxesRatio()*axisLength[Y];
390   rightMargin = width - leftMargin - axisLength[X];
391       }
392     }
393     else gp.setAxesRatio(r);
394     //gp.setAxisLength(X,axisLength[X]);
395     //gp.setAxisLength(Y,axisLength[Y]);
396 
397 
398     if (gp.getGraphType() == gp.GRAPHTYPE_PIPER) {
399       if (!gp.useLegendPos()) {
400   gp.setLegendPos(borderWidth,borderWidth+3.0f);
401       }
402 
403       // needed to convert data scale to graphical pixel scaling
404       tan = 2.0*axisLength[Y]/axisLength[X];
405       arc = Math.atan(tan);
406       sin = Math.sin(arc);
407       cos = Math.cos(arc);
408       normalSep = piperSep*sin;
409       
410       triangleBottom = (axisLength[X]-piperSep)/2.0;
411       triangleHeight = (axisLength[Y]-normalSep)/2.0;
412       triangleSide   = triangleHeight/sin;
413       sep[X] = triangleBottom/(numberOfTics[X]-1);
414       sep[Y] = triangleHeight/(numberOfTics[Y]-1);
415       if (tHeight > 0.0) tHeight += 10.0;
416     }
417     else {
418       if (!gp.useLegendPos()) {
419   gp.setLegendPos(leftMargin + ticLength[Y] + 7.0f,
420       topMargin + 7.0f);
421       }
422       sep[X] = axisLength[X]/(numberOfTics[X]-1);
423       sep[Y] = axisLength[Y]/(numberOfTics[Y]-1);
424     }
425     ticLength[X] = gp.getTicLength(X)*width;
426     ticLength[Y] = gp.getTicLength(Y)*width;
427     setDefaultLabelPositions(maxYLabel);
428     if (JPlot.debug) System.out.println("done.");
429   }
430 
431   /*
432    * Set the label positions to default values.
433    */
434   private void setDefaultLabelPositions(double m) {
435     int numOfOtherLabels=0;
436     if (gp.getGraphType() == gp.GRAPHTYPE_PIPER) m *= 0.7;
437     for (Enumeration e=gp.getLabels().elements(); e.hasMoreElements();) {
438       GraphLabel gl = (GraphLabel) e.nextElement();
439       if (!gl.usePosition()) {
440   double x=0.0;
441   double y=0.0;
442   if (gl.equals(GraphLabel.XLABEL)) {
443     x = leftMargin+axisLength[X]/2;
444     y = topMargin+axisLength[Y]+(bottomMargin-borderWidth)/2+ySep;
445   }
446   else if (gl.equals(GraphLabel.YLABEL)) {
447     x = borderWidth + (leftMargin-borderWidth-m)/2;
448     y = topMargin+axisLength[Y]/2;
449   }
450   else if (gl.equals(GraphLabel.TITLE)) {
451     x = leftMargin+axisLength[X]/2;
452     y = topMargin/2;
453   }
454   else if (gl.equals(GraphLabel.OTHER)) {
455     x = leftMargin + 10.0f + gl.getWidth()/2;
456     y = topMargin + axisLength[Y] - 14.0f*(1+numOfOtherLabels);
457     numOfOtherLabels++;
458   }
459   else if (gl.equals(GraphLabel.PIPER_X1)) {
460     x = leftMargin+triangleBottom/2;
461     y = topMargin+axisLength[Y]+(bottomMargin-borderWidth)/2+ySep;
462   }
463   else if (gl.equals(GraphLabel.PIPER_X2)) {
464     x = leftMargin+axisLength[X]-triangleBottom/2;
465     y = topMargin+axisLength[Y]+(bottomMargin-borderWidth)/2+ySep;
466   }
467   else if (gl.equals(GraphLabel.PIPER_Y1)) {
468     gl.setRotation(-arc);
469     double xx = m+sin*(xSep+gl.getTextHeight());
470     x = leftMargin+triangleBottom/4-xx;
471     y = topMargin+axisLength[Y]-triangleHeight/2-xx/tan;
472   }
473   else if (gl.equals(GraphLabel.PIPER_Y2)) {
474     gl.setRotation(arc);
475     double xx = m+cos*(xSep+gl.getTextHeight());
476     x = leftMargin+axisLength[X]-triangleBottom/4+xx;
477     y = topMargin+axisLength[Y]-triangleHeight/2-xx/tan;
478   }
479   else if (gl.equals(GraphLabel.PIPER_Y3)) {
480     gl.setRotation(-arc);
481     double xx = m+sin*(xSep+gl.getTextHeight());
482     x = leftMargin+axisLength[X]/2-triangleBottom/4-xx;
483     y = topMargin+triangleHeight/2-xx/tan;
484   }
485   else if (gl.equals(GraphLabel.PIPER_Y4)) {
486     gl.setRotation(arc);
487     double xx = m+cos*(xSep+gl.getTextHeight());
488     x = leftMargin+axisLength[X]/2+triangleBottom/4+xx;
489     y = topMargin+triangleHeight/2-xx/tan;
490   }
491   else if (gl.equals(GraphLabel.DATA)) {
492     x = toX(gl.getXPos());
493     y = toY(gl.getYPos());
494     gl.setUsePosition(true);
495     //gl.setID(GraphLabel.OTHER);
496   }
497   gl.setLocation(x-gl.getWidth()/2,y-gl.getHeight()/2);
498   gl.setRotation(gl.getRotation());
499       }
500     }
501   }
502 
503   /**
504    * Find the minimum and maximum values for X- and Y axes.
505    * @param axis axis for which the min and max are needed.
506    * @param vector array containing the data set.
507    */
508   abstract boolean setMinMax(int axis, Vector data);
509   
510   /*
511    * Plot the labels, ALL the labels on the graph.
512    * @param g2 a Graphics2D instance (canvas).
513    */
514   protected void plotLabels(Graphics2D g2) {
515     for (Enumeration e=gp.getLabels().elements(); e.hasMoreElements();) {
516       GraphLabel gl = (GraphLabel) e.nextElement();
517       if (!gl.hide() && !gl.getText().equals("")) {
518   g2.setColor(gl.getColor());
519   float x = (float)gl.getXPos();
520   float y = (float)gl.getYPos();
521   g2.rotate(gl.getRotation(),x,y);
522   g2.drawString(gl.getCharIterator(),x,y);
523   g2.rotate(-gl.getRotation(),x,y);
524       }
525     }
526   }
527 
528   /*
529    * Finds the height and width of the box which envelops the legend.
530    * This box is used for dragging the legend.
531    * @param data vector with data arrays.
532    */
533   protected void getLegendBox(Vector data) {
534     String s = "";
535     int k=0;
536     for (Enumeration e=data.elements(); e.hasMoreElements();) {
537       DataArray da = (DataArray)e.nextElement();
538       if (da.drawLegend()) {
539   if (da.getName().length() > s.length()) s = da.getName();
540   k++;
541       }
542     }
543     FontMetrics fm = getFontMetrics(gp.getLegendFont());
544     legendWidth = fm.stringWidth(s) + keyLen + xSep;
545     ldy = fm.getHeight()*gp.getLegendSpacing();
546     legendHeight = ldy*(k-1) + fm.getHeight() + 2;
547   }
548 
549   /*
550    * Writes the legend in the upper-left corner of the graph, or
551    * whereever the user has pinned it. Note that the legend is drawn
552    * one 'ldy' (separation in the y-direction) lower than expected,
553    * this is a poor-man's trick to get the bounding-box right as we
554    * start to drag'n drop.
555    */
556   protected void drawLegend(Graphics2D g2, DataArray da, int i) {
557     float y = (float)(gp.getLegendPos(Y) + ldy*(i+1));
558     float x = (float)(gp.getLegendPos(X));
559     float yy = y-(float)ldy/4;
560     if (da.drawLine()) {
561       line.setLine(x,yy,x+keyLen,yy);
562       g2.draw(line);
563     }
564     if (da.drawSymbol()) {
565       drawPointType(da.getSymbol(),g2,x+keyLen/2,yy,da.getSymbolSize());
566     }
567     g2.drawString(da.getName(),(float)(x+keyLen+xSep),y);
568   }
569 
570   /**
571    * Returns the X-value scaled to the pixel-availability.
572    * This function takes the X-value and returns the corresponding
573    * coordinates for the panel.
574    * @param x real x-value (as introduced by the data)
575    * @return x-value scaled to the actual panel coordinates
576    */
577   abstract double toX(double x);
578 
579   /**
580    * Returns the Y-value scaled to the pixel-availability.
581    * This function takes the Y-value and returns the corresponding
582    * coordinates for the panel.
583    * @param x real x-value (as introduced by the data)
584    * @return x-value scaled to the actual panel coordinates
585    */
586   abstract double toY(double x);
587 
588   /**
589    * Fills the graph area with a background color.
590    * @param g2 Graphics canvas
591    */
592   abstract void fillGraphArea(Graphics2D g2);
593 
594   /**
595    * Draws the graph on the graphics canvas of this panel.
596    * Performs a test on the minimum and maximum values. If
597    * they are different by more than 10 % with respect to their
598    * previous values, release all fixed positions of legend, labels
599    * etc and restore the default positions. Otherwise, they may fall
600    * far beyond the screen, leading to a coredump.
601    * @param data vector of Data Array vectors with the data points
602    */
603   protected void show(Vector data) {
604     this.data = data;
605     if (data.size() < 1) return;
606     if (gp.drawLegend()) getLegendBox(data);
607 
608     // find the minimum and maximum values of the current datasets:
609     for (int k=0; k<Na; k++) {
610       if (!setMinMax(k,data)) {
611   Utils.bummer(null,"Invalid data, no way to show a decent graph");
612   return;
613       }
614     }
615     makeTicLabels();
616     updateMargins();
617     updateGraph();
618   }
619 
620   public void resetLabelPositions(double w0, double h0, 
621           double w1, double h1) {
622     if (gp.useLegendPos()) {
623       gp.setLegendPos(w1*gp.getLegendPos(gp.X_AXIS)/w0,
624           h1*gp.getLegendPos(gp.Y_AXIS)/h0);
625     }
626     for (Enumeration e=gp.getLabels().elements(); e.hasMoreElements();) {
627       GraphLabel gl = (GraphLabel) e.nextElement();
628       if (gl.usePosition()) gl.setLocation(w1*gl.getX()/w0,h1*gl.getY()/h0);
629       if (gl.equals(GraphLabel.PIPER_Y1)) gl.setRotation(-arc);
630       else if (gl.equals(GraphLabel.PIPER_Y2)) gl.setRotation(arc);
631     }
632   }
633 
634   /**
635    * Creates the graph and returns the graph in a buffer.
636    * The graph is drawn using the specified dimension. This class
637    * stores the current widht/height of the graph and changes the
638    * margins (computation-intensive) only if the size is actually
639    * different from the current settings.
640    *
641    * @param size size of the image
642    * @return image in an image buffer, ready to be drawn.
643    */
644   public BufferedImage getBufferedImage(Dimension size) {
645     if (width != size.getWidth() || height != size.getHeight()) {
646       double w=width;
647       double h=height;
648       width = size.width;
649       height = size.height;
650       bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
651       updateMargins();
652       resetLabelPositions(w,h,size.width,size.height);
653       updateGraph();
654     }
655     return bi;
656   }
657 
658   /**
659    * Overloaded paintComponent, is called at each repaint.
660    * If the panel size changed then the user probably
661    * resized the window with the mouse and we have to reset the
662    * margins of the graph.
663    * @param g graphics context
664    */
665   public void paintComponent(Graphics g) {
666     super.paintComponent(g);
667     Graphics2D g2 = (Graphics2D) g;
668     panelSize = getSize(panelSize);
669     g2.drawImage(getBufferedImage(panelSize),0,0,this);
670   }
671 
672   /**
673    * This function builds the graph in a double-buffered image zone.
674    */
675   protected Graphics2D createGraphics() {
676     Graphics2D g2  = bi.createGraphics();
677     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
678       RenderingHints.VALUE_ANTIALIAS_ON);
679     g2.setColor(gp.getBackgroundColor());
680     g2.fill(new Rectangle(0,0,width,height));
681     if (gp.showBox()) {
682       rect.setRect(borderWidth,borderWidth,
683        width-2*borderWidth,height-2*borderWidth);
684       g2.setColor(gp.getBoxFillColor());
685       g2.fill(rect);
686       g2.setColor(gp.getBoxColor());
687       g2.draw(rect);
688     }
689     fillGraphArea(g2);
690     return g2;
691   }
692 
693   /**
694    * This function builds the graph in a double-buffered image zone.
695    */
696   abstract void updateGraph();
697 
698   /**
699    * Draws a point-type corresponding to a symbolic constant,
700    * yielding values from 0 to gp.NO_SYMBOL (==13).
701    * @param index symbolic constant
702    * @param g2 graphics 2D instance
703    * @param x x-coordinate of the midpoint of the point
704    * @param y y-coordinate of the midpoint of the point
705    * @param ps size of the point in pixels
706    */
707   static void drawPointType(int index, Graphics2D g2, double _x, double _y, float ps) {
708     if (index > 12 || ps <= 0.0f) return;
709     float x = (float)(_x)-ps/2.0f;
710     float y = (float)(_y)-ps/2.0f;
711     if (index == 0) g2.draw(new Ellipse2D.Float(x,y,ps,ps));
712     else if (index == 1) g2.draw(new Rectangle2D.Float(x,y,ps,ps));
713     else if (index == 2) {
714       GeneralPath gp = new GeneralPath();
715       gp.moveTo(x,y+ps/2.0f);
716       gp.lineTo(x+ps/2.0f,y+ps);
717       gp.lineTo(x+ps,y+ps/2.0f);
718       gp.lineTo(x+ps/2.0f,y);
719       gp.lineTo(x,y+ps/2.0f);
720       g2.draw(gp);
721     }
722     else if (index == 3) {
723       GeneralPath gp = new GeneralPath();
724       gp.moveTo(x,y+ps);
725       gp.lineTo(x+ps,y+ps);
726       gp.lineTo(x+ps/2.0f,y);
727       gp.lineTo(x,y+ps);
728       g2.draw(gp);
729     }
730     if (index == 4) {
731       g2.draw(new Ellipse2D.Float(x,y,ps,ps));
732       g2.fill(new Ellipse2D.Float(x,y,ps,ps));
733     }
734     else if (index == 5) {
735       g2.draw(new Rectangle2D.Float(x,y,ps,ps));
736       g2.fill(new Rectangle2D.Float(x,y,ps,ps));
737     }
738     else if (index == 6) {
739       GeneralPath gp = new GeneralPath();
740       gp.moveTo(x,y+ps/2.0f);
741       gp.lineTo(x+ps/2.0f,y+ps);
742       gp.lineTo(x+ps,y+ps/2.0f);
743       gp.lineTo(x+ps/2.0f,y);
744       gp.lineTo(x,y+ps/2.0f);
745       g2.draw(gp);
746       g2.fill(gp);
747     }
748     else if (index == 7) {
749       GeneralPath gp = new GeneralPath();
750       gp.moveTo(x,y+ps);
751       gp.lineTo(x+ps,y+ps);
752       gp.lineTo(x+ps/2.0f,y);
753       gp.lineTo(x,y+ps);
754       g2.draw(gp);
755       g2.fill(gp);
756     }
757     else if (index == 8) {
758       g2.draw(new Line2D.Float(x,y+ps/2.0f,x+ps,y+ps/2.0f));
759       g2.draw(new Line2D.Float(x+ps/2.0f,y,x+ps/2.0f,y+ps));
760     }
761     else if (index == 9) {
762       g2.draw(new Line2D.Float(x,y,x+ps,y+ps));
763       g2.draw(new Line2D.Float(x+ps,y,x,y+ps));
764     }
765     else if (index == 10) {
766       g2.draw(new Line2D.Float(x,y+ps/2.0f,x+ps,y+ps/2.0f));
767       g2.draw(new Line2D.Float(x+ps/2.0f,y,x+ps/2.0f,y+ps));
768       g2.draw(new Line2D.Float(x,y,x+ps,y+ps));
769       g2.draw(new Line2D.Float(x+ps,y,x,y+ps));
770     }
771     else if (index == 11) {
772       g2.draw(new Line2D.Float(x+ps/2.0f,y+ps/2.0f,x+ps/2.0f,y+ps/2.0f));
773     }
774     else if (index == 12) {
775       g2.draw(new Line2D.Float(x,y+ps/3.0f,x+ps,y+ps/3.0f));
776       g2.draw(new Line2D.Float(x,y+2.0f*ps/3.0f,x+ps,y+2.0f*ps/3.0f));
777       g2.draw(new Line2D.Float(x+ps/3.0f,y,x+ps/3.0f,y+ps));
778       g2.draw(new Line2D.Float(x+2.0f*ps/3.0f,y,x+2.0f*ps/3.0f,y+ps));
779     }
780   }
781 
782   public int print(Graphics g, PageFormat pageFormat, int pageIndex) 
783     throws PrinterException {
784     g.translate((int)pageFormat.getImageableX(),
785     (int)pageFormat.getImageableY());
786     if (pageIndex >= 1) return Printable.NO_SUCH_PAGE;
787     //int wPage = (int)pageFormat.getImageableWidth();
788     //int hPage = (int)pageFormat.getImageableHeight();
789     //paintComponent(g);
790     Color bgColor = gp.getBackgroundColor();
791     gp.setBackgroundColor(Color.white);
792     width += 1;  // trick to rebuild the bufferedImage
793     paintComponent(g);
794     gp.setBackgroundColor(bgColor);
795     System.gc();
796     return PAGE_EXISTS;
797   }
798 
799   public class GraphMouseListener implements MouseListener {
800 
801     public void mouseClicked(MouseEvent e) {
802       double xx,yy;
803       if (e.getClickCount() == 2) {
804   //int a = (int)JPlot.plotFrame.getLocation().getX() + 20;
805   //int b = (int)JPlot.plotFrame.getLocation().getY() + 20;
806   int a = e.getX() + 20;
807   int b = e.getY() + 20;
808   // show the legend parameter dialog if the legend is hit:
809   xx = gp.getLegendPos(X);
810   yy = gp.getLegendPos(Y);
811   if (gp.drawLegend() && xStart > xx && xStart < xx+legendWidth &&
812       yStart > yy && yStart < yy+legendHeight) {
813     jplot.legendPanel.show(jplot.frame,a,b);
814   }
815   else {
816     // show the label parameters dialog if a label is hit:
817     for (Enumeration el=gp.getLabels().elements(); 
818          el.hasMoreElements();) {
819       GraphLabel gl = (GraphLabel) el.nextElement();
820       if (gl.contains(xStart,yStart)) {
821         jplot.labelPanel.show(jplot.frame,a,b);
822         break;
823       }
824     }
825   }
826       }
827     }
828     public void mouseEntered(MouseEvent e) {}
829     public void mouseExited(MouseEvent e) {}
830     public boolean startDrag(boolean active, int x, int y, int w, int h) {
831       if (!active) {
832   active = true;
833   x1 = x;
834   y1 = y;
835   boxWidth = w;
836   boxHeight = h;
837   Graphics g = getGraphics();
838 
839   // draws a box with the upper-left corner at x1,y1 and with the
840   // specified width (to the right) and height (direct to the bottom):
841   //------------------------------------------------------------------
842   g.drawRect(x1,y1,boxWidth,boxHeight);
843       }
844       else active = false;
845       return active;
846     }
847     public void mousePressed(MouseEvent e) {
848       double xx, yy;
849       xStart = e.getX();
850       yStart = e.getY();
851       if (JPlot.debug) {
852   System.out.println("mouse at (x,y) = " + xStart + "," + yStart);
853       }
854 
855       // If the mouse pointer hits the legend box,
856       // start dragging of the legend:
857       //------------------------------------------
858       xx = gp.getLegendPos(X);
859       yy = gp.getLegendPos(Y);
860       if (gp.drawLegend() && xStart > xx && xStart < xx+legendWidth &&
861     yStart > yy && yStart < yy+legendHeight) {
862   legendActive = startDrag(legendActive,(int)xx,(int)yy,
863          (int)(legendWidth),(int)(legendHeight));
864   somethingActive = legendActive;
865       }
866 
867       // Check whether the mouse hits one of the current labels
868       //-------------------------------------------------------
869       else {
870   for (Enumeration el=gp.getLabels().elements(); el.hasMoreElements();) {
871     GraphLabel gl = (GraphLabel) el.nextElement();
872     if (gl.contains(xStart,yStart)) {
873       gl.setActive(startDrag(gl.isActive(),(int)gl.getX(),(int)gl.getY(),
874            (int)gl.getWidth(),(int)gl.getHeight()));
875       somethingActive = gl.isActive();
876       break;
877     }
878   }
879       }
880     }
881     public void mouseReleased(MouseEvent e) {
882       if (!somethingActive) return;
883       else if (legendActive) {
884   legendActive = false;
885   gp.setLegendPos((double)x1,(double)y1);
886   gp.setUseLegendPos(true);
887   gp.setDataChanged(true);
888   updateGraph();
889       }
890       else {
891   for (Enumeration el=gp.getLabels().elements(); el.hasMoreElements();) {
892     GraphLabel gl = (GraphLabel) el.nextElement();
893     if (gl.isActive()) {
894       gl.setActive(false);
895       if (x1 != (int)gl.getX() || y1 != (int)gl.getY()) {
896         gp.setDataChanged(true);
897         gl.setLocation((double)x1,(double)y1);
898         gl.setUsePosition(true);
899         updateGraph();
900         if (JPlot.debug) {
901     System.out.println("fixing label " + gl.getText() + 
902            ", bb = " + x1 + "," + y1 + "," +
903            (int)gl.getWidth() + "," + (int)gl.getHeight() + 
904            ",  text at " + (int)gl.getXPos() + "," + 
905            (int)gl.getYPos());
906         }
907       }
908       else repaint();
909       break;
910     }
911   }
912       }
913       somethingActive = false;
914     }
915   }
916   
917   public class DragListener implements MouseMotionListener {
918     public void mouseDragged(MouseEvent e) {
919       if (somethingActive) {
920   int x = e.getX();
921   int y = e.getY();
922 
923   // erase the previous box:
924   Graphics g = getGraphics();
925   g.setXORMode(boxColor);
926   g.drawRect(x1,y1,boxWidth,boxHeight);
927   
928   // draw the new box:
929   x1 += (x - xStart);
930   y1 += (y - yStart);
931   g.drawRect(x1,y1,boxWidth,boxHeight);
932   g.setPaintMode();
933   xStart = x;
934   yStart = y;
935       }
936     }
937     public void mouseMoved(MouseEvent e) {}
938   }
939 }