KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > JSci > swing > JHistogram


1 package JSci.swing;
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.awt.*;
10 import JSci.maths.ExtraMath;
11
12 /**
13 * A histogram Swing component.
14 * The y-values are the counts for each bin.
15 * Each bin is specified by an interval.
16 * So that y[i] contains the counts for the bin from x[i-1] to x[i].
17 * The value of y[0] is disregarded.
18 * @version 1.0
19 * @author Mark Hale
20 */

21 public class JHistogram extends JDoubleBufferedComponent implements GraphDataListener {
22         /**
23         * Data model.
24         */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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