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 }