1 package JSci.swing; 2 3 import java.awt.*; 4 import java.awt.geom.Point2D ; 5 import java.awt.geom.Rectangle2D ; 6 import java.text.DecimalFormat ; 7 import java.text.NumberFormat ; 8 import java.text.ParseException ; 9 import JSci.awt.*; 10 import JSci.maths.ExtraMath; 11 12 21 public class JHistogram extends JDoubleBufferedComponent implements GraphDataListener { 22 25 protected Graph2DModel model; 26 29 protected Point origin = new Point(); 30 33 protected Color seriesColor[]={Color.blue,Color.green,Color.red,Color.yellow,Color.cyan,Color.lightGray,Color.magenta,Color.orange,Color.pink}; 34 37 protected boolean numbering = true; 38 protected NumberFormat xNumberFormat = new DecimalFormat ("##0.0"); 39 protected NumberFormat yNumberFormat = new DecimalFormat ("##0.0"); 40 protected boolean gridLines = false; 41 private final Color gridLineColor = Color.lightGray; 42 45 private float xScale,yScale; 46 49 private float minX, minY, maxX, maxY; 50 private boolean autoXExtrema=true, autoYExtrema=true; 51 private float xGrowth, yGrowth; 52 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 62 protected final int scalePad=5; 63 protected final int axisPad=25; 64 protected int leftAxisPad; 65 68 public JHistogram(Graph2DModel gm) { 69 model=gm; 70 model.addGraphDataListener(this); 71 dataChanged(new GraphDataEvent(model)); 72 } 73 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 85 public final Graph2DModel getModel() { 86 return model; 87 } 88 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 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 165 public final void setNumbering(boolean flag) { 166 numbering=flag; 167 leftAxisPad=axisPad; 168 if(numbering && getFont() != null) { 169 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 setNumbering(numbering); 187 } 188 194 public final void setNumberFormat(NumberFormat format) { 195 xNumberFormat = format; 196 yNumberFormat = format; 197 setNumbering(numbering); 198 } 199 202 public final void setXNumberFormat(NumberFormat format) { 203 xNumberFormat = format; 204 setNumbering(numbering); 205 } 206 209 public final void setYNumberFormat(NumberFormat format) { 210 yNumberFormat = format; 211 setNumbering(numbering); 212 } 213 217 public final void setGridLines(boolean flag) { 218 gridLines = flag; 219 redraw(); 220 } 221 225 public final void setXIncrement(float dx) { 226 if(dx < 0.0f) { 227 throw new IllegalArgumentException ("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 244 public final float getXIncrement() { 245 return xInc; 246 } 247 251 public final void setYIncrement(float dy) { 252 if(dy < 0.0f) { 253 throw new IllegalArgumentException ("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 270 public final float getYIncrement() { 271 return yInc; 272 } 273 277 public final void setXExtrema(float min, float max) { 278 if(min==0.0f && max==0.0f) { 279 autoXExtrema=true; 280 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 min-=0.5f; 297 max+=0.5f; 298 } 299 if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) { 300 min=-5.0f; 302 max=5.0f; 303 } 304 } else if(max<=min) { 305 throw new IllegalArgumentException ("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 332 public final void setYExtrema(float min, float max) { 333 if(min==0.0f && max==0.0f) { 334 autoYExtrema=true; 335 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 max+=5.0f; 352 } 353 if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) { 354 min=0.0f; 356 max=5.0f; 357 } 358 } else if(max<=min) { 359 throw new IllegalArgumentException ("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 385 public final Rectangle2D.Float getExtrema() { 386 return new Rectangle2D.Float (minX, minY, maxX-minX, maxY-minY); 387 } 388 393 public final void setColor(int n,Color c) { 394 seriesColor[n]=c; 395 redraw(); 396 } 397 401 public final Color getColor(int n) { 402 return seriesColor[n]; 403 } 404 407 public final void setBounds(int x,int y,int width,int height) { 408 super.setBounds(x,y,width,height); 409 rescale(); 410 } 411 414 public Dimension getPreferredSize() { 415 return getMinimumSize(); 416 } 417 420 public Dimension getMinimumSize() { 421 return new Dimension(170, 170); 422 } 423 426 protected final void rescale() { 427 final Dimension minSize = getMinimumSize(); 428 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 if(autoYInc) { 440 yInc = (float) ExtraMath.round((double)yIncPixels/(double)yScale, 1); 441 if(yInc == 0.0f) 442 yInc = Float.MIN_VALUE; 443 } 444 origin.x=leftAxisPad-Math.round(minX*xScale); 446 origin.y=thisHeight-axisPad+Math.round(minY*yScale); 447 redraw(); 448 } 449 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 458 protected final Point2D.Float 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 ((float)x, (float)y); 462 } 463 466 protected final void drawAxes(Graphics g) { 467 final int width = getWidth(); 469 final int height = getHeight(); 470 g.setColor(getForeground()); 471 if(gridLines || numbering) { 473 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 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 if(minY > 0.0f) { 539 g.drawLine(leftAxisPad-scalePad, height-axisPad, width-(axisPad-scalePad), height-axisPad); 541 } else if(maxY < 0.0f) { 542 g.drawLine(leftAxisPad-scalePad, axisPad, width-(axisPad-scalePad), axisPad); 544 } else { 545 g.drawLine(leftAxisPad-scalePad, origin.y, width-(axisPad-scalePad), origin.y); 547 } 548 if(minX > 0.0f) { 550 g.drawLine(leftAxisPad, axisPad-scalePad, leftAxisPad, height-(axisPad-scalePad)); 552 } else if(maxX < 0.0f) { 553 g.drawLine(width-axisPad, axisPad-scalePad, width-axisPad, height-(axisPad-scalePad)); 555 } else { 556 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 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 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 586 protected void drawData(Graphics g) { 587 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 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 618 protected void offscreenPaint(Graphics g) { 619 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 |