001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2022, by David Gilbert. All rights reserved. 006 * 007 * https://github.com/jfree/orson-charts 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package org.jfree.chart3d.axis; 034 035import java.awt.BasicStroke; 036import java.awt.Color; 037import java.awt.Paint; 038import java.awt.Stroke; 039import java.io.IOException; 040import java.io.ObjectInputStream; 041import java.io.ObjectOutputStream; 042import java.io.Serializable; 043import java.util.ArrayList; 044import java.util.LinkedHashMap; 045import java.util.List; 046import java.util.Map; 047 048import org.jfree.chart3d.internal.Args; 049import org.jfree.chart3d.internal.ObjectUtils; 050import org.jfree.chart3d.internal.SerialUtils; 051import org.jfree.chart3d.ChartElementVisitor; 052import org.jfree.chart3d.data.Range; 053import org.jfree.chart3d.data.category.CategoryDataset3D; 054import org.jfree.chart3d.marker.MarkerData; 055import org.jfree.chart3d.marker.NumberMarker; 056import org.jfree.chart3d.marker.RangeMarker; 057import org.jfree.chart3d.marker.ValueMarker; 058import org.jfree.chart3d.plot.CategoryPlot3D; 059import org.jfree.chart3d.plot.XYZPlot; 060 061/** 062 * A base class for implementing numerical axes. 063 * <br><br> 064 * NOTE: This class is serializable, but the serialization format is subject 065 * to change in future releases and should not be relied upon for persisting 066 * instances of this class. 067 */ 068@SuppressWarnings("serial") 069public abstract class AbstractValueAxis3D extends AbstractAxis3D 070 implements ValueAxis3D, Serializable{ 071 072 /** The type of use for which the axis has been configured. */ 073 private ValueAxis3DType configuredType; 074 075 /** The axis range. */ 076 protected Range range; 077 078 private boolean inverted; 079 080 /** 081 * A flag that controls whether or not the axis range is automatically 082 * adjusted to display all of the data items in the dataset. 083 */ 084 private boolean autoAdjustRange; 085 086 /** The percentage margin to leave at the lower end of the axis. */ 087 private double lowerMargin; 088 089 /** The percentage margin to leave at the upper end of the axis. */ 090 private double upperMargin; 091 092 /** 093 * The default range to apply when there is no data in the dataset and the 094 * autoAdjustRange flag is true. A sensible default is going to depend on 095 * the context, so the user should change it as necessary. 096 */ 097 private Range defaultAutoRange; 098 099 /** 100 * The minimum length for the axis range when auto-calculated. This will 101 * be applied, for example, when the dataset contains just a single value. 102 */ 103 private double minAutoRangeLength; 104 105 /** The tick label offset (number of Java2D units). */ 106 private double tickLabelOffset; 107 108 /** The length of tick marks (in Java2D units). Can be set to 0.0. */ 109 private double tickMarkLength; 110 111 /** The tick mark stroke (never {@code null}). */ 112 private transient Stroke tickMarkStroke; 113 114 /** The tick mark paint (never {@code null}). */ 115 private transient Paint tickMarkPaint; 116 117 /** The orientation for the tick labels. */ 118 private LabelOrientation tickLabelOrientation; 119 120 /** The tick label factor (defaults to 1.4). */ 121 private double tickLabelFactor; 122 123 /** Storage for value markers for the axis (empty by default). */ 124 private final Map<String, ValueMarker> valueMarkers; 125 126 /** 127 * Creates a new axis instance. 128 * 129 * @param label the axis label ({@code null} permitted). 130 * @param range the axis range ({@code null} not permitted). 131 */ 132 public AbstractValueAxis3D(String label, Range range) { 133 super(label); 134 Args.nullNotPermitted(range, "range"); 135 this.configuredType = null; 136 this.range = range; 137 this.autoAdjustRange = true; 138 this.lowerMargin = 0.05; 139 this.upperMargin = 0.05; 140 this.defaultAutoRange = new Range(0.0, 1.0); 141 this.minAutoRangeLength = 0.001; 142 this.tickLabelOffset = 5.0; 143 this.tickLabelOrientation = LabelOrientation.PARALLEL; 144 this.tickLabelFactor = 1.4; 145 this.tickMarkLength = 3.0; 146 this.tickMarkStroke = new BasicStroke(0.5f); 147 this.tickMarkPaint = Color.GRAY; 148 this.valueMarkers = new LinkedHashMap<>(); 149 } 150 151 /** 152 * Returns the configured type for the axis. 153 * 154 * @return The configured type ({@code null} if the axis has not yet 155 * been assigned to a plot). 156 * 157 * @since 1.3 158 */ 159 @Override 160 public ValueAxis3DType getConfiguredType() { 161 return this.configuredType; 162 } 163 164 /** 165 * Returns a string representing the configured type of the axis. 166 * 167 * @return A string. 168 */ 169 @Override 170 protected String axisStr() { 171 if (this.configuredType == null) { 172 return ""; 173 } 174 if (this.configuredType.equals(ValueAxis3DType.VALUE)) { 175 return "value"; 176 } 177 if (this.configuredType.equals(ValueAxis3DType.X)) { 178 return "x"; 179 } 180 if (this.configuredType.equals(ValueAxis3DType.Y)) { 181 return "y"; 182 } 183 if (this.configuredType.equals(ValueAxis3DType.Z)) { 184 return "z"; 185 } 186 return ""; 187 } 188 189 /** 190 * Returns the axis range. You can set the axis range manually or you can 191 * rely on the autoAdjustRange feature to set the axis range to match 192 * the data being plotted. 193 * 194 * @return the axis range (never {@code null}). 195 */ 196 @Override 197 public Range getRange() { 198 return this.range; 199 } 200 201 /** 202 * Sets the axis range (bounds) and sends an {@link Axis3DChangeEvent} to 203 * all registered listeners. 204 * 205 * @param range the new range (must have positive length and 206 * {@code null} is not permitted). 207 */ 208 @Override 209 public void setRange(Range range) { 210 Args.nullNotPermitted(range, "range"); 211 if (range.getLength() <= 0.0) { 212 throw new IllegalArgumentException( 213 "Requires a range with length > 0"); 214 } 215 this.range = range; 216 this.autoAdjustRange = false; 217 fireChangeEvent(true); 218 } 219 220 /** 221 * Updates the axis range (used by the auto-range calculation) without 222 * notifying listeners. 223 * 224 * @param range the new range. 225 */ 226 protected void updateRange(Range range) { 227 this.range = range; 228 } 229 230 /** 231 * Sets the axis range and sends an {@link Axis3DChangeEvent} to all 232 * registered listeners. 233 * 234 * @param min the lower bound for the range (requires min < max). 235 * @param max the upper bound for the range (requires max > min). 236 */ 237 @Override 238 public void setRange(double min, double max) { 239 setRange(new Range(min, max)); 240 } 241 242 /** 243 * Returns the flag that controls whether or not the axis range is 244 * automatically updated in response to dataset changes. The default 245 * value is {@code true}. 246 * 247 * @return A boolean. 248 */ 249 public boolean isAutoAdjustRange() { 250 return this.autoAdjustRange; 251 } 252 253 /** 254 * Sets the flag that controls whether or not the axis range is 255 * automatically updated in response to dataset changes, and sends an 256 * {@link Axis3DChangeEvent} to all registered listeners. 257 * 258 * @param autoAdjust the new flag value. 259 */ 260 public void setAutoAdjustRange(boolean autoAdjust) { 261 this.autoAdjustRange = autoAdjust; 262 fireChangeEvent(true); 263 } 264 265 /** 266 * Returns the size of the lower margin that is added by the auto-range 267 * calculation, as a percentage of the data range. This margin is used to 268 * prevent data items from being plotted right at the edges of the chart. 269 * The default value is {@code 0.05} (five percent). 270 * 271 * @return The lower margin. 272 */ 273 public double getLowerMargin() { 274 return this.lowerMargin; 275 } 276 277 /** 278 * Sets the size of the lower margin that will be added by the auto-range 279 * calculation and sends an {@link Axis3DChangeEvent} to all registered 280 * listeners. 281 * 282 * @param margin the margin as a percentage of the data range 283 * (0.05 = five percent). 284 * 285 * @see #setUpperMargin(double) 286 */ 287 public void setLowerMargin(double margin) { 288 this.lowerMargin = margin; 289 fireChangeEvent(true); 290 } 291 292 /** 293 * Returns the size of the upper margin that is added by the auto-range 294 * calculation, as a percentage of the data range. This margin is used to 295 * prevent data items from being plotted right at the edges of the chart. 296 * The default value is {@code 0.05} (five percent). 297 * 298 * @return The upper margin. 299 */ 300 public double getUpperMargin() { 301 return this.upperMargin; 302 } 303 304 /** 305 * Sets the size of the upper margin that will be added by the auto-range 306 * calculation and sends an {@link Axis3DChangeEvent} to all registered 307 * listeners. 308 * 309 * @param margin the margin as a percentage of the data range 310 * (0.05 = five percent). 311 * 312 * @see #setLowerMargin(double) 313 */ 314 public void setUpperMargin(double margin) { 315 this.upperMargin = margin; 316 fireChangeEvent(true); 317 } 318 319 320 /** 321 * Returns the default range used when the {@code autoAdjustRange} 322 * flag is {@code true} but the dataset contains no values. The 323 * default range is {@code (0.0 to 1.0)}, depending on the context 324 * you may want to change this. 325 * 326 * @return The default range (never {@code null}). 327 * 328 * @see #setDefaultAutoRange(Range) 329 */ 330 public Range getDefaultAutoRange() { 331 return this.defaultAutoRange; 332 } 333 334 /** 335 * Sets the default range used when the {@code autoAdjustRange} 336 * flag is {@code true} but the dataset contains no values, and sends 337 * an {@link Axis3DChangeEvent} to all registered listeners. 338 * 339 * @param range the range ({@code null} not permitted). 340 * 341 * @see #getDefaultAutoRange() 342 */ 343 public void setDefaultAutoRange(Range range) { 344 Args.nullNotPermitted(range, "range"); 345 this.defaultAutoRange = range; 346 fireChangeEvent(true); 347 } 348 349 /** 350 * Returns the minimum length for the axis range when auto-calculated. 351 * The default value is 0.001. 352 * 353 * @return The minimum length. 354 * 355 * @since 1.4 356 */ 357 public double getMinAutoRangeLength() { 358 return this.minAutoRangeLength; 359 } 360 361 /** 362 * Sets the minimum length for the axis range when it is auto-calculated 363 * and sends a change event to all registered listeners. 364 * 365 * @param length the new minimum length. 366 * 367 * @since 1.4 368 */ 369 public void setMinAutoRangeLength(double length) { 370 Args.positiveRequired(length, "length"); 371 this.minAutoRangeLength = length; 372 fireChangeEvent(this.range.getLength() < length); 373 } 374 375 /** 376 * Returns the flag that determines whether or not the order of values on 377 * the axis is inverted. The default value is {@code false}. 378 * 379 * @return A boolean. 380 * 381 * @since 1.5 382 */ 383 @Override 384 public boolean isInverted() { 385 return this.inverted; 386 } 387 388 /** 389 * Sets the flag that determines whether or not the order of values on the 390 * axis is inverted, and sends an {@link Axis3DChangeEvent} to all 391 * registered listeners. 392 * 393 * @param inverted the new flag value. 394 * 395 * @since 1.5 396 */ 397 @Override 398 public void setInverted(boolean inverted) { 399 this.inverted = inverted; 400 fireChangeEvent(true); 401 } 402 403 /** 404 * Returns the orientation for the tick labels. The default value is 405 * {@link LabelOrientation#PARALLEL}. 406 * 407 * @return The orientation for the tick labels (never {@code null}). 408 * 409 * @since 1.2 410 */ 411 public LabelOrientation getTickLabelOrientation() { 412 return this.tickLabelOrientation; 413 } 414 415 /** 416 * Sets the orientation for the tick labels and sends a change event to 417 * all registered listeners. In general, {@code PARALLEL} is the 418 * best setting for X and Z axes, and {@code PERPENDICULAR} is the 419 * best setting for Y axes. 420 * 421 * @param orientation the orientation ({@code null} not permitted). 422 * 423 * @since 1.2 424 */ 425 public void setTickLabelOrientation(LabelOrientation orientation) { 426 Args.nullNotPermitted(orientation, "orientation"); 427 this.tickLabelOrientation = orientation; 428 fireChangeEvent(false); 429 } 430 431 /** 432 * Returns the tick label factor, a multiplier for the label height to 433 * determine the maximum number of tick labels that can be displayed. 434 * The default value is {@code 1.4}. 435 * 436 * @return The tick label factor. 437 */ 438 public double getTickLabelFactor() { 439 return this.tickLabelFactor; 440 } 441 442 /** 443 * Sets the tick label factor and sends an {@link Axis3DChangeEvent} 444 * to all registered listeners. This should be at least 1.0, higher values 445 * will result in larger gaps between the tick marks. 446 * 447 * @param factor the factor. 448 */ 449 public void setTickLabelFactor(double factor) { 450 this.tickLabelFactor = factor; 451 fireChangeEvent(false); 452 } 453 454 /** 455 * Returns the tick label offset, the gap between the tick marks and the 456 * tick labels (in Java2D units). The default value is {@code 5.0}. 457 * 458 * @return The tick label offset. 459 */ 460 public double getTickLabelOffset() { 461 return this.tickLabelOffset; 462 } 463 464 /** 465 * Sets the tick label offset and sends an {@link Axis3DChangeEvent} to 466 * all registered listeners. 467 * 468 * @param offset the offset. 469 */ 470 public void setTickLabelOffset(double offset) { 471 this.tickLabelOffset = offset; 472 } 473 474 /** 475 * Returns the length of the tick marks (in Java2D units). The default 476 * value is {@code 3.0}. 477 * 478 * @return The length of the tick marks. 479 */ 480 public double getTickMarkLength() { 481 return this.tickMarkLength; 482 } 483 484 /** 485 * Sets the length of the tick marks and sends an {@link Axis3DChangeEvent} 486 * to all registered listeners. You can set this to {@code 0.0} if 487 * you prefer no tick marks to be displayed on the axis. 488 * 489 * @param length the length (in Java2D units). 490 */ 491 public void setTickMarkLength(double length) { 492 this.tickMarkLength = length; 493 fireChangeEvent(false); 494 } 495 496 /** 497 * Returns the stroke used to draw the tick marks. The default value is 498 * {@code BasicStroke(0.5f)}. 499 * 500 * @return The tick mark stroke (never {@code null}). 501 */ 502 public Stroke getTickMarkStroke() { 503 return this.tickMarkStroke; 504 } 505 506 /** 507 * Sets the stroke used to draw the tick marks and sends an 508 * {@link Axis3DChangeEvent} to all registered listeners. 509 * 510 * @param stroke the stroke ({@code null} not permitted). 511 */ 512 public void setTickMarkStroke(Stroke stroke) { 513 Args.nullNotPermitted(stroke, "stroke"); 514 this.tickMarkStroke = stroke; 515 fireChangeEvent(false); 516 } 517 518 /** 519 * Returns the paint used to draw the tick marks. The default value is 520 * {@code Color.GRAY}. 521 * 522 * @return The tick mark paint (never {@code null}). 523 */ 524 public Paint getTickMarkPaint() { 525 return this.tickMarkPaint; 526 } 527 528 /** 529 * Sets the paint used to draw the tick marks and sends an 530 * {@link Axis3DChangeEvent} to all registered listeners. 531 * 532 * @param paint the paint ({@code null} not permitted). 533 */ 534 public void setTickMarkPaint(Paint paint) { 535 Args.nullNotPermitted(paint, "paint"); 536 this.tickMarkPaint = paint; 537 fireChangeEvent(false); 538 } 539 540 /** 541 * Configures the axis to be used as the value axis for the specified 542 * plot. This method is used internally, you should not need to call it 543 * directly. 544 * 545 * @param plot the plot ({@code null} not permitted). 546 */ 547 @Override @SuppressWarnings("unchecked") 548 public void configureAsValueAxis(CategoryPlot3D plot) { 549 this.configuredType = ValueAxis3DType.VALUE; 550 if (this.autoAdjustRange) { 551 CategoryDataset3D dataset = plot.getDataset(); 552 Range valueRange = plot.getRenderer().findValueRange(dataset); 553 if (valueRange != null) { 554 updateRange(adjustedDataRange(valueRange)); 555 } else { 556 updateRange(this.defaultAutoRange); 557 } 558 } 559 } 560 561 /** 562 * Configures the axis to be used as the x-axis for the specified plot. 563 * This method is used internally, you should not need to call it 564 * directly. 565 * 566 * @param plot the plot ({@code null} not permitted). 567 */ 568 @Override 569 public void configureAsXAxis(XYZPlot plot) { 570 this.configuredType = ValueAxis3DType.X; 571 if (this.autoAdjustRange) { 572 Range xRange = plot.getRenderer().findXRange(plot.getDataset()); 573 if (xRange != null) { 574 updateRange(adjustedDataRange(xRange)); 575 } else { 576 updateRange(this.defaultAutoRange); 577 } 578 } 579 } 580 581 /** 582 * Configures the axis to be used as the y-axis for the specified plot. 583 * This method is used internally, you should not need to call it 584 * directly. 585 * 586 * @param plot the plot ({@code null} not permitted). 587 */ 588 @Override 589 public void configureAsYAxis(XYZPlot plot) { 590 this.configuredType = ValueAxis3DType.Y; 591 if (this.autoAdjustRange) { 592 Range yRange = plot.getRenderer().findYRange(plot.getDataset()); 593 if (yRange != null) { 594 updateRange(adjustedDataRange(yRange)); 595 } else { 596 updateRange(this.defaultAutoRange); 597 } 598 } 599 } 600 601 /** 602 * Configures the axis to be used as the z-axis for the specified plot. 603 * This method is used internally, you should not need to call it 604 * directly. 605 * 606 * @param plot the plot ({@code null} not permitted). 607 */ 608 @Override 609 public void configureAsZAxis(XYZPlot plot) { 610 this.configuredType = ValueAxis3DType.Z; 611 if (this.autoAdjustRange) { 612 Range zRange = plot.getRenderer().findZRange(plot.getDataset()); 613 if (zRange != null) { 614 updateRange(adjustedDataRange(zRange)); 615 } else { 616 updateRange(this.defaultAutoRange); 617 } 618 } 619 } 620 621 /** 622 * Adjusts the range by adding the lower and upper margins and taking into 623 * account any other settings. 624 * 625 * @param range the range ({@code null} not permitted). 626 * 627 * @return The adjusted range. 628 */ 629 protected abstract Range adjustedDataRange(Range range); 630 631 /** 632 * Returns the marker with the specified key, if there is one. 633 * 634 * @param key the key ({@code null} not permitted). 635 * 636 * @return The marker (possibly {@code null}). 637 * 638 * @since 1.2 639 */ 640 @Override 641 public ValueMarker getMarker(String key) { 642 return this.valueMarkers.get(key); 643 } 644 645 /** 646 * Sets the marker for the specified key and sends a change event to 647 * all registered listeners. If there is an existing marker it is replaced 648 * (the axis will no longer listen for change events on the previous 649 * marker). 650 * 651 * @param key the key that identifies the marker ({@code null} not 652 * permitted). 653 * @param marker the marker ({@code null} permitted). 654 * 655 * @since 1.2 656 */ 657 public void setMarker(String key, ValueMarker marker) { 658 ValueMarker existing = this.valueMarkers.get(key); 659 if (existing != null) { 660 existing.removeChangeListener(this); 661 } 662 this.valueMarkers.put(key, marker); 663 marker.addChangeListener(this); 664 fireChangeEvent(false); 665 } 666 667 /** 668 * Returns a new map containing the markers assigned to this axis. 669 * 670 * @return A map. 671 * 672 * @since 1.2 673 */ 674 public Map<String, ValueMarker> getMarkers() { 675 return new LinkedHashMap<>(this.valueMarkers); 676 } 677 678 /** 679 * Generates and returns a list of marker data items for the axis. 680 * 681 * @return A list of marker data items (never {@code null}). 682 */ 683 @Override 684 public List<MarkerData> generateMarkerData() { 685 List<MarkerData> result = new ArrayList<>(); 686 Range range = getRange(); 687 for (Map.Entry<String, ValueMarker> entry 688 : this.valueMarkers.entrySet()) { 689 ValueMarker vm = entry.getValue(); 690 if (range.intersects(vm.getRange())) { 691 MarkerData markerData; 692 if (vm instanceof NumberMarker) { 693 NumberMarker nm = (NumberMarker) vm; 694 markerData = new MarkerData(entry.getKey(), 695 range.percent(nm.getValue())); 696 markerData.setLabelAnchor(nm.getLabel() != null 697 ? nm.getLabelAnchor() : null); 698 } else if (vm instanceof RangeMarker) { 699 RangeMarker rm = (RangeMarker) vm; 700 double startValue = rm.getStart().getValue(); 701 boolean startPegged = false; 702 if (!range.contains(startValue)) { 703 startValue = range.peggedValue(startValue); 704 startPegged = true; 705 } 706 double startPos = range.percent(startValue); 707 double endValue = rm.getEnd().getValue(); 708 boolean endPegged = false; 709 if (!range.contains(endValue)) { 710 endValue = range.peggedValue(endValue); 711 endPegged = true; 712 } 713 double endPos = range.percent(endValue); 714 markerData = new MarkerData(entry.getKey(), startPos, 715 startPegged, endPos, endPegged); 716 markerData.setLabelAnchor(rm.getLabel() != null 717 ? rm.getLabelAnchor() : null); 718 } else { 719 throw new RuntimeException("Unrecognised marker."); 720 } 721 result.add(markerData); 722 } 723 } 724 return result; 725 } 726 727 /** 728 * Receives a {@link ChartElementVisitor}. This method is part of a general 729 * mechanism for traversing the chart structure and performing operations 730 * on each element in the chart. You will not normally call this method 731 * directly. 732 * 733 * @param visitor the visitor ({@code null} not permitted). 734 * 735 * @since 1.2 736 */ 737 @Override 738 public void receive(ChartElementVisitor visitor) { 739 for (ValueMarker marker : this.valueMarkers.values()) { 740 marker.receive(visitor); 741 } 742 visitor.visit(this); 743 } 744 745 @Override 746 public boolean equals(Object obj) { 747 if (obj == this) { 748 return true; 749 } 750 if (!(obj instanceof AbstractValueAxis3D)) { 751 return false; 752 } 753 AbstractValueAxis3D that = (AbstractValueAxis3D) obj; 754 if (!this.range.equals(that.range)) { 755 return false; 756 } 757 if (this.autoAdjustRange != that.autoAdjustRange) { 758 return false; 759 } 760 if (this.lowerMargin != that.lowerMargin) { 761 return false; 762 } 763 if (this.upperMargin != that.upperMargin) { 764 return false; 765 } 766 if (!this.defaultAutoRange.equals(that.defaultAutoRange)) { 767 return false; 768 } 769 if (this.tickLabelOffset != that.tickLabelOffset) { 770 return false; 771 } 772 if (this.tickLabelFactor != that.tickLabelFactor) { 773 return false; 774 } 775 if (!this.tickLabelOrientation.equals(that.tickLabelOrientation)) { 776 return false; 777 } 778 if (this.tickMarkLength != that.tickMarkLength) { 779 return false; 780 } 781 if (!ObjectUtils.equalsPaint(this.tickMarkPaint, that.tickMarkPaint)) { 782 return false; 783 } 784 if (!this.tickMarkStroke.equals(that.tickMarkStroke)) { 785 return false; 786 } 787 return super.equals(obj); 788 } 789 790 /** 791 * Provides serialization support. 792 * 793 * @param stream the output stream. 794 * 795 * @throws IOException if there is an I/O error. 796 */ 797 private void writeObject(ObjectOutputStream stream) throws IOException { 798 stream.defaultWriteObject(); 799 SerialUtils.writePaint(this.tickMarkPaint, stream); 800 SerialUtils.writeStroke(this.tickMarkStroke, stream); 801 } 802 803 /** 804 * Provides serialization support. 805 * 806 * @param stream the input stream. 807 * 808 * @throws IOException if there is an I/O error. 809 * @throws ClassNotFoundException if there is a classpath problem. 810 */ 811 private void readObject(ObjectInputStream stream) 812 throws IOException, ClassNotFoundException { 813 stream.defaultReadObject(); 814 this.tickMarkPaint = SerialUtils.readPaint(stream); 815 this.tickMarkStroke = SerialUtils.readStroke(stream); 816 } 817 818}