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 17 public abstract class Graph2D extends DoubleBufferedCanvas implements GraphDataListener { 18 public final static int LINEAR_SCALE = 0; 19 public final static int LOG_SCALE = 1; 20 23 protected Graph2DModel model; 24 27 protected Point origin = new Point(); 28 protected Graph2D.DataMarker dataMarker = Graph2D.DataMarker.NONE; 29 32 protected Color seriesColor[]={Color.black,Color.blue,Color.green,Color.red,Color.yellow,Color.cyan,Color.lightGray,Color.magenta,Color.orange,Color.pink}; 33 36 protected boolean xNumbering = true, yNumbering = true; 37 protected NumberFormat xNumberFormat = new DecimalFormat ("##0.0"); 38 protected NumberFormat yNumberFormat = new DecimalFormat ("##0.0"); 39 protected boolean xAxisLine = true, yAxisLine = true; 40 protected boolean gridLines = false; 41 private final Color gridLineColor = Color.lightGray; 42 45 private float xScale,yScale; 46 49 private int xScaleType, yScaleType; 50 53 private float minX, minY, maxX, maxY; 54 private float scaledMinX, scaledMinY, scaledMaxX, scaledMaxY; 55 private boolean autoXExtrema=true, autoYExtrema=true; 56 private float xGrowth, yGrowth; 57 60 private final float xIncPixels = 40.0f; 61 private final float yIncPixels = 40.0f; 62 private float xInc,yInc; 63 private boolean autoXInc=true,autoYInc=true; 64 67 protected final int scalePad=5; 68 protected final int axisPad=25; 69 protected int leftAxisPad; 70 73 public Graph2D(Graph2DModel gm) { 74 model=gm; 75 model.addGraphDataListener(this); 76 dataChanged(new GraphDataEvent(model)); 77 } 78 81 public final void setModel(Graph2DModel gm) { 82 model.removeGraphDataListener(this); 83 model=gm; 84 model.addGraphDataListener(this); 85 dataChanged(new GraphDataEvent(model)); 86 } 87 90 public final Graph2DModel getModel() { 91 return model; 92 } 93 98 public void dataChanged(GraphDataEvent e) { 99 if(e.isIncremental()) { 100 Graphics g = getOffscreenGraphics(); 101 if(g == null) 102 return; 103 final int series = e.getSeries(); 104 if(series == GraphDataEvent.ALL_SERIES) { 105 model.firstSeries(); 106 int n = 0; 107 do { 108 final int i = model.seriesLength() - 1; 109 incrementalRescale(model.getXCoord(i), model.getYCoord(i)); 110 g.setColor(seriesColor[n]); 111 drawDataPoint(g, i); 112 n++; 113 } while(model.nextSeries()); 114 } else { 115 model.firstSeries(); 116 int n=0; 117 for(; n<series; n++) 118 model.nextSeries(); 119 final int i = model.seriesLength() - 1; 120 incrementalRescale(model.getXCoord(i), model.getYCoord(i)); 121 g.setColor(seriesColor[n]); 122 drawDataPoint(g, i); 123 } 124 repaint(); 125 } else { 126 int n = 1; 128 model.firstSeries(); 129 while(model.nextSeries()) 130 n++; 131 if(n>seriesColor.length) { 132 Color tmp[]=seriesColor; 133 seriesColor=new Color[n]; 134 System.arraycopy(tmp,0,seriesColor,0,tmp.length); 135 for(int i=tmp.length; i<n; i++) 136 seriesColor[i]=seriesColor[i-tmp.length]; 137 } 138 if(autoXExtrema) 139 setXExtrema(0.0f, 0.0f); 140 if(autoYExtrema) 141 setYExtrema(0.0f, 0.0f); 142 redraw(); 143 } 144 } 145 private void incrementalRescale(final float x, final float y) { 146 float min, max; 147 if(x < minX) 148 min = autoXExtrema ? Math.min(x, minX-xGrowth) : minX-xGrowth; 149 else 150 min = minX; 151 if(x > maxX) 152 max = autoXExtrema ? Math.max(x, maxX+xGrowth) : maxX+xGrowth; 153 else 154 max = maxX; 155 rescaleX(min, max); 156 157 if(y < minY) 158 min = autoYExtrema ? Math.min(y, minY-yGrowth) : minY-yGrowth; 159 else 160 min = minY; 161 if(y > maxY) 162 max = autoYExtrema ? Math.max(y, maxY+yGrowth) : maxY+yGrowth; 163 else 164 max = maxY; 165 rescaleY(min, max); 166 } 167 171 public final void setNumbering(boolean flag) { 172 setNumbering(flag, flag); 173 } 174 public final void setNumbering(boolean xFlag, boolean yFlag) { 175 xNumbering = xFlag; 176 yNumbering = yFlag; 177 leftAxisPad = axisPad; 178 if(yNumbering && getFont() != null) { 179 final FontMetrics metrics = getFontMetrics(getFont()); 181 final int maxYNumLen = metrics.stringWidth(yNumberFormat.format(maxY)); 182 final int minYNumLen = metrics.stringWidth(yNumberFormat.format(minY)); 183 int yNumPad = Math.max(minYNumLen, maxYNumLen); 184 if(scaledMinX<0.0f) { 185 final int negXLen = (int)((Math.max(getSize().width,getMinimumSize().width)-2*(axisPad+scalePad))*scaledMinX/(scaledMinX-scaledMaxX)); 186 yNumPad = Math.max(yNumPad-negXLen, 0); 187 } 188 leftAxisPad += yNumPad; 189 } 190 rescale(); 191 } 192 public void addNotify() { 193 super.addNotify(); 194 setNumbering(xNumbering, yNumbering); 197 } 198 204 public final void setNumberFormat(NumberFormat format) { 205 xNumberFormat = format; 206 yNumberFormat = format; 207 setNumbering(xNumbering, yNumbering); 208 } 209 212 public final void setXNumberFormat(NumberFormat format) { 213 xNumberFormat = format; 214 setNumbering(xNumbering, yNumbering); 215 } 216 219 public final void setYNumberFormat(NumberFormat format) { 220 yNumberFormat = format; 221 setNumbering(xNumbering, yNumbering); 222 } 223 226 public final void setAxisLines(boolean xFlag, boolean yFlag) { 227 xAxisLine = xFlag; 228 yAxisLine = yFlag; 229 redraw(); 230 } 231 235 public final void setGridLines(boolean flag) { 236 gridLines = flag; 237 redraw(); 238 } 239 243 public final void setXScale(int t) { 244 if(xScaleType != t) { 245 xScaleType = t; 246 dataChanged(new GraphDataEvent(model)); 247 } 248 } 249 253 public final void setYScale(int t) { 254 if(yScaleType != t) { 255 yScaleType = t; 256 dataChanged(new GraphDataEvent(model)); 257 } 258 } 259 263 public final void setXIncrement(float dx) { 264 if(dx < 0.0f) { 265 throw new IllegalArgumentException ("Increment should be positive."); 266 } else if(dx == 0.0f) { 267 if(!autoXInc) { 268 autoXInc = true; 269 rescale(); 270 } 271 } else { 272 autoXInc = false; 273 if(dx != xInc) { 274 xInc = dx; 275 rescale(); 276 } 277 } 278 } 279 282 public final float getXIncrement() { 283 return xInc; 284 } 285 289 public final void setYIncrement(float dy) { 290 if(dy < 0.0f) { 291 throw new IllegalArgumentException ("Increment should be positive."); 292 } else if(dy == 0.0f) { 293 if(!autoYInc) { 294 autoYInc = true; 295 rescale(); 296 } 297 } else { 298 autoYInc = false; 299 if(dy != yInc) { 300 yInc = dy; 301 rescale(); 302 } 303 } 304 } 305 308 public final float getYIncrement() { 309 return yInc; 310 } 311 315 public final void setXExtrema(float min, float max) { 316 if(min==0.0f && max==0.0f) { 317 autoXExtrema=true; 318 min=Float.POSITIVE_INFINITY; 320 max=Float.NEGATIVE_INFINITY; 321 float tmp; 322 model.firstSeries(); 323 do { 324 for(int i=0;i<model.seriesLength();i++) { 325 tmp=model.getXCoord(i); 326 if(!Float.isNaN(tmp)) { 327 min=Math.min(tmp,min); 328 max=Math.max(tmp,max); 329 } 330 } 331 } while(model.nextSeries()); 332 if(min==max) { 333 if(yScaleType == LOG_SCALE) { 335 min/=10.0f; 336 max*=10.0f; 337 } else { 338 min-=0.5f; 339 max+=0.5f; 340 } 341 } 342 if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) { 343 if(xScaleType == LOG_SCALE) { 345 min = 1.0f; 346 max = 100.0f; 347 } else { 348 min=-5.0f; 349 max=5.0f; 350 } 351 } 352 } else if(max<=min) { 353 throw new IllegalArgumentException ("Maximum should be greater than minimum; max = "+max+" and min = "+min); 354 } else { 355 autoXExtrema=false; 356 } 357 rescaleX(min, max); 358 } 359 362 public final void setXExtrema(float min, float max, float growth) { 363 setXExtrema(min, max); 364 xGrowth = growth; 365 } 366 public final float getXMinimum() { 367 return minX; 368 } 369 public final float getXMaximum() { 370 return maxX; 371 } 372 private void rescaleX(final float min, final float max) { 373 if(min != minX || max != maxX) { 374 minX = min; 375 maxX = max; 376 if(xScaleType == LOG_SCALE) { 377 scaledMinX = (float) Math.log(minX); 378 scaledMaxX = (float) Math.log(maxX); 379 } else { 380 scaledMinX = minX; 381 scaledMaxX = maxX; 382 } 383 setNumbering(xNumbering, yNumbering); 384 } 385 } 386 390 public final void setYExtrema(float min, float max) { 391 if(min==0.0f && max==0.0f) { 392 autoYExtrema=true; 393 min=Float.POSITIVE_INFINITY; 395 max=Float.NEGATIVE_INFINITY; 396 float tmp; 397 model.firstSeries(); 398 do { 399 for(int i=0;i<model.seriesLength();i++) { 400 tmp=model.getYCoord(i); 401 if(!Float.isNaN(tmp)) { 402 min=Math.min(tmp,min); 403 max=Math.max(tmp,max); 404 } 405 } 406 } while(model.nextSeries()); 407 if(min==max) { 408 if(yScaleType == LOG_SCALE) { 410 min/=10.0f; 411 max*=10.0f; 412 } else { 413 min-=0.5f; 414 max+=0.5f; 415 } 416 } 417 if(min==Float.POSITIVE_INFINITY || max==Float.NEGATIVE_INFINITY) { 418 if(yScaleType == LOG_SCALE) { 420 min = 1.0f; 421 max = 100.0f; 422 } else { 423 min=-5.0f; 424 max=5.0f; 425 } 426 } 427 } else if(max<=min) { 428 throw new IllegalArgumentException ("Maximum should be greater than minimum; max = "+max+" and min = "+min); 429 } else { 430 autoYExtrema=false; 431 } 432 rescaleY(min, max); 433 } 434 437 public final void setYExtrema(float min, float max, float growth) { 438 setYExtrema(min, max); 439 yGrowth = growth; 440 } 441 public final float getYMinimum() { 442 return minY; 443 } 444 public final float getYMaximum() { 445 return maxY; 446 } 447 private void rescaleY(final float min, final float max) { 448 if(min != minY || max != maxY) { 449 minY = min; 450 maxY = max; 451 if(yScaleType == LOG_SCALE) { 452 scaledMinY = (float) Math.log(minY); 453 scaledMaxY = (float) Math.log(maxY); 454 } else { 455 scaledMinY = minY; 456 scaledMaxY = maxY; 457 } 458 setNumbering(xNumbering, yNumbering); 459 } 460 } 461 464 public final Rectangle2D.Float getExtrema() { 465 return new Rectangle2D.Float (minX, minY, maxX-minX, maxY-minY); 466 } 467 470 public final void setMarker(Graph2D.DataMarker marker) { 471 dataMarker = marker; 472 redraw(); 473 } 474 479 public final void setColor(int n,Color c) { 480 seriesColor[n]=c; 481 redraw(); 482 } 483 487 public final Color getColor(int n) { 488 return seriesColor[n]; 489 } 490 493 public final void setBounds(int x,int y,int width,int height) { 494 super.setBounds(x,y,width,height); 495 rescale(); 496 } 497 500 public Dimension getPreferredSize() { 501 return getMinimumSize(); 502 } 503 506 public Dimension getMinimumSize() { 507 return new Dimension(170, 170); 508 } 509 512 protected final void rescale() { 513 final Dimension minSize = getMinimumSize(); 514 final Dimension size = getSize(); 515 final int thisWidth=Math.max(size.width, minSize.width); 516 final int thisHeight=Math.max(size.height, minSize.height); 517 xScale = (float) ((double)(thisWidth-(leftAxisPad+axisPad)) / (double)(scaledMaxX-scaledMinX)); 518 yScale = (float) ((double)(thisHeight-2*axisPad) / (double)(scaledMaxY-scaledMinY)); 519 if(autoXInc) { 520 xInc = (float) ExtraMath.round((double)xIncPixels/(double)xScale, 1); 521 if(xInc == 0.0f) 522 xInc = Float.MIN_VALUE; 523 } 524 if(autoYInc) { 526 yInc = (float) ExtraMath.round((double)yIncPixels/(double)yScale, 1); 527 if(yInc == 0.0f) 528 yInc = Float.MIN_VALUE; 529 } 530 origin.x=leftAxisPad-Math.round(scaledMinX*xScale); 532 origin.y=thisHeight-axisPad+Math.round(scaledMinY*yScale); 533 redraw(); 534 } 535 538 protected final Point dataToScreen(float x,float y) { 539 if(xScaleType == LOG_SCALE) 540 x = (float) Math.log(x); 541 if(yScaleType == LOG_SCALE) 542 y = (float) Math.log(y); 543 return scaledDataToScreen(x, y); 544 } 545 548 protected final Point scaledDataToScreen(float x, float y) { 549 return new Point(origin.x+Math.round(xScale*x), origin.y-Math.round(yScale*y)); 550 } 551 554 protected final Point2D.Float screenToData(Point p) { 555 double x = (double)(p.x-origin.x) / (double)xScale; 556 double y = (double)(origin.y-p.y) / (double)yScale; 557 if(xScaleType == LOG_SCALE) 558 x = Math.exp(x); 559 if(yScaleType == LOG_SCALE) 560 y = Math.exp(y); 561 return new Point2D.Float ((float)x, (float)y); 562 } 563 566 protected final void drawAxes(Graphics g) { 567 final Dimension size = getSize(); 568 final int width = size.width; 569 final int height = size.height; 570 g.setColor(getForeground()); 571 if(gridLines || xNumbering) { 573 float xAxisY; 575 if(scaledMinY > 0.0f) 576 xAxisY = scaledMinY; 577 else if(scaledMaxY < 0.0f) 578 xAxisY = scaledMaxY; 579 else 580 xAxisY = 0.0f; 581 for(double x=(scaledMinX>0.0f)?scaledMinX:xInc; x<=scaledMaxX; x+=xInc) { 582 Point p=scaledDataToScreen((float)x, xAxisY); 583 if(gridLines) { 584 g.setColor(gridLineColor); 585 g.drawLine(p.x, axisPad-scalePad, p.x, height-(axisPad-scalePad)); 586 g.setColor(getForeground()); 587 } 588 if(xNumbering) { 589 drawXLabel(g, x, p); 590 } 591 } 592 for(double x=-xInc; x>=scaledMinX; x-=xInc) { 593 Point p=scaledDataToScreen((float)x, xAxisY); 594 if(gridLines) { 595 g.setColor(gridLineColor); 596 g.drawLine(p.x, axisPad-scalePad, p.x, height-(axisPad-scalePad)); 597 g.setColor(getForeground()); 598 } 599 if(xNumbering) { 600 drawXLabel(g, x, p); 601 } 602 } 603 } 604 if(gridLines || yNumbering) { 605 float yAxisX; 607 if(scaledMinX > 0.0f) 608 yAxisX = scaledMinX; 609 else if(scaledMaxX < 0.0f) 610 yAxisX = scaledMaxX; 611 else 612 yAxisX = 0.0f; 613 for(double y=(scaledMinY>0.0f)?scaledMinY:yInc; y<=scaledMaxY; y+=yInc) { 614 Point p=scaledDataToScreen(yAxisX, (float)y); 615 if(gridLines) { 616 g.setColor(gridLineColor); 617 g.drawLine(leftAxisPad-scalePad, p.y, width-(axisPad-scalePad), p.y); 618 g.setColor(getForeground()); 619 } 620 if(yNumbering) { 621 drawYLabel(g, y, p); 622 } 623 } 624 for(double y=-yInc; y>=scaledMinY; y-=yInc) { 625 Point p=scaledDataToScreen(yAxisX, (float)y); 626 if(gridLines) { 627 g.setColor(gridLineColor); 628 g.drawLine(leftAxisPad-scalePad, p.y, width-(axisPad-scalePad), p.y); 629 g.setColor(getForeground()); 630 } 631 if(yNumbering) { 632 drawYLabel(g, y, p); 633 } 634 } 635 } 636 637 if(xAxisLine) { 639 if(scaledMinY > 0.0f) { 641 g.drawLine(leftAxisPad-scalePad, height-axisPad, width-(axisPad-scalePad), height-axisPad); 643 } else if(scaledMaxY < 0.0f) { 644 g.drawLine(leftAxisPad-scalePad, axisPad, width-(axisPad-scalePad), axisPad); 646 } else { 647 g.drawLine(leftAxisPad-scalePad, origin.y, width-(axisPad-scalePad), origin.y); 649 } 650 } 651 if(yAxisLine) { 652 if(scaledMinX > 0.0f) { 654 g.drawLine(leftAxisPad, axisPad-scalePad, leftAxisPad, height-(axisPad-scalePad)); 656 } else if(scaledMaxX < 0.0f) { 657 g.drawLine(width-axisPad, axisPad-scalePad, width-axisPad, height-(axisPad-scalePad)); 659 } else { 660 g.drawLine(origin.x, axisPad-scalePad, origin.x, height-(axisPad-scalePad)); 662 } 663 } 664 } 665 protected void drawXLabel(Graphics g, double x, Point p) { 666 double scaledX; 667 if(xScaleType == LOG_SCALE) 668 scaledX = Math.exp(x); 669 else 670 scaledX = x; 671 String str = xNumberFormat.format(scaledX); 672 FontMetrics metrics=g.getFontMetrics(); 673 int strWidth=metrics.stringWidth(str); 674 int strHeight=metrics.getHeight(); 675 g.drawLine(p.x,p.y,p.x,p.y+5); 676 g.drawString(str,p.x-strWidth/2,p.y+5+strHeight); 677 } 678 protected void drawYLabel(Graphics g, double y, Point p) { 679 double scaledY; 680 if(yScaleType == LOG_SCALE) 681 scaledY = Math.exp(y); 682 else 683 scaledY = y; 684 String str = yNumberFormat.format(scaledY); 685 FontMetrics metrics=g.getFontMetrics(); 686 int strWidth=metrics.stringWidth(str); 687 int strHeight=metrics.getHeight(); 688 g.drawLine(p.x,p.y,p.x-5,p.y); 689 g.drawString(str,p.x-8-strWidth,p.y+strHeight/3); 690 } 691 695 protected void drawData(Graphics g) { 696 model.firstSeries(); 698 g.setColor(seriesColor[0]); 699 int i; 700 for(i=0; i<model.seriesLength(); i++) 701 drawDataPoint(g, i); 702 for(int n=1; model.nextSeries(); n++) { 703 g.setColor(seriesColor[n]); 704 for(i=0; i<model.seriesLength(); i++) 705 drawDataPoint(g, i); 706 } 707 } 708 712 private void drawDataPoint(Graphics g, int i) { 713 Point p = dataToScreen(model.getXCoord(i), model.getYCoord(i)); 714 dataMarker.paint(g, p.x, p.y); 715 } 716 719 protected void offscreenPaint(Graphics g) { 720 drawAxes(g); 721 final Dimension size = getSize(); 722 final int width = size.width; 723 final int height = size.height; 724 g.setClip(leftAxisPad-scalePad, axisPad-scalePad, width-(leftAxisPad+axisPad-2*scalePad), height-2*(axisPad-scalePad)); 725 drawData(g); 726 } 727 728 public interface DataMarker { 729 public void paint(Graphics g, int x, int y); 730 731 public static final DataMarker NONE = new NoneDataMarker(); 732 public class Circle implements DataMarker { 733 private final int size; 734 public Circle(int size) { 735 this.size = size; 736 } 737 public void paint(Graphics g, int x, int y) { 738 g.fillOval(x-size/2, y-size/2, size, size); 739 } 740 } 741 public class Square implements DataMarker { 742 private final int size; 743 public Square(int size) { 744 this.size = size; 745 } 746 public void paint(Graphics g, int x, int y) { 747 g.fillRect(x-size/2, y-size/2, size, size); 748 } 749 } 750 } 751 private static class NoneDataMarker implements DataMarker { 752 private NoneDataMarker() {} 753 public void paint(Graphics g, int x, int y) {} 754 } 755 } 756 757 | Popular Tags |