1 package JSci.awt; 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.maths.ExtraMath; 10 11 20 public class Histogram extends DoubleBufferedCanvas implements GraphDataListener { 21 24 protected Graph2DModel model; 25 28 protected Point origin = new Point(); 29 32 protected Color seriesColor[]={Color.blue,Color.green,Color.red,Color.yellow,Color.cyan,Color.lightGray,Color.magenta,Color.orange,Color.pink}; 33 36 protected boolean numbering = true; 37 protected NumberFormat xNumberFormat = new DecimalFormat ("##0.0"); 38 protected NumberFormat yNumberFormat = new DecimalFormat ("##0.0"); 39 protected boolean gridLines = false; 40 private final Color gridLineColor = Color.lightGray; 41 44 private float xScale,yScale; 45 48 private float minX, minY, maxX, maxY; 49 private boolean autoXExtrema=true, autoYExtrema=true; 50 private float xGrowth, yGrowth; 51 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 61 protected final int scalePad=5; 62 protected final int axisPad=25; 63 protected int leftAxisPad; 64 67 public Histogram(Graph2DModel gm) { 68 model=gm; 69 model.addGraphDataListener(this); 70 dataChanged(new GraphDataEvent(model)); 71 } 72 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 84 public final Graph2DModel getModel() { 85 return model; 86 } 87 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 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 164 public final void setNumbering(boolean flag) { 165 numbering=flag; 166 leftAxisPad=axisPad; 167 if(numbering && getFont() != null) { 168 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 setNumbering(numbering); 186 } 187 193 public final void setNumberFormat(NumberFormat format) { 194 xNumberFormat = format; 195 yNumberFormat = format; 196 setNumbering(numbering); 197 } 198 201 public final void setXNumberFormat(NumberFormat format) { 202 xNumberFormat = format; 203 setNumbering(numbering); 204 } 205 208 public final void setYNumberFormat(NumberFormat format) { 209 yNumberFormat = format; 210 setNumbering(numbering); 211 } 212 216 public final void setGridLines(boolean flag) { 217 gridLines = flag; 218 redraw(); 219 } 220 224 public final void setXIncrement(float dx) { 225 if(dx < 0.0f) { 226 throw new IllegalArgumentException ("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 243 public final float getXIncrement() { 244 return xInc; 245 } 246 250 public final void setYIncrement(float dy) { 251 if(dy < 0.0f) { 252 throw new IllegalArgumentException ("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 269 public final float getYIncrement() { 270 return yInc; 271 } 272 276 public final void setXExtrema(float min, float max) { 277 if(min==0.0f && max==0.0f) { 278 autoXExtrema=true; 279 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 min-=0.5f; 296 max+=0.5f; 297 } 298 if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) { 299 min=-5.0f; 301 max=5.0f; 302 } 303 } else if(max<=min) { 304 throw new IllegalArgumentException ("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 331 public final void setYExtrema(float min, float max) { 332 if(min==0.0f && max==0.0f) { 333 autoYExtrema=true; 334 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 max+=5.0f; 351 } 352 if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) { 353 min=0.0f; 355 max=5.0f; 356 } 357 } else if(max<=min) { 358 throw new IllegalArgumentException ("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 384 public final Rectangle2D.Float getExtrema() { 385 return new Rectangle2D.Float (minX, minY, maxX-minX, maxY-minY); 386 } 387 392 public final void setColor(int n,Color c) { 393 seriesColor[n]=c; 394 redraw(); 395 } 396 400 public final Color getColor(int n) { 401 return seriesColor[n]; 402 } 403 406 public final void setBounds(int x,int y,int width,int height) { 407 super.setBounds(x,y,width,height); 408 rescale(); 409 } 410 413 public Dimension getPreferredSize() { 414 return getMinimumSize(); 415 } 416 419 public Dimension getMinimumSize() { 420 return new Dimension(170, 170); 421 } 422 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 if(autoYInc) { 439 yInc = (float) ExtraMath.round((double)yIncPixels/(double)yScale, 1); 440 if(yInc == 0.0f) 441 yInc = Float.MIN_VALUE; 442 } 443 origin.x=leftAxisPad-Math.round(minX*xScale); 445 origin.y=thisHeight-axisPad+Math.round(minY*yScale); 446 redraw(); 447 } 448 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 457 protected final Point2D.Float 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 ((float)x, (float)y); 461 } 462 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 if(gridLines || numbering) { 472 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 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 if(minY > 0.0f) { 538 g.drawLine(leftAxisPad-scalePad, height-axisPad, width-(axisPad-scalePad), height-axisPad); 540 } else if(maxY < 0.0f) { 541 g.drawLine(leftAxisPad-scalePad, axisPad, width-(axisPad-scalePad), axisPad); 543 } else { 544 g.drawLine(leftAxisPad-scalePad, origin.y, width-(axisPad-scalePad), origin.y); 546 } 547 if(minX > 0.0f) { 549 g.drawLine(leftAxisPad, axisPad-scalePad, leftAxisPad, height-(axisPad-scalePad)); 551 } else if(maxX < 0.0f) { 552 g.drawLine(width-axisPad, axisPad-scalePad, width-axisPad, height-(axisPad-scalePad)); 554 } else { 555 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 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 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 585 protected void drawData(Graphics g) { 586 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 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 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 |