| 1 167 168 package org.jfree.chart.plot; 169 170 import java.awt.AlphaComposite ; 171 import java.awt.BasicStroke ; 172 import java.awt.Color ; 173 import java.awt.Composite ; 174 import java.awt.Graphics2D ; 175 import java.awt.Paint ; 176 import java.awt.Shape ; 177 import java.awt.Stroke ; 178 import java.awt.geom.Line2D ; 179 import java.awt.geom.Point2D ; 180 import java.awt.geom.Rectangle2D ; 181 import java.io.IOException ; 182 import java.io.ObjectInputStream ; 183 import java.io.ObjectOutputStream ; 184 import java.io.Serializable ; 185 import java.util.ArrayList ; 186 import java.util.Collection ; 187 import java.util.Collections ; 188 import java.util.HashMap ; 189 import java.util.Iterator ; 190 import java.util.List ; 191 import java.util.Map ; 192 import java.util.ResourceBundle ; 193 import java.util.TreeMap ; 194 195 import org.jfree.chart.LegendItem; 196 import org.jfree.chart.LegendItemCollection; 197 import org.jfree.chart.annotations.XYAnnotation; 198 import org.jfree.chart.axis.Axis; 199 import org.jfree.chart.axis.AxisCollection; 200 import org.jfree.chart.axis.AxisLocation; 201 import org.jfree.chart.axis.AxisSpace; 202 import org.jfree.chart.axis.AxisState; 203 import org.jfree.chart.axis.ValueAxis; 204 import org.jfree.chart.axis.ValueTick; 205 import org.jfree.chart.event.ChartChangeEventType; 206 import org.jfree.chart.event.PlotChangeEvent; 207 import org.jfree.chart.event.RendererChangeEvent; 208 import org.jfree.chart.event.RendererChangeListener; 209 import org.jfree.chart.renderer.xy.XYItemRenderer; 210 import org.jfree.chart.renderer.xy.XYItemRendererState; 211 import org.jfree.data.Range; 212 import org.jfree.data.general.Dataset; 213 import org.jfree.data.general.DatasetChangeEvent; 214 import org.jfree.data.general.DatasetUtilities; 215 import org.jfree.data.xy.XYDataset; 216 import org.jfree.io.SerialUtilities; 217 import org.jfree.ui.Layer; 218 import org.jfree.ui.RectangleEdge; 219 import org.jfree.ui.RectangleInsets; 220 import org.jfree.util.ObjectList; 221 import org.jfree.util.ObjectUtilities; 222 import org.jfree.util.PublicCloneable; 223 224 235 public class XYPlot extends Plot implements ValueAxisPlot, 236 Zoomable, 237 RendererChangeListener, 238 Cloneable , PublicCloneable, 239 Serializable { 240 241 242 private static final long serialVersionUID = 7044148245716569264L; 243 244 245 public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke ( 246 0.5f, 247 BasicStroke.CAP_BUTT, 248 BasicStroke.JOIN_BEVEL, 249 0.0f, 250 new float[] {2.0f, 2.0f}, 251 0.0f 252 ); 253 254 255 public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray; 256 257 258 public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false; 259 260 261 public static final Stroke DEFAULT_CROSSHAIR_STROKE 262 = DEFAULT_GRIDLINE_STROKE; 263 264 265 public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue; 266 267 268 protected static ResourceBundle localizationResources 269 = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle"); 270 271 272 private PlotOrientation orientation; 273 274 275 private RectangleInsets axisOffset; 276 277 278 private ObjectList domainAxes; 279 280 281 private ObjectList domainAxisLocations; 282 283 284 private ObjectList rangeAxes; 285 286 287 private ObjectList rangeAxisLocations; 288 289 290 private ObjectList datasets; 291 292 293 private ObjectList renderers; 294 295 300 private Map datasetToDomainAxisMap; 301 302 307 private Map datasetToRangeAxisMap; 308 309 310 private transient Point2D quadrantOrigin = new Point2D.Double (0.0, 0.0); 311 312 313 private transient Paint [] quadrantPaint 314 = new Paint [] {null, null, null, null}; 315 316 317 private boolean domainGridlinesVisible; 318 319 320 private transient Stroke domainGridlineStroke; 321 322 323 private transient Paint domainGridlinePaint; 324 325 326 private boolean rangeGridlinesVisible; 327 328 329 private transient Stroke rangeGridlineStroke; 330 331 332 private transient Paint rangeGridlinePaint; 333 334 338 private boolean rangeZeroBaselineVisible; 339 340 341 private transient Stroke rangeZeroBaselineStroke; 342 343 344 private transient Paint rangeZeroBaselinePaint; 345 346 347 private boolean domainCrosshairVisible; 348 349 350 private double domainCrosshairValue; 351 352 353 private transient Stroke domainCrosshairStroke; 354 355 356 private transient Paint domainCrosshairPaint; 357 358 362 private boolean domainCrosshairLockedOnData = true; 363 364 365 private boolean rangeCrosshairVisible; 366 367 368 private double rangeCrosshairValue; 369 370 371 private transient Stroke rangeCrosshairStroke; 372 373 374 private transient Paint rangeCrosshairPaint; 375 376 380 private boolean rangeCrosshairLockedOnData = true; 381 382 383 private Map foregroundDomainMarkers; 384 385 386 private Map backgroundDomainMarkers; 387 388 389 private Map foregroundRangeMarkers; 390 391 392 private Map backgroundRangeMarkers; 393 394 399 private List annotations; 400 401 402 private transient Paint domainTickBandPaint; 403 404 405 private transient Paint rangeTickBandPaint; 406 407 408 private AxisSpace fixedDomainAxisSpace; 409 410 411 private AxisSpace fixedRangeAxisSpace; 412 413 417 private DatasetRenderingOrder datasetRenderingOrder 418 = DatasetRenderingOrder.REVERSE; 419 420 424 private SeriesRenderingOrder seriesRenderingOrder 425 = SeriesRenderingOrder.REVERSE; 426 427 431 private int weight; 432 433 437 private LegendItemCollection fixedLegendItems; 438 439 442 public XYPlot() { 443 this(null, null, null, null); 444 } 445 446 454 public XYPlot(XYDataset dataset, 455 ValueAxis domainAxis, 456 ValueAxis rangeAxis, 457 XYItemRenderer renderer) { 458 459 super(); 460 461 this.orientation = PlotOrientation.VERTICAL; 462 this.weight = 1; this.axisOffset = RectangleInsets.ZERO_INSETS; 464 465 this.domainAxes = new ObjectList(); 467 this.domainAxisLocations = new ObjectList(); 468 this.foregroundDomainMarkers = new HashMap (); 469 this.backgroundDomainMarkers = new HashMap (); 470 471 this.rangeAxes = new ObjectList(); 472 this.rangeAxisLocations = new ObjectList(); 473 this.foregroundRangeMarkers = new HashMap (); 474 this.backgroundRangeMarkers = new HashMap (); 475 476 this.datasets = new ObjectList(); 477 this.renderers = new ObjectList(); 478 479 this.datasetToDomainAxisMap = new TreeMap (); 480 this.datasetToRangeAxisMap = new TreeMap (); 481 482 this.datasets.set(0, dataset); 483 if (dataset != null) { 484 dataset.addChangeListener(this); 485 } 486 487 this.renderers.set(0, renderer); 488 if (renderer != null) { 489 renderer.setPlot(this); 490 renderer.addChangeListener(this); 491 } 492 493 this.domainAxes.set(0, domainAxis); 494 this.mapDatasetToDomainAxis(0, 0); 495 if (domainAxis != null) { 496 domainAxis.setPlot(this); 497 domainAxis.addChangeListener(this); 498 } 499 this.domainAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT); 500 501 this.rangeAxes.set(0, rangeAxis); 502 this.mapDatasetToRangeAxis(0, 0); 503 if (rangeAxis != null) { 504 rangeAxis.setPlot(this); 505 rangeAxis.addChangeListener(this); 506 } 507 this.rangeAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT); 508 509 configureDomainAxes(); 510 configureRangeAxes(); 511 512 this.domainGridlinesVisible = true; 513 this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE; 514 this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT; 515 516 this.rangeGridlinesVisible = true; 517 this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE; 518 this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT; 519 520 this.rangeZeroBaselineVisible = false; 521 this.rangeZeroBaselinePaint = Color.black; 522 this.rangeZeroBaselineStroke = new BasicStroke (0.5f); 523 524 this.domainCrosshairVisible = false; 525 this.domainCrosshairValue = 0.0; 526 this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; 527 this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; 528 529 this.rangeCrosshairVisible = false; 530 this.rangeCrosshairValue = 0.0; 531 this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; 532 this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; 533 534 this.annotations = new java.util.ArrayList (); 535 536 } 537 538 543 public String getPlotType() { 544 return localizationResources.getString("XY_Plot"); 545 } 546 547 552 public PlotOrientation getOrientation() { 553 return this.orientation; 554 } 555 556 561 public void setOrientation(PlotOrientation orientation) { 562 if (orientation == null) { 563 throw new IllegalArgumentException ("Null 'orientation' argument."); 564 } 565 if (orientation != this.orientation) { 566 this.orientation = orientation; 567 notifyListeners(new PlotChangeEvent(this)); 568 } 569 } 570 571 576 public RectangleInsets getAxisOffset() { 577 return this.axisOffset; 578 } 579 580 585 public void setAxisOffset(RectangleInsets offset) { 586 if (offset == null) { 587 throw new IllegalArgumentException ("Null 'offset' argument."); 588 } 589 this.axisOffset = offset; 590 notifyListeners(new PlotChangeEvent(this)); 591 } 592 593 600 public ValueAxis getDomainAxis() { 601 return getDomainAxis(0); 602 } 603 604 611 public ValueAxis getDomainAxis(int index) { 612 ValueAxis result = null; 613 if (index < this.domainAxes.size()) { 614 result = (ValueAxis) this.domainAxes.get(index); 615 } 616 if (result == null) { 617 Plot parent = getParent(); 618 if (parent instanceof XYPlot) { 619 XYPlot xy = (XYPlot) parent; 620 result = xy.getDomainAxis(index); 621 } 622 } 623 return result; 624 } 625 626 632 public void setDomainAxis(ValueAxis axis) { 633 setDomainAxis(0, axis); 634 } 635 636 643 public void setDomainAxis(int index, ValueAxis axis) { 644 setDomainAxis(index, axis, true); 645 } 646 647 655 public void setDomainAxis(int index, ValueAxis axis, boolean notify) { 656 ValueAxis existing = getDomainAxis(index); 657 if (existing != null) { 658 existing.removeChangeListener(this); 659 } 660 if (axis != null) { 661 axis.setPlot(this); 662 } 663 this.domainAxes.set(index, axis); 664 if (axis != null) { 665 axis.configure(); 666 axis.addChangeListener(this); 667 } 668 if (notify) { 669 notifyListeners(new PlotChangeEvent(this)); 670 } 671 } 672 673 679 public void setDomainAxes(ValueAxis[] axes) { 680 for (int i = 0; i < axes.length; i++) { 681 setDomainAxis(i, axes[i], false); 682 } 683 notifyListeners(new PlotChangeEvent(this)); 684 } 685 686 691 public AxisLocation getDomainAxisLocation() { 692 return (AxisLocation) this.domainAxisLocations.get(0); 693 } 694 695 701 public void setDomainAxisLocation(AxisLocation location) { 702 setDomainAxisLocation(location, true); 704 } 705 706 713 public void setDomainAxisLocation(AxisLocation location, boolean notify) { 714 if (location == null) { 715 throw new IllegalArgumentException ("Null 'location' argument."); 716 } 717 this.domainAxisLocations.set(0, location); 718 if (notify) { 719 notifyListeners(new PlotChangeEvent(this)); 720 } 721 } 722 723 729 public RectangleEdge getDomainAxisEdge() { 730 return Plot.resolveDomainAxisLocation( 731 getDomainAxisLocation(), this.orientation 732 ); 733 } 734 735 740 public int getDomainAxisCount() { 741 return this.domainAxes.size(); 742 } 743 744 748 public void clearDomainAxes() { 749 for (int i = 0; i < this.domainAxes.size(); i++) { 750 ValueAxis axis = (ValueAxis) this.domainAxes.get(i); 751 if (axis != null) { 752 axis.removeChangeListener(this); 753 } 754 } 755 this.domainAxes.clear(); 756 notifyListeners(new PlotChangeEvent(this)); 757 } 758 759 762 public void configureDomainAxes() { 763 for (int i = 0; i < this.domainAxes.size(); i++) { 764 ValueAxis axis = (ValueAxis) this.domainAxes.get(i); 765 if (axis != null) { 766 axis.configure(); 767 } 768 } 769 } 770 771 780 public AxisLocation getDomainAxisLocation(int index) { 781 AxisLocation result = null; 782 if (index < this.domainAxisLocations.size()) { 783 result = (AxisLocation) this.domainAxisLocations.get(index); 784 } 785 if (result == null) { 786 result = AxisLocation.getOpposite(getDomainAxisLocation()); 787 } 788 return result; 789 } 790 791 798 public void setDomainAxisLocation(int index, AxisLocation location) { 799 this.domainAxisLocations.set(index, location); 800 notifyListeners(new PlotChangeEvent(this)); 801 } 802 803 810 public RectangleEdge getDomainAxisEdge(int index) { 811 AxisLocation location = getDomainAxisLocation(index); 812 RectangleEdge result = Plot.resolveDomainAxisLocation( 813 location, this.orientation 814 ); 815 if (result == null) { 816 result = RectangleEdge.opposite(getDomainAxisEdge()); 817 } 818 return result; 819 } 820 821 828 public ValueAxis getRangeAxis() { 829 return getRangeAxis(0); 830 } 831 832 839 public void setRangeAxis(ValueAxis axis) { 840 841 if (axis != null) { 842 axis.setPlot(this); 843 } 844 845 ValueAxis existing = getRangeAxis(); 847 if (existing != null) { 848 existing.removeChangeListener(this); 849 } 850 851 this.rangeAxes.set(0, axis); 852 if (axis != null) { 853 axis.configure(); 854 axis.addChangeListener(this); 855 } 856 notifyListeners(new PlotChangeEvent(this)); 857 858 } 859 860 865 public AxisLocation getRangeAxisLocation() { 866 return (AxisLocation) this.rangeAxisLocations.get(0); 867 } 868 869 875 public void setRangeAxisLocation(AxisLocation location) { 876 setRangeAxisLocation(location, true); 878 } 879 880 887 public void setRangeAxisLocation(AxisLocation location, boolean notify) { 888 if (location == null) { 889 throw new IllegalArgumentException ("Null 'location' argument."); 890 } 891 this.rangeAxisLocations.set(0, location); 892 if (notify) { 893 notifyListeners(new PlotChangeEvent(this)); 894 } 895 896 } 897 898 903 public RectangleEdge getRangeAxisEdge() { 904 return Plot.resolveRangeAxisLocation( 905 getRangeAxisLocation(), this.orientation 906 ); 907 } 908 909 916 public ValueAxis getRangeAxis(int index) { 917 ValueAxis result = null; 918 if (index < this.rangeAxes.size()) { 919 result = (ValueAxis) this.rangeAxes.get(index); 920 |