KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > JSci > awt > Histogram


1 package JSci.awt;
2
3 import java.awt.*;
4 import java.awt.geom.Point2D JavaDoc;
5 import java.awt.geom.Rectangle2D JavaDoc;
6 import java.text.DecimalFormat JavaDoc;
7 import java.text.NumberFormat JavaDoc;
8 import java.text.ParseException JavaDoc;
9 import JSci.maths.ExtraMath;
10
11 /**
12 * A histogram AWT component.
13 * The y-values are the counts for each bin.
14 * Each bin is specified by an interval.
15 * So that y[i] contains the counts for the bin from x[i-1] to x[i].
16 * The value of y[0] is disregarded.
17 * @version 1.0
18 * @author Mark Hale
19 */

20 public class Histogram extends DoubleBufferedCanvas implements GraphDataListener {
21         /**
22         * Data model.
23         */

24         protected Graph2DModel model;
25         /**
26         * Origin.
27         */

28         protected Point origin = new Point();
29         /**
30         * Series colors.
31         */

32         protected Color seriesColor[]={Color.blue,Color.green,Color.red,Color.yellow,Color.cyan,Color.lightGray,Color.magenta,Color.orange,Color.pink};
33         /**
34         * Axis numbering.
35         */

36         protected boolean numbering = true;
37         protected NumberFormat JavaDoc xNumberFormat = new DecimalFormat JavaDoc("##0.0");
38         protected NumberFormat JavaDoc yNumberFormat = new DecimalFormat JavaDoc("##0.0");
39         protected boolean gridLines = false;
40         private final Color gridLineColor = Color.lightGray;
41         /**
42         * Axis scaling.
43         */

44         private float xScale,yScale;
45         /**
46         * Axis extrema.
47         */

48         private float minX, minY, maxX, maxY;
49         private boolean autoXExtrema=true, autoYExtrema=true;
50         private float xGrowth, yGrowth;
51         /**
52         * Axis numbering increment.
53         */

54         private final float xIncPixels = 40.0f;
55         private final float yIncPixels = 40.0f;
56         private float xInc,yInc;
57         private boolean autoXInc=true,autoYInc=true;
58         /**
59         * Padding.
60         */

61         protected final int scalePad=5;
62         protected final int axisPad=25;
63         protected int leftAxisPad;
64         /**
65         * Constructs a histogram.
66         */

67         public Histogram(Graph2DModel gm) {
68                 model=gm;
69                 model.addGraphDataListener(this);
70                 dataChanged(new GraphDataEvent(model));
71         }
72         /**
73         * Sets the data plotted by this graph to the specified data.
74         */

75         public final void setModel(Graph2DModel gm) {
76                 model.removeGraphDataListener(this);
77                 model=gm;
78                 model.addGraphDataListener(this);
79                 dataChanged(new GraphDataEvent(model));
80         }
81         /**
82         * Returns the model used by this graph.
83         */

84         public final Graph2DModel getModel() {
85                 return model;
86         }
87         /**
88         * Implementation of GraphDataListener.
89         * Application code will not use this method explicitly, it is used internally.
90         */

91         public void dataChanged(GraphDataEvent e) {
92                 if(e.isIncremental()) {
93                         Graphics g = getOffscreenGraphics();
94                         if(g == null)
95                                 return;
96                         final int series = e.getSeries();
97                         if(series == GraphDataEvent.ALL_SERIES) {
98                                 model.firstSeries();
99                                 int n = 0;
100                                 do {
101                                         final int i = model.seriesLength() - 1;
102                                         incrementalRescale(model.getXCoord(i), model.getYCoord(i));
103                                         g.setColor(seriesColor[n]);
104                                         drawDataPoint(g, i);
105                                         n++;
106                                 } while(model.nextSeries());
107                         } else {
108                                 model.firstSeries();
109                                 int n=0;
110                                 for(; n<series; n++)
111                                         model.nextSeries();
112                                 final int i = model.seriesLength() - 1;
113                                 incrementalRescale(model.getXCoord(i), model.getYCoord(i));
114                                 g.setColor(seriesColor[n]);
115                                 drawDataPoint(g, i);
116                         }
117                         repaint();
118                 } else {
119                         // ensure there are enough colors
120
int n = 1;
121                         model.firstSeries();
122                         while(model.nextSeries())
123                                 n++;
124                         if(n>seriesColor.length) {
125                                 Color tmp[]=seriesColor;
126                                 seriesColor=new Color[n];
127                                 System.arraycopy(tmp,0,seriesColor,0,tmp.length);
128                                 for(int i=tmp.length; i<n; i++)
129                                         seriesColor[i]=seriesColor[i-tmp.length];
130                         }
131                         if(autoXExtrema)
132                                 setXExtrema(0.0f, 0.0f);
133                         if(autoYExtrema)
134                                 setYExtrema(0.0f, 0.0f);
135                         redraw();
136                 }
137         }
138         private void incrementalRescale(final float x, final float y) {
139                 float min, max;
140                 if(x < minX)
141                         min = autoXExtrema ? Math.min(x, minX-xGrowth) : minX-xGrowth;
142                 else
143                         min = minX;
144                 if(x > maxX)
145                         max = autoXExtrema ? Math.max(x, maxX+xGrowth) : maxX+xGrowth;
146                 else
147                         max = maxX;
148                 rescaleX(min, max);
149
150                 if(y < minY)
151                         min = autoYExtrema ? Math.min(y, minY-yGrowth) : minY-yGrowth;
152                 else
153                         min = minY;
154                 if(y > maxY)
155                         max = autoYExtrema ? Math.max(y, maxY+yGrowth) : maxY+yGrowth;
156                 else
157                         max = maxY;
158                 rescaleY(min, max);
159         }
160         /**
161         * Turns axis numbering on/off.
162         * Default is on.
163         */

164         public final void setNumbering(boolean flag) {
165                 numbering=flag;
166                 leftAxisPad=axisPad;
167                 if(numbering && getFont() != null) {
168                         // adjust leftAxisPad to accomodate y-axis numbering
169
final FontMetrics metrics = getFontMetrics(getFont());
170                         final int maxYNumLen = metrics.stringWidth(yNumberFormat.format(maxY));
171                         final int minYNumLen = metrics.stringWidth(yNumberFormat.format(minY));
172                         int yNumPad = Math.max(minYNumLen, maxYNumLen);
173                         if(minX<0.0f) {
174                                 final int negXLen = (int)((Math.max(getSize().width,getMinimumSize().width)-2*(axisPad+scalePad))*minX/(minX-maxX));
175                                 yNumPad = Math.max(yNumPad-negXLen, 0);
176                         }
177                         leftAxisPad += yNumPad;
178                 }
179                 rescale();
180         }
181         public void addNotify() {
182                 super.addNotify();
183                 // getFont() is now not null
184
// recalculate padding
185
setNumbering(numbering);
186         }
187         /**
188         * Sets the display format used for axis numbering.
189         * Convenience method.
190         * @see #setXNumberFormat(NumberFormat)
191         * @see #setYNumberFormat(NumberFormat)
192         */

193         public final void setNumberFormat(NumberFormat JavaDoc format) {
194                 xNumberFormat = format;
195                 yNumberFormat = format;
196                 setNumbering(numbering);
197         }
198         /**
199         * Sets the display format used for x-axis numbering.
200         */

201         public final void setXNumberFormat(NumberFormat JavaDoc format) {
202                 xNumberFormat = format;
203                 setNumbering(numbering);
204         }
205         /**
206         * Sets the display format used for y-axis numbering.
207         */

208         public final void setYNumberFormat(NumberFormat JavaDoc format) {
209                 yNumberFormat = format;
210                 setNumbering(numbering);
211         }
212         /**
213         * Turns grid lines on/off.
214         * Default is off.
215         */

216         public final void setGridLines(boolean flag) {
217                 gridLines = flag;
218                 redraw();
219         }
220         /**
221         * Sets the x-axis numbering increment.
222         * @param dx use 0.0f for auto-adjusting (default).
223         */

224         public final void setXIncrement(float dx) {
225                 if(dx < 0.0f) {
226                         throw new IllegalArgumentException JavaDoc("Increment should be positive.");
227                 } else if(dx == 0.0f) {
228                         if(!autoXInc) {
229                                 autoXInc = true;
230                                 rescale();
231                         }
232                 } else {
233                         autoXInc = false;
234                         if(dx != xInc) {
235                                 xInc = dx;
236                                 rescale();
237                         }
238                 }
239         }
240         /**
241         * Returns the x-axis numbering increment.
242         */

243         public final float getXIncrement() {
244                 return xInc;
245         }
246         /**
247         * Sets the y-axis numbering increment.
248         * @param dy use 0.0f for auto-adjusting (default).
249         */

250         public final void setYIncrement(float dy) {
251                 if(dy < 0.0f) {
252                         throw new IllegalArgumentException JavaDoc("Increment should be positive.");
253                 } else if(dy == 0.0f) {
254                         if(!autoYInc) {
255                                 autoYInc = true;
256                                 rescale();
257                         }
258                 } else {
259                         autoYInc = false;
260                         if(dy != yInc) {
261                                 yInc = dy;
262                                 rescale();
263                         }
264                 }
265         }
266         /**
267         * Returns the y-axis numbering increment.
268         */

269         public final float getYIncrement() {
270                 return yInc;
271         }
272         /**
273         * Sets the minimum/maximum values on the x-axis.
274         * Set both min and max to 0.0f for auto-adjusting (default).
275         */

276         public final void setXExtrema(float min, float max) {
277                 if(min==0.0f && max==0.0f) {
278                         autoXExtrema=true;
279                         // determine min and max from model
280
min=Float.POSITIVE_INFINITY;
281                         max=Float.NEGATIVE_INFINITY;
282                         float tmp;
283                         model.firstSeries();
284                         do {
285                                 for(int i=0;i<model.seriesLength();i++) {
286                                         tmp=model.getXCoord(i);
287                                         if(!Float.isNaN(tmp)) {
288                                                 min=Math.min(tmp,min);
289                                                 max=Math.max(tmp,max);
290                                         }
291                                 }
292                         } while(model.nextSeries());
293                         if(min==max) {
294                                 // default values if no variation in data
295
min-=0.5f;
296                                 max+=0.5f;
297                         }
298                         if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) {
299                                 // default values if no data
300
min=-5.0f;
301                                 max=5.0f;
302                         }
303                 } else if(max<=min) {
304                         throw new IllegalArgumentException JavaDoc("Maximum should be greater than minimum; max = "+max+" and min = "+min);
305                 } else {
306                         autoXExtrema=false;
307                 }
308                 rescaleX(min, max);
309         }
310         public final void setXExtrema(float min, float max, float growth) {
311                 setXExtrema(min, max);
312                 xGrowth = growth;
313         }
314     public final float getXMinimum() {
315         return minX;
316     }
317     public final float getXMaximum() {
318         return maxX;
319     }
320         private void rescaleX(final float min, final float max) {
321                 if(min != minX || max != maxX) {
322                         minX = min;
323                         maxX = max;
324                         setNumbering(numbering);
325                 }
326         }
327         /**
328         * Sets the minimum/maximum values on the y-axis.
329         * Set both min and max to 0.0f for auto-adjusting (default).
330         */

331         public final void setYExtrema(float min, float max) {
332                 if(min==0.0f && max==0.0f) {
333                         autoYExtrema=true;
334                         // determine min and max from model
335
min=Float.POSITIVE_INFINITY;
336                         max=Float.NEGATIVE_INFINITY;
337                         float tmp;
338                         model.firstSeries();
339                         do {
340                                 for(int i=0;i<model.seriesLength();i++) {
341                                         tmp=model.getYCoord(i);
342                                         if(!Float.isNaN(tmp)) {
343                                                 min=Math.min(tmp,min);
344                                                 max=Math.max(tmp,max);
345                                         }
346                                 }
347                         } while(model.nextSeries());
348                         if(min==max) {
349                                 // default values if no variation in data
350
max+=5.0f;
351                         }
352                         if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) {
353                                 // default values if no data
354
min=0.0f;
355                                 max=5.0f;
356                         }
357                 } else if(max<=min) {
358                         throw new IllegalArgumentException JavaDoc("Maximum should be greater than minimum; max = "+max+" and min = "+min);
359                 } else {
360                         autoYExtrema=false;
361                 }
362                 rescaleY(min, max);
363         }
364         public final void setYExtrema(float min, float max, float growth) {
365                 setYExtrema(min, max);
366                 yGrowth = growth;
367         }
368     public final float getYMinimum() {
369         return minY;
370     }
371     public final float getYMaximum() {
372         return maxY;
373     }
374         private void rescaleY(final float min, final float max) {
375                 if(min != minY || max != maxY) {
376                         minY = min;
377                         maxY = max;
378                         setNumbering(numbering);
379                 }
380         }
381         /**
382         * Returns the bounding box for the axis extrema.
383         */

384         public final Rectangle2D.Float JavaDoc getExtrema() {
385                 return new Rectangle2D.Float JavaDoc(minX, minY, maxX-minX, maxY-minY);
386         }
387         /**
388         * Sets the color of the nth y-series.
389         * @param n the index of the y-series.
390         * @param c the line color.
391         */

392         public final void setColor(int n,Color c) {
393                 seriesColor[n]=c;
394                 redraw();
395         }
396         /**
397         * Gets the color of the nth y-series.
398         * @param n the index of the y-series.
399         */

400         public final Color getColor(int n) {
401                 return seriesColor[n];
402         }
403         /**
404         * Reshapes this graph to the specified bounding box.
405         */

406         public final void setBounds(int x,int y,int width,int height) {
407                 super.setBounds(x,y,width,height);
408                 rescale();
409         }
410         /**
411         * Returns the preferred size of this component.
412         */

413         public Dimension getPreferredSize() {
414                 return getMinimumSize();
415         }
416         /**
417         * Returns the minimum size of this component.
418         */

419         public Dimension getMinimumSize() {
420                 return new Dimension(170, 170);
421         }
422         /**
423         * Rescales this graph.
424         */

425         protected final void rescale() {
426                 final Dimension minSize = getMinimumSize();
427                 final Dimension size = getSize();
428                 final int thisWidth=Math.max(size.width, minSize.width);
429                 final int thisHeight=Math.max(size.height, minSize.height);
430                 xScale = (float) ((double)(thisWidth-(leftAxisPad+axisPad)) / (double)(maxX-minX));
431                 yScale = (float) ((double)(thisHeight-2*axisPad) / (double)(maxY-minY));
432                 if(autoXInc) {
433                         xInc = (float) ExtraMath.round((double)xIncPixels/(double)xScale, 1);
434                         if(xInc == 0.0f)
435                                 xInc = Float.MIN_VALUE;
436                 }
437                 //assert xInc > 0.0f;
438
if(autoYInc) {
439                         yInc = (float) ExtraMath.round((double)yIncPixels/(double)yScale, 1);
440                         if(yInc == 0.0f)
441                                 yInc = Float.MIN_VALUE;
442                 }
443                 //assert yInc > 0.0f;
444
origin.x=leftAxisPad-Math.round(minX*xScale);
445                 origin.y=thisHeight-axisPad+Math.round(minY*yScale);
446                 redraw();
447         }
448         /**
449         * Converts a data point to screen coordinates.
450         */

451         protected final Point dataToScreen(float x,float y) {
452                 return new Point(origin.x+Math.round(xScale*x), origin.y-Math.round(yScale*y));
453         }
454         /**
455         * Converts a screen point to data coordinates.
456         */

457         protected final Point2D.Float JavaDoc screenToData(Point p) {
458                 double x = (double)(p.x-origin.x) / (double)xScale;
459                 double y = (double)(origin.y-p.y) / (double)yScale;
460                 return new Point2D.Float JavaDoc((float)x, (float)y);
461         }
462         /**
463         * Draws the graph axes.
464         */

465         protected final void drawAxes(Graphics g) {
466                 final Dimension size = getSize();
467                 final int width = size.width;
468                 final int height = size.height;
469                 g.setColor(getForeground());
470 // grid lines and numbering
471
if(gridLines || numbering) {
472 // x-axis numbering and vertical grid lines
473
float xAxisY;
474                         if(minY > 0.0f) {
475                                 xAxisY = minY;
476                         } else if(maxY <= 0.0f) {
477                                 xAxisY = maxY;
478                         } else {
479                                 xAxisY = 0.0f;
480             }
481                         for(double x=(minX>0.0f)?minX:xInc; x<=maxX; x+=xInc) {
482                                 Point p=dataToScreen((float)x, xAxisY);
483                                 if(gridLines) {
484                                         g.setColor(gridLineColor);
485                                         g.drawLine(p.x, axisPad-scalePad, p.x, height-(axisPad-scalePad));
486                                         g.setColor(getForeground());
487                                 }
488                                 if(numbering) {
489                     drawXLabel(g, x, p);
490                                 }
491                         }
492                         for(double x=-xInc; x>=minX; x-=xInc) {
493                                 Point p=dataToScreen((float)x, xAxisY);
494                                 if(gridLines) {
495                                         g.setColor(gridLineColor);
496                                         g.drawLine(p.x, axisPad-scalePad, p.x, height-(axisPad-scalePad));
497                                         g.setColor(getForeground());
498                                 }
499                                 if(numbering) {
500                     drawXLabel(g, x, p);
501                                 }
502                         }
503 // y-axis numbering and horizontal grid lines
504
float yAxisX;
505                         if(minX > 0.0f)
506                                 yAxisX = minX;
507                         else if(maxX < 0.0f)
508                                 yAxisX = maxX;
509                         else
510                                 yAxisX = 0.0f;
511                         for(double y=(minY>0.0f)?minY:yInc; y<=maxY; y+=yInc) {
512                                 Point p=dataToScreen(yAxisX, (float)y);
513                                 if(gridLines) {
514                                         g.setColor(gridLineColor);
515                                         g.drawLine(leftAxisPad-scalePad, p.y, width-(axisPad-scalePad), p.y);
516                                         g.setColor(getForeground());
517                                 }
518                                 if(numbering) {
519                     drawYLabel(g, y, p);
520                                 }
521                         }
522                         for(double y=-yInc; y>=minY; y-=yInc) {
523                                 Point p=dataToScreen(yAxisX, (float)y);
524                                 if(gridLines) {
525                                         g.setColor(gridLineColor);
526                                         g.drawLine(leftAxisPad-scalePad, p.y, width-(axisPad-scalePad), p.y);
527                                         g.setColor(getForeground());
528                                 }
529                                 if(numbering) {
530                     drawYLabel(g, y, p);
531                                 }
532                         }
533                 }
534
535 // axis lines
536
// horizontal axis
537
if(minY > 0.0f) {
538                         // draw at bottom
539
g.drawLine(leftAxisPad-scalePad, height-axisPad, width-(axisPad-scalePad), height-axisPad);
540                 } else if(maxY < 0.0f) {
541                         // draw at top
542
g.drawLine(leftAxisPad-scalePad, axisPad, width-(axisPad-scalePad), axisPad);
543                 } else {
544                         // draw through y origin
545
g.drawLine(leftAxisPad-scalePad, origin.y, width-(axisPad-scalePad), origin.y);
546                 }
547                 // vertical axis
548
if(minX > 0.0f) {
549                         // draw at left
550
g.drawLine(leftAxisPad, axisPad-scalePad, leftAxisPad, height-(axisPad-scalePad));
551                 } else if(maxX < 0.0f) {
552                         // draw at right
553
g.drawLine(width-axisPad, axisPad-scalePad, width-axisPad, height-(axisPad-scalePad));
554                 } else {
555                         // draw through x origin
556
g.drawLine(origin.x, axisPad-scalePad, origin.x, height-(axisPad-scalePad));
557                 }
558         }
559     protected void drawXLabel(Graphics g, double x, Point p) {
560         String JavaDoc str = xNumberFormat.format(x);
561         FontMetrics metrics=g.getFontMetrics();
562         int strWidth=metrics.stringWidth(str);
563         int strHeight=metrics.getHeight();
564         boolean numberingAbove = (maxY <= 0.0f);
565         if(numberingAbove) {
566             g.drawLine(p.x,p.y,p.x,p.y-5);
567             g.drawString(str,p.x-strWidth/2,p.y-7);
568         } else {
569             g.drawLine(p.x,p.y,p.x,p.y+5);
570             g.drawString(str,p.x-strWidth/2,p.y+5+strHeight);
571         }
572     }
573     protected void drawYLabel(Graphics g, double y, Point p) {
574         String JavaDoc str = yNumberFormat.format(y);
575         FontMetrics metrics=g.getFontMetrics();
576         int strWidth=metrics.stringWidth(str);
577         int strHeight=metrics.getHeight();
578         g.drawLine(p.x,p.y,p.x-5,p.y);
579         g.drawString(str,p.x-8-strWidth,p.y+strHeight/3);
580     }
581         /**
582         * Draws the graph data.
583         * Override this method to change how the graph data is plotted.
584         */

585         protected void drawData(Graphics g) {
586 // bars
587
model.firstSeries();
588                 for(int i=1; i<model.seriesLength(); i++) {
589                     g.setColor(seriesColor[0]);
590                         drawDataPoint(g, i);
591         }
592                 for(int n=1; model.nextSeries(); n++) {
593                         for(int i=1; i<model.seriesLength(); i++) {
594                             g.setColor(seriesColor[n]);
595                                 drawDataPoint(g, i);
596             }
597                 }
598         }
599         /**
600         * Draws a single data point in the current series.
601         * @param i index of the data point.
602         */

603         private void drawDataPoint(Graphics g, int i) {
604         if(i == 0)
605             return;
606                 Point p1 = dataToScreen(model.getXCoord(i-1), 0.0f);
607                 Point p2 = dataToScreen(model.getXCoord(i), model.getYCoord(i));
608         int width = Math.abs(p2.x-p1.x);
609         int height = Math.abs(p2.y-p1.y);
610         g.fillRect(p1.x, p2.y, width, height);
611         g.setColor(Color.black);
612         g.drawRect(p1.x, p2.y, width, height);
613         }
614         /**
615         * Paints the graph (draws the graph axes and data).
616         */

617         protected void offscreenPaint(Graphics g) {
618                 final Dimension size = getSize();
619                 final int width = size.width;
620                 final int height = size.height;
621                 g.setClip(leftAxisPad-scalePad, axisPad-scalePad, width-(leftAxisPad+axisPad-2*scalePad), height-2*(axisPad-scalePad));
622                 drawData(g);
623         g.setClip(null);
624                 drawAxes(g);
625         }
626 }
627
Popular Tags