1 /* 2 * Copyright 2012 AndroidPlot.com 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.androidplot.xy; 18 19 import android.content.Context; 20 import android.graphics.Canvas; 21 import android.graphics.Color; 22 import android.graphics.Paint; 23 import android.graphics.PointF; 24 import android.util.AttributeSet; 25 import com.androidplot.Plot; 26 import com.androidplot.ui.*; 27 import com.androidplot.ui.TextOrientationType; 28 import com.androidplot.ui.widget.TextLabelWidget; 29 import com.androidplot.util.PixelUtils; 30 31 import java.text.Format; 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * A View to graphically display x/y coordinates. 37 */ 38 public class XYPlot extends Plot<XYSeries, XYSeriesFormatter, XYSeriesRenderer> { 39 40 private BoundaryMode domainOriginBoundaryMode; 41 private BoundaryMode rangeOriginBoundaryMode; 42 43 // widgets 44 private XYLegendWidget legendWidget; 45 private XYGraphWidget graphWidget; 46 private TextLabelWidget domainLabelWidget; 47 private TextLabelWidget rangeLabelWidget; 48 49 private XYStepMode domainStepMode = XYStepMode.SUBDIVIDE; 50 private double domainStepValue = 10; 51 52 private XYStepMode rangeStepMode = XYStepMode.SUBDIVIDE; 53 private double rangeStepValue = 10; 54 55 // user settable min/max values 56 private Number userMinX; 57 private Number userMaxX; 58 private Number userMinY; 59 private Number userMaxY; 60 61 // these are the final min/max used for dispplaying data 62 private Number calculatedMinX; 63 private Number calculatedMaxX; 64 private Number calculatedMinY; 65 private Number calculatedMaxY; 66 67 // previous calculated min/max vals. 68 // primarily used for GROW/SHRINK operations. 69 private Number prevMinX; 70 private Number prevMaxX; 71 private Number prevMinY; 72 private Number prevMaxY; 73 74 // uses set boundary min and max values 75 // should be null if not used. 76 private Number rangeTopMin = null; 77 private Number rangeTopMax = null; 78 private Number rangeBottomMin = null; 79 private Number rangeBottomMax = null; 80 private Number domainLeftMin = null; 81 private Number domainLeftMax = null; 82 private Number domainRightMin = null; 83 private Number domainRightMax = null; 84 85 // used for calculating the domain/range extents that will be displayed on the plot. 86 // using boundaries and origins are mutually exclusive. because of this, 87 // setting one will disable the other. when only setting the FramingModel, 88 // the origin or boundary is set to the current value of the plot. 89 private XYFramingModel domainFramingModel = XYFramingModel.EDGE; 90 private XYFramingModel rangeFramingModel = XYFramingModel.EDGE; 91 92 private Number userDomainOrigin; 93 private Number userRangeOrigin; 94 95 private Number calculatedDomainOrigin; 96 private Number calculatedRangeOrigin; 97 98 @SuppressWarnings("FieldCanBeLocal") 99 private Number domainOriginExtent = null; 100 @SuppressWarnings("FieldCanBeLocal") 101 private Number rangeOriginExtent = null; 102 103 private BoundaryMode domainUpperBoundaryMode = BoundaryMode.AUTO; 104 private BoundaryMode domainLowerBoundaryMode = BoundaryMode.AUTO; 105 private BoundaryMode rangeUpperBoundaryMode = BoundaryMode.AUTO; 106 private BoundaryMode rangeLowerBoundaryMode = BoundaryMode.AUTO; 107 108 private boolean drawDomainOriginEnabled = true; 109 private boolean drawRangeOriginEnabled = true; 110 111 private ArrayList<YValueMarker> yValueMarkers; 112 private ArrayList<XValueMarker> xValueMarkers; 113 114 private RectRegion defaultBounds; 115 116 117 private static final int DEFAULT_LEGEND_WIDGET_H_DP = 10; 118 private static final int DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP = 7; 119 private static final int DEFAULT_GRAPH_WIDGET_H_DP = 18; 120 private static final int DEFAULT_GRAPH_WIDGET_W_DP = 10; 121 private static final int DEFAULT_DOMAIN_LABEL_WIDGET_H_DP = 10; 122 private static final int DEFAULT_DOMAIN_LABEL_WIDGET_W_DP = 80; 123 private static final int DEFAULT_RANGE_LABEL_WIDGET_H_DP = 50; 124 private static final int DEFAULT_RANGE_LABEL_WIDGET_W_DP = 10; 125 126 private static final int DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP = 0; 127 private static final int DEFAULT_LEGEND_WIDGET_X_OFFSET_DP = 40; 128 private static final int DEFAULT_GRAPH_WIDGET_Y_OFFSET_DP = 0; 129 private static final int DEFAULT_GRAPH_WIDGET_X_OFFSET_DP = 0; 130 private static final int DEFAULT_DOMAIN_LABEL_WIDGET_Y_OFFSET_DP = 0; 131 private static final int DEFAULT_DOMAIN_LABEL_WIDGET_X_OFFSET_DP = 20; 132 private static final int DEFAULT_RANGE_LABEL_WIDGET_Y_OFFSET_DP = 0; 133 private static final int DEFAULT_RANGE_LABEL_WIDGET_X_OFFSET_DP = 0; 134 135 private static final int DEFAULT_GRAPH_WIDGET_TOP_MARGIN_DP = 3; 136 private static final int DEFAULT_GRAPH_WIDGET_RIGHT_MARGIN_DP = 3; 137 private static final int DEFAULT_PLOT_LEFT_MARGIN_DP = 2; 138 private static final int DEFAULT_PLOT_RIGHT_MARGIN_DP = 2; 139 private static final int DEFAULT_PLOT_BOTTOM_MARGIN_DP = 2; 140 141 public XYPlot(Context context, String title) { 142 super(context, title); 143 } 144 145 public XYPlot(Context context, String title, RenderMode mode) { 146 super(context, title, mode); 147 } 148 149 public XYPlot(Context context, AttributeSet attributes) { 150 super(context, attributes); 151 } 152 153 public XYPlot(Context context, AttributeSet attrs, int defStyle) { 154 super(context, attrs, defStyle); 155 156 } 157 158 @Override 159 protected void onPreInit() { 160 legendWidget = new XYLegendWidget( 161 getLayoutManager(), 162 this, 163 new SizeMetrics( 164 PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_H_DP), 165 SizeLayoutType.ABSOLUTE, 0.5f, SizeLayoutType.RELATIVE), 166 new DynamicTableModel(0, 1), 167 new SizeMetrics( 168 PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP), 169 SizeLayoutType.ABSOLUTE, 170 PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP), 171 SizeLayoutType.ABSOLUTE)); 172 173 graphWidget = new XYGraphWidget( 174 getLayoutManager(), 175 this, 176 new SizeMetrics( 177 PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_H_DP), 178 SizeLayoutType.FILL, 179 PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_W_DP), 180 SizeLayoutType.FILL)); 181 182 Paint backgroundPaint = new Paint(); 183 backgroundPaint.setColor(Color.DKGRAY); 184 backgroundPaint.setStyle(Paint.Style.FILL); 185 graphWidget.setBackgroundPaint(backgroundPaint); 186 187 188 domainLabelWidget = new TextLabelWidget( 189 getLayoutManager(), 190 new SizeMetrics( 191 PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_H_DP), 192 SizeLayoutType.ABSOLUTE, 193 PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_W_DP), 194 SizeLayoutType.ABSOLUTE), 195 TextOrientationType.HORIZONTAL); 196 rangeLabelWidget = new TextLabelWidget( 197 getLayoutManager(), 198 new SizeMetrics( 199 PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_H_DP), 200 SizeLayoutType.ABSOLUTE, 201 PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_W_DP), 202 SizeLayoutType.ABSOLUTE), 203 TextOrientationType.VERTICAL_ASCENDING); 204 205 legendWidget.position( 206 PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_X_OFFSET_DP), 207 XLayoutStyle.ABSOLUTE_FROM_RIGHT, 208 PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP), 209 YLayoutStyle.ABSOLUTE_FROM_BOTTOM, 210 AnchorPosition.RIGHT_BOTTOM); 211 212 graphWidget.position( 213 PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_X_OFFSET_DP), 214 XLayoutStyle.ABSOLUTE_FROM_RIGHT, 215 PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_Y_OFFSET_DP), 216 YLayoutStyle.ABSOLUTE_FROM_CENTER, 217 AnchorPosition.RIGHT_MIDDLE); 218 219 domainLabelWidget.position( 220 PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_X_OFFSET_DP), 221 XLayoutStyle.ABSOLUTE_FROM_LEFT, 222 PixelUtils.dpToPix(DEFAULT_DOMAIN_LABEL_WIDGET_Y_OFFSET_DP), 223 YLayoutStyle.ABSOLUTE_FROM_BOTTOM, 224 AnchorPosition.LEFT_BOTTOM); 225 226 rangeLabelWidget.position( 227 PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_X_OFFSET_DP), 228 XLayoutStyle.ABSOLUTE_FROM_LEFT, 229 PixelUtils.dpToPix(DEFAULT_RANGE_LABEL_WIDGET_Y_OFFSET_DP), 230 YLayoutStyle.ABSOLUTE_FROM_CENTER, 231 AnchorPosition.LEFT_MIDDLE); 232 233 getLayoutManager().moveToTop(getTitleWidget()); 234 getLayoutManager().moveToTop(getLegendWidget()); 235 graphWidget.setMarginTop(PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_TOP_MARGIN_DP)); 236 graphWidget.setMarginRight(PixelUtils.dpToPix(DEFAULT_GRAPH_WIDGET_RIGHT_MARGIN_DP)); 237 238 getDomainLabelWidget().pack(); 239 getRangeLabelWidget().pack(); 240 setPlotMarginLeft(PixelUtils.dpToPix(DEFAULT_PLOT_LEFT_MARGIN_DP)); 241 setPlotMarginRight(PixelUtils.dpToPix(DEFAULT_PLOT_RIGHT_MARGIN_DP)); 242 setPlotMarginBottom(PixelUtils.dpToPix(DEFAULT_PLOT_BOTTOM_MARGIN_DP)); 243 244 xValueMarkers = new ArrayList<XValueMarker>(); 245 yValueMarkers = new ArrayList<YValueMarker>(); 246 247 setDefaultBounds(new RectRegion(-1, 1, -1, 1)); 248 } 249 250 251 public void setGridPadding(float left, float top, float right, float bottom) { 252 getGraphWidget().setGridPaddingTop(top); 253 getGraphWidget().setGridPaddingBottom(bottom); 254 getGraphWidget().setGridPaddingLeft(left); 255 getGraphWidget().setGridPaddingRight(right); 256 } 257 258 @Override 259 protected void notifyListenersBeforeDraw(Canvas canvas) { 260 super.notifyListenersBeforeDraw(canvas); 261 262 // this call must be AFTER the notify so that if the listener 263 // is a synchronized series, it has the opportunity to 264 // place a read lock on it's data. 265 calculateMinMaxVals(); 266 } 267 268 /** 269 * Checks whether the point is within the plot's graph area. 270 * 271 * @param x 272 * @param y 273 * @return 274 */ 275 public boolean containsPoint(float x, float y) { 276 if (getGraphWidget().getGridRect() != null) { 277 return getGraphWidget().getGridRect().contains(x, y); 278 } 279 return false; 280 } 281 282 283 /** 284 * Convenience method - wraps containsPoint(PointF). 285 * 286 * @param point 287 * @return 288 */ 289 public boolean containsPoint(PointF point) { 290 return containsPoint(point.x, point.y); 291 } 292 293 public void setCursorPosition(PointF point) { 294 getGraphWidget().setCursorPosition(point); 295 } 296 297 public void setCursorPosition(float x, float y) { 298 getGraphWidget().setCursorPosition(x, y); 299 } 300 301 public Number getYVal(PointF point) { 302 return getGraphWidget().getYVal(point); 303 } 304 305 public Number getXVal(PointF point) { 306 return getGraphWidget().getXVal(point); 307 } 308 309 private boolean isXValWithinView(double xVal) { 310 return (userMinY == null || xVal >= userMinY.doubleValue()) && 311 userMaxY == null || xVal <= userMaxY.doubleValue(); 312 } 313 314 private boolean isPointVisible(Number x, Number y) { 315 // values without both an x and y val arent visible 316 if (x == null || y == null) { 317 return false; 318 } 319 return isValWithinRange(y.doubleValue(), userMinY, userMaxY) && 320 isValWithinRange(x.doubleValue(), userMinX, userMaxX); 321 } 322 323 private boolean isValWithinRange(double val, Number min, Number max) { 324 boolean isAboveMinThreshold = min == null || val >= min.doubleValue(); 325 boolean isBelowMaxThreshold = max == null || val <= max.doubleValue(); 326 return isAboveMinThreshold && 327 isBelowMaxThreshold; 328 } 329 330 public void calculateMinMaxVals() { 331 prevMinX = calculatedMinX; 332 prevMaxX = calculatedMaxX; 333 prevMinY = calculatedMinY; 334 prevMaxY = calculatedMaxY; 335 336 calculatedMinX = userMinX; 337 calculatedMaxX = userMaxX; 338 calculatedMinY = userMinY; 339 calculatedMaxY = userMaxY; 340 341 // next we go through each series to update our min/max values: 342 for (final XYSeries series : getSeriesSet()) { 343 // step through each point in each series: 344 for (int i = 0; i < series.size(); i++) { 345 Number thisX = series.getX(i); 346 Number thisY = series.getY(i); 347 if (isPointVisible(thisX, thisY)) { 348 // only calculate if a static value has not been set: 349 if (userMinX == null) { 350 if (thisX != null && (calculatedMinX == null || 351 thisX.doubleValue() < calculatedMinX.doubleValue())) { 352 calculatedMinX = thisX; 353 } 354 } 355 356 if (userMaxX == null) { 357 if (thisX != null && (calculatedMaxX == null || 358 thisX.doubleValue() > calculatedMaxX.doubleValue())) { 359 calculatedMaxX = thisX; 360 } 361 } 362 363 if (userMinY == null) { 364 if (thisY != null && (calculatedMinY == null || 365 thisY.doubleValue() < calculatedMinY.doubleValue())) { 366 calculatedMinY = thisY; 367 } 368 } 369 370 if (userMaxY == null) { 371 if (thisY != null && (calculatedMaxY == null || thisY.doubleValue() > calculatedMaxY.doubleValue())) { 372 calculatedMaxY = thisY; 373 } 374 } 375 } 376 } 377 } 378 379 // at this point we now know what points are going to be visible on our 380 // plot, but we still need to make corrections based on modes being used: 381 // (grow, shrink etc.) 382 switch (domainFramingModel) { 383 case ORIGIN: 384 updateDomainMinMaxForOriginModel(); 385 break; 386 case EDGE: 387 updateDomainMinMaxForEdgeModel(); 388 calculatedMinX = ApplyUserMinMax(calculatedMinX, domainLeftMin, 389 domainLeftMax); 390 calculatedMaxX = ApplyUserMinMax(calculatedMaxX, 391 domainRightMin, domainRightMax); 392 break; 393 default: 394 throw new UnsupportedOperationException( 395 "Domain Framing Model not yet supported: " + domainFramingModel); 396 } 397 398 switch (rangeFramingModel) { 399 case ORIGIN: 400 updateRangeMinMaxForOriginModel(); 401 break; 402 case EDGE: 403 if (getSeriesSet().size() > 0) { 404 updateRangeMinMaxForEdgeModel(); 405 calculatedMinY = ApplyUserMinMax(calculatedMinY, 406 rangeBottomMin, rangeBottomMax); 407 calculatedMaxY = ApplyUserMinMax(calculatedMaxY, rangeTopMin, 408 rangeTopMax); 409 } 410 break; 411 default: 412 throw new UnsupportedOperationException( 413 "Range Framing Model not yet supported: " + domainFramingModel); 414 } 415 416 calculatedDomainOrigin = userDomainOrigin != null ? userDomainOrigin : getCalculatedMinX(); 417 calculatedRangeOrigin = this.userRangeOrigin != null ? userRangeOrigin : getCalculatedMinY(); 418 } 419 420 /** 421 * Should ONLY be called from updateMinMax. 422 * Results are undefined otherwise. 423 */ 424 private void updateDomainMinMaxForEdgeModel() { 425 switch (domainUpperBoundaryMode) { 426 case FIXED: 427 break; 428 case AUTO: 429 break; 430 case GROW: 431 if (!(prevMaxX == null || (calculatedMaxX.doubleValue() > prevMaxX.doubleValue()))) { 432 calculatedMaxX = prevMaxX; 433 } 434 break; 435 case SHRINNK: 436 if (!(prevMaxX == null || calculatedMaxX.doubleValue() < prevMaxX.doubleValue())) { 437 calculatedMaxX = prevMaxX; 438 } 439 break; 440 default: 441 throw new UnsupportedOperationException( 442 "DomainUpperBoundaryMode not yet implemented: " + domainUpperBoundaryMode); 443 } 444 445 switch (domainLowerBoundaryMode) { 446 case FIXED: 447 break; 448 case AUTO: 449 break; 450 case GROW: 451 if (!(prevMinX == null || calculatedMinX.doubleValue() < prevMinX.doubleValue())) { 452 calculatedMinX = prevMinX; 453 } 454 break; 455 case SHRINNK: 456 if (!(prevMinX == null || calculatedMinX.doubleValue() > prevMinX.doubleValue())) { 457 calculatedMinX = prevMinX; 458 } 459 break; 460 default: 461 throw new UnsupportedOperationException( 462 "DomainLowerBoundaryMode not supported: " + domainLowerBoundaryMode); 463 } 464 } 465 466 public void updateRangeMinMaxForEdgeModel() { 467 switch (rangeUpperBoundaryMode) { 468 case FIXED: 469 break; 470 case AUTO: 471 break; 472 case GROW: 473 if (!(prevMaxY == null || calculatedMaxY.doubleValue() > prevMaxY.doubleValue())) { 474 calculatedMaxY = prevMaxY; 475 } 476 break; 477 case SHRINNK: 478 if (!(prevMaxY == null || calculatedMaxY.doubleValue() < prevMaxY.doubleValue())) { 479 calculatedMaxY = prevMaxY; 480 } 481 break; 482 default: 483 throw new UnsupportedOperationException( 484 "RangeUpperBoundaryMode not supported: " + rangeUpperBoundaryMode); 485 } 486 487 switch (rangeLowerBoundaryMode) { 488 case FIXED: 489 break; 490 case AUTO: 491 break; 492 case GROW: 493 if (!(prevMinY == null || calculatedMinY.doubleValue() < prevMinY.doubleValue())) { 494 calculatedMinY = prevMinY; 495 } 496 break; 497 case SHRINNK: 498 if (!(prevMinY == null || calculatedMinY.doubleValue() > prevMinY.doubleValue())) { 499 calculatedMinY = prevMinY; 500 } 501 break; 502 default: 503 throw new UnsupportedOperationException( 504 "RangeLowerBoundaryMode not supported: " + rangeLowerBoundaryMode); 505 } 506 } 507 508 /** 509 * Apply user supplied min and max to the calculated boundary value. 510 * 511 * @param value 512 * @param min 513 * @param max 514 */ 515 private Number ApplyUserMinMax(Number value, Number min, Number max) { 516 value = (((min == null) || (value.doubleValue() > min.doubleValue())) 517 ? value 518 : min); 519 value = (((max == null) || (value.doubleValue() < max.doubleValue())) 520 ? value 521 : max); 522 return value; 523 } 524 525 /** 526 * Centers the domain axis on origin. 527 * 528 * @param origin 529 */ 530 public void centerOnDomainOrigin(Number origin) { 531 centerOnDomainOrigin(origin, null, BoundaryMode.AUTO); 532 } 533 534 /** 535 * Centers the domain on origin, calculating the upper and lower boundaries of the axis 536 * using mode and extent. 537 * 538 * @param origin 539 * @param extent 540 * @param mode 541 */ 542 public void centerOnDomainOrigin(Number origin, Number extent, BoundaryMode mode) { 543 if (origin == null) { 544 throw new NullPointerException("Origin param cannot be null."); 545 } 546 domainFramingModel = XYFramingModel.ORIGIN; 547 setUserDomainOrigin(origin); 548 domainOriginExtent = extent; 549 domainOriginBoundaryMode = mode; 550 551 if (domainOriginBoundaryMode == BoundaryMode.FIXED) { 552 double domO = userDomainOrigin.doubleValue(); 553 double domE = domainOriginExtent.doubleValue(); 554 userMaxX = domO + domE; 555 userMinX = domO - domE; 556 } else { 557 userMaxX = null; 558 userMinX = null; 559 } 560 } 561 562 /** 563 * Centers the range axis on origin. 564 * 565 * @param origin 566 */ 567 public void centerOnRangeOrigin(Number origin) { 568 centerOnRangeOrigin(origin, null, BoundaryMode.AUTO); 569 } 570 571 /** 572 * Centers the domain on origin, calculating the upper and lower boundaries of the axis 573 * using mode and extent. 574 * 575 * @param origin 576 * @param extent 577 * @param mode 578 */ 579 @SuppressWarnings("SameParameterValue") 580 public void centerOnRangeOrigin(Number origin, Number extent, BoundaryMode mode) { 581 if (origin == null) { 582 throw new NullPointerException("Origin param cannot be null."); 583 } 584 rangeFramingModel = XYFramingModel.ORIGIN; 585 setUserRangeOrigin(origin); 586 rangeOriginExtent = extent; 587 rangeOriginBoundaryMode = mode; 588 589 if (rangeOriginBoundaryMode == BoundaryMode.FIXED) { 590 double raO = userRangeOrigin.doubleValue(); 591 double raE = rangeOriginExtent.doubleValue(); 592 userMaxY = raO + raE; 593 userMinY = raO - raE; 594 } else { 595 userMaxY = null; 596 userMinY = null; 597 } 598 } 599 600 /** 601 * Returns the distance between x and y. 602 * Result is never a negative number. 603 * 604 * @param x 605 * @param y 606 * @return 607 */ 608 private double distance(double x, double y) { 609 if (x > y) { 610 return x - y; 611 } else { 612 return y - x; 613 } 614 } 615 616 public void updateDomainMinMaxForOriginModel() { 617 double origin = userDomainOrigin.doubleValue(); 618 double maxXDelta = distance(calculatedMaxX.doubleValue(), origin); 619 double minXDelta = distance(calculatedMinX.doubleValue(), origin); 620 double delta = maxXDelta > minXDelta ? maxXDelta : minXDelta; 621 double dlb = origin - delta; 622 double dub = origin + delta; 623 switch (domainOriginBoundaryMode) { 624 case AUTO: 625 calculatedMinX = dlb; 626 calculatedMaxX = dub; 627 628 break; 629 // if fixed, then the value already exists within "user" vals. 630 case FIXED: 631 break; 632 case GROW: { 633 634 if (prevMinX == null || dlb < prevMinX.doubleValue()) { 635 calculatedMinX = dlb; 636 } else { 637 calculatedMinX = prevMinX; 638 } 639 640 if (prevMaxX == null || dub > prevMaxX.doubleValue()) { 641 calculatedMaxX = dub; 642 } else { 643 calculatedMaxX = prevMaxX; 644 } 645 } 646 break; 647 case SHRINNK: 648 if (prevMinX == null || dlb > prevMinX.doubleValue()) { 649 calculatedMinX = dlb; 650 } else { 651 calculatedMinX = prevMinX; 652 } 653 654 if (prevMaxX == null || dub < prevMaxX.doubleValue()) { 655 calculatedMaxX = dub; 656 } else { 657 calculatedMaxX = prevMaxX; 658 } 659 break; 660 default: 661 throw new UnsupportedOperationException("Domain Origin Boundary Mode not yet supported: " + domainOriginBoundaryMode); 662 } 663 } 664 665 public void updateRangeMinMaxForOriginModel() { 666 switch (rangeOriginBoundaryMode) { 667 case AUTO: 668 double origin = userRangeOrigin.doubleValue(); 669 double maxYDelta = distance(calculatedMaxY.doubleValue(), origin); 670 double minYDelta = distance(calculatedMinY.doubleValue(), origin); 671 if (maxYDelta > minYDelta) { 672 calculatedMinY = origin - maxYDelta; 673 calculatedMaxY = origin + maxYDelta; 674 } else { 675 calculatedMinY = origin - minYDelta; 676 calculatedMaxY = origin + minYDelta; 677 } 678 break; 679 case FIXED: 680 case GROW: 681 case SHRINNK: 682 default: 683 throw new UnsupportedOperationException( 684 "Range Origin Boundary Mode not yet supported: " + rangeOriginBoundaryMode); 685 } 686 } 687 688 /** 689 * Convenience method - wraps XYGraphWidget.getTicksPerRangeLabel(). 690 * Equivalent to getGraphWidget().getTicksPerRangeLabel(). 691 * 692 * @return 693 */ 694 public int getTicksPerRangeLabel() { 695 return graphWidget.getTicksPerRangeLabel(); 696 } 697 698 /** 699 * Convenience method - wraps XYGraphWidget.setTicksPerRangeLabel(). 700 * Equivalent to getGraphWidget().setTicksPerRangeLabel(). 701 * 702 * @param ticksPerRangeLabel 703 */ 704 public void setTicksPerRangeLabel(int ticksPerRangeLabel) { 705 graphWidget.setTicksPerRangeLabel(ticksPerRangeLabel); 706 } 707 708 /** 709 * Convenience method - wraps XYGraphWidget.getTicksPerDomainLabel(). 710 * Equivalent to getGraphWidget().getTicksPerDomainLabel(). 711 * 712 * @return 713 */ 714 public int getTicksPerDomainLabel() { 715 return graphWidget.getTicksPerDomainLabel(); 716 } 717 718 /** 719 * Convenience method - wraps XYGraphWidget.setTicksPerDomainLabel(). 720 * Equivalent to getGraphWidget().setTicksPerDomainLabel(). 721 * 722 * @param ticksPerDomainLabel 723 */ 724 public void setTicksPerDomainLabel(int ticksPerDomainLabel) { 725 graphWidget.setTicksPerDomainLabel(ticksPerDomainLabel); 726 } 727 728 public XYStepMode getDomainStepMode() { 729 return domainStepMode; 730 } 731 732 public void setDomainStepMode(XYStepMode domainStepMode) { 733 this.domainStepMode = domainStepMode; 734 } 735 736 public double getDomainStepValue() { 737 return domainStepValue; 738 } 739 740 public void setDomainStepValue(double domainStepValue) { 741 this.domainStepValue = domainStepValue; 742 } 743 744 public void setDomainStep(XYStepMode mode, double value) { 745 setDomainStepMode(mode); 746 setDomainStepValue(value); 747 } 748 749 public XYStepMode getRangeStepMode() { 750 return rangeStepMode; 751 } 752 753 public void setRangeStepMode(XYStepMode rangeStepMode) { 754 this.rangeStepMode = rangeStepMode; 755 } 756 757 public double getRangeStepValue() { 758 return rangeStepValue; 759 } 760 761 public void setRangeStepValue(double rangeStepValue) { 762 this.rangeStepValue = rangeStepValue; 763 } 764 765 public void setRangeStep(XYStepMode mode, double value) { 766 setRangeStepMode(mode); 767 setRangeStepValue(value); 768 } 769 770 public String getDomainLabel() { 771 return getDomainLabelWidget().getText(); 772 } 773 774 public void setDomainLabel(String domainLabel) { 775 getDomainLabelWidget().setText(domainLabel); 776 } 777 778 public String getRangeLabel() { 779 return getRangeLabelWidget().getText(); 780 } 781 782 public void setRangeLabel(String rangeLabel) { 783 getRangeLabelWidget().setText(rangeLabel); 784 } 785 786 public XYLegendWidget getLegendWidget() { 787 return legendWidget; 788 } 789 790 public void setLegendWidget(XYLegendWidget legendWidget) { 791 this.legendWidget = legendWidget; 792 } 793 794 public XYGraphWidget getGraphWidget() { 795 return graphWidget; 796 } 797 798 public void setGraphWidget(XYGraphWidget graphWidget) { 799 this.graphWidget = graphWidget; 800 } 801 802 public TextLabelWidget getDomainLabelWidget() { 803 return domainLabelWidget; 804 } 805 806 public void setDomainLabelWidget(TextLabelWidget domainLabelWidget) { 807 this.domainLabelWidget = domainLabelWidget; 808 } 809 810 public TextLabelWidget getRangeLabelWidget() { 811 return rangeLabelWidget; 812 } 813 814 public void setRangeLabelWidget(TextLabelWidget rangeLabelWidget) { 815 this.rangeLabelWidget = rangeLabelWidget; 816 } 817 818 /** 819 * Convenience method - wraps XYGraphWidget.getRangeValueFormat(). 820 * 821 * @return 822 */ 823 public Format getRangeValueFormat() { 824 return graphWidget.getRangeValueFormat(); 825 } 826 827 /** 828 * Convenience method - wraps XYGraphWidget.setRangeValueFormat(). 829 * 830 * @param rangeValueFormat 831 */ 832 public void setRangeValueFormat(Format rangeValueFormat) { 833 graphWidget.setRangeValueFormat(rangeValueFormat); 834 } 835 836 /** 837 * Convenience method - wraps XYGraphWidget.getDomainValueFormat(). 838 * 839 * @return 840 */ 841 public Format getDomainValueFormat() { 842 return graphWidget.getDomainValueFormat(); 843 } 844 845 /** 846 * Convenience method - wraps XYGraphWidget.setDomainValueFormat(). 847 * 848 * @param domainValueFormat 849 */ 850 public void setDomainValueFormat(Format domainValueFormat) { 851 graphWidget.setDomainValueFormat(domainValueFormat); 852 } 853 854 /** 855 * Setup the boundary mode, boundary values only applicable in FIXED mode. 856 * 857 * @param lowerBoundary 858 * @param upperBoundary 859 * @param mode 860 */ 861 public synchronized void setDomainBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) { 862 setDomainBoundaries(lowerBoundary, mode, upperBoundary, mode); 863 } 864 865 /** 866 * Setup the boundary mode, boundary values only applicable in FIXED mode. 867 * 868 * @param lowerBoundary 869 * @param lowerBoundaryMode 870 * @param upperBoundary 871 * @param upperBoundaryMode 872 */ 873 public synchronized void setDomainBoundaries(Number lowerBoundary, BoundaryMode lowerBoundaryMode, 874 Number upperBoundary, BoundaryMode upperBoundaryMode) { 875 setDomainLowerBoundary(lowerBoundary, lowerBoundaryMode); 876 setDomainUpperBoundary(upperBoundary, upperBoundaryMode); 877 } 878 879 /** 880 * Setup the boundary mode, boundary values only applicable in FIXED mode. 881 * 882 * @param lowerBoundary 883 * @param upperBoundary 884 * @param mode 885 */ 886 public synchronized void setRangeBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) { 887 setRangeBoundaries(lowerBoundary, mode, upperBoundary, mode); 888 } 889 890 /** 891 * Setup the boundary mode, boundary values only applicable in FIXED mode. 892 * 893 * @param lowerBoundary 894 * @param lowerBoundaryMode 895 * @param upperBoundary 896 * @param upperBoundaryMode 897 */ 898 public synchronized void setRangeBoundaries(Number lowerBoundary, BoundaryMode lowerBoundaryMode, 899 Number upperBoundary, BoundaryMode upperBoundaryMode) { 900 setRangeLowerBoundary(lowerBoundary, lowerBoundaryMode); 901 setRangeUpperBoundary(upperBoundary, upperBoundaryMode); 902 } 903 904 protected synchronized void setDomainUpperBoundaryMode(BoundaryMode mode) { 905 this.domainUpperBoundaryMode = mode; 906 } 907 908 protected synchronized void setUserMaxX(Number boundary) { 909 // Ifor 12/10/2011 910 // you want null for auto grow and shrink 911 //if(boundary == null) { 912 // throw new NullPointerException("Boundary value cannot be null."); 913 //} 914 this.userMaxX = boundary; 915 } 916 917 /** 918 * Setup the boundary mode, boundary values only applicable in FIXED mode. 919 * 920 * @param boundary 921 * @param mode 922 */ 923 public synchronized void setDomainUpperBoundary(Number boundary, BoundaryMode mode) { 924 setUserMaxX((mode == BoundaryMode.FIXED) ? boundary : null); 925 setDomainUpperBoundaryMode(mode); 926 setDomainFramingModel(XYFramingModel.EDGE); 927 } 928 929 protected synchronized void setDomainLowerBoundaryMode(BoundaryMode mode) { 930 this.domainLowerBoundaryMode = mode; 931 } 932 933 protected synchronized void setUserMinX(Number boundary) { 934 // Ifor 12/10/2011 935 // you want null for auto grow and shrink 936 //if(boundary == null) { 937 // throw new NullPointerException("Boundary value cannot be null."); 938 //} 939 this.userMinX = boundary; 940 } 941 942 /** 943 * Setup the boundary mode, boundary values only applicable in FIXED mode. 944 * 945 * @param boundary 946 * @param mode 947 */ 948 public synchronized void setDomainLowerBoundary(Number boundary, BoundaryMode mode) { 949 setUserMinX((mode == BoundaryMode.FIXED) ? boundary : null); 950 setDomainLowerBoundaryMode(mode); 951 setDomainFramingModel(XYFramingModel.EDGE); 952 //updateMinMaxVals(); 953 } 954 955 protected synchronized void setRangeUpperBoundaryMode(BoundaryMode mode) { 956 this.rangeUpperBoundaryMode = mode; 957 } 958 959 protected synchronized void setUserMaxY(Number boundary) { 960 // Ifor 12/10/2011 961 // you want null for auto grow and shrink 962 //if(boundary == null) { 963 // throw new NullPointerException("Boundary value cannot be null."); 964 //} 965 this.userMaxY = boundary; 966 } 967 968 /** 969 * Setup the boundary mode, boundary values only applicable in FIXED mode. 970 * 971 * @param boundary 972 * @param mode 973 */ 974 public synchronized void setRangeUpperBoundary(Number boundary, BoundaryMode mode) { 975 setUserMaxY((mode == BoundaryMode.FIXED) ? boundary : null); 976 setRangeUpperBoundaryMode(mode); 977 setRangeFramingModel(XYFramingModel.EDGE); 978 } 979 980 protected synchronized void setRangeLowerBoundaryMode(BoundaryMode mode) { 981 this.rangeLowerBoundaryMode = mode; 982 } 983 984 protected synchronized void setUserMinY(Number boundary) { 985 // Ifor 12/10/2011 986 // you want null for auto grow and shrink 987 //if(boundary == null) { 988 // throw new NullPointerException("Boundary value cannot be null."); 989 //} 990 this.userMinY = boundary; 991 } 992 993 /** 994 * Setup the boundary mode, boundary values only applicable in FIXED mode. 995 * 996 * @param boundary 997 * @param mode 998 */ 999 public synchronized void setRangeLowerBoundary(Number boundary, BoundaryMode mode) { 1000 setUserMinY((mode == BoundaryMode.FIXED) ? boundary : null); 1001 setRangeLowerBoundaryMode(mode); 1002 setRangeFramingModel(XYFramingModel.EDGE); 1003 } 1004 1005 private Number getUserMinX() { 1006 return userMinX; 1007 } 1008 1009 private Number getUserMaxX() { 1010 return userMaxX; 1011 } 1012 1013 private Number getUserMinY() { 1014 return userMinY; 1015 } 1016 1017 private Number getUserMaxY() { 1018 return userMaxY; 1019 } 1020 1021 public Number getDomainOrigin() { 1022 return calculatedDomainOrigin; 1023 } 1024 1025 public Number getRangeOrigin() { 1026 return calculatedRangeOrigin; 1027 } 1028 1029 protected BoundaryMode getDomainUpperBoundaryMode() { 1030 return domainUpperBoundaryMode; 1031 } 1032 1033 protected BoundaryMode getDomainLowerBoundaryMode() { 1034 return domainLowerBoundaryMode; 1035 } 1036 1037 protected BoundaryMode getRangeUpperBoundaryMode() { 1038 return rangeUpperBoundaryMode; 1039 } 1040 1041 protected BoundaryMode getRangeLowerBoundaryMode() { 1042 return rangeLowerBoundaryMode; 1043 } 1044 1045 public synchronized void setUserDomainOrigin(Number origin) { 1046 if (origin == null) { 1047 throw new NullPointerException("Origin value cannot be null."); 1048 } 1049 this.userDomainOrigin = origin; 1050 } 1051 1052 public synchronized void setUserRangeOrigin(Number origin) { 1053 if (origin == null) { 1054 throw new NullPointerException("Origin value cannot be null."); 1055 } 1056 this.userRangeOrigin = origin; 1057 } 1058 1059 public XYFramingModel getDomainFramingModel() { 1060 return domainFramingModel; 1061 } 1062 1063 @SuppressWarnings("SameParameterValue") 1064 protected void setDomainFramingModel(XYFramingModel domainFramingModel) { 1065 this.domainFramingModel = domainFramingModel; 1066 } 1067 1068 public XYFramingModel getRangeFramingModel() { 1069 1070 return rangeFramingModel; 1071 } 1072 1073 @SuppressWarnings("SameParameterValue") 1074 protected void setRangeFramingModel(XYFramingModel rangeFramingModel) { 1075 this.rangeFramingModel = rangeFramingModel; 1076 } 1077 1078 /** 1079 * CalculatedMinX value after the the framing model has been applied. 1080 * 1081 * @return 1082 */ 1083 public Number getCalculatedMinX() { 1084 return calculatedMinX != null ? calculatedMinX : getDefaultBounds().getMinX(); 1085 } 1086 1087 /** 1088 * CalculatedMaxX value after the the framing model has been applied. 1089 * 1090 * @return 1091 */ 1092 public Number getCalculatedMaxX() { 1093 return calculatedMaxX != null ? calculatedMaxX : getDefaultBounds().getMaxX(); 1094 } 1095 1096 /** 1097 * CalculatedMinY value after the the framing model has been applied. 1098 * 1099 * @return 1100 */ 1101 public Number getCalculatedMinY() { 1102 return calculatedMinY != null ? calculatedMinY : getDefaultBounds().getMinY(); 1103 } 1104 1105 /** 1106 * CalculatedMaxY value after the the framing model has been applied. 1107 * 1108 * @return 1109 */ 1110 public Number getCalculatedMaxY() { 1111 return calculatedMaxY != null ? calculatedMaxY : getDefaultBounds().getMaxY(); 1112 } 1113 1114 public boolean isDrawDomainOriginEnabled() { 1115 return drawDomainOriginEnabled; 1116 } 1117 1118 public void setDrawDomainOriginEnabled(boolean drawDomainOriginEnabled) { 1119 this.drawDomainOriginEnabled = drawDomainOriginEnabled; 1120 } 1121 1122 public boolean isDrawRangeOriginEnabled() { 1123 return drawRangeOriginEnabled; 1124 } 1125 1126 public void setDrawRangeOriginEnabled(boolean drawRangeOriginEnabled) { 1127 this.drawRangeOriginEnabled = drawRangeOriginEnabled; 1128 } 1129 1130 /** 1131 * Appends the specified marker to the end of plot's yValueMarkers list. 1132 * 1133 * @param marker The YValueMarker to be added. 1134 * @return true if the object was successfully added, false otherwise. 1135 */ 1136 public boolean addMarker(YValueMarker marker) { 1137 if (yValueMarkers.contains(marker)) { 1138 return false; 1139 } else { 1140 return yValueMarkers.add(marker); 1141 } 1142 } 1143 1144 /** 1145 * Removes the specified marker from the plot. 1146 * 1147 * @param marker 1148 * @return The YValueMarker removed if successfull, null otherwise. 1149 */ 1150 public YValueMarker removeMarker(YValueMarker marker) { 1151 int markerIndex = yValueMarkers.indexOf(marker); 1152 if (markerIndex == -1) { 1153 return null; 1154 } else { 1155 return yValueMarkers.remove(markerIndex); 1156 } 1157 } 1158 1159 /** 1160 * Convenience method - combines removeYMarkers() and removeXMarkers(). 1161 * 1162 * @return 1163 */ 1164 public int removeMarkers() { 1165 int removed = removeXMarkers(); 1166 removed += removeYMarkers(); 1167 return removed; 1168 } 1169 1170 /** 1171 * Removes all YValueMarker instances from the plot. 1172 * 1173 * @return 1174 */ 1175 public int removeYMarkers() { 1176 int numMarkersRemoved = yValueMarkers.size(); 1177 yValueMarkers.clear(); 1178 return numMarkersRemoved; 1179 } 1180 1181 /** 1182 * Appends the specified marker to the end of plot's xValueMarkers list. 1183 * 1184 * @param marker The XValueMarker to be added. 1185 * @return true if the object was successfully added, false otherwise. 1186 */ 1187 public boolean addMarker(XValueMarker marker) { 1188 return !xValueMarkers.contains(marker) && xValueMarkers.add(marker); 1189 } 1190 1191 /** 1192 * Removes the specified marker from the plot. 1193 * 1194 * @param marker 1195 * @return The XValueMarker removed if successfull, null otherwise. 1196 */ 1197 public XValueMarker removeMarker(XValueMarker marker) { 1198 int markerIndex = xValueMarkers.indexOf(marker); 1199 if (markerIndex == -1) { 1200 return null; 1201 } else { 1202 return xValueMarkers.remove(markerIndex); 1203 } 1204 } 1205 1206 /** 1207 * Removes all XValueMarker instances from the plot. 1208 * 1209 * @return 1210 */ 1211 public int removeXMarkers() { 1212 int numMarkersRemoved = xValueMarkers.size(); 1213 xValueMarkers.clear(); 1214 return numMarkersRemoved; 1215 } 1216 1217 protected List<YValueMarker> getYValueMarkers() { 1218 return yValueMarkers; 1219 } 1220 1221 protected List<XValueMarker> getXValueMarkers() { 1222 return xValueMarkers; 1223 } 1224 1225 public RectRegion getDefaultBounds() { 1226 return defaultBounds; 1227 } 1228 1229 public void setDefaultBounds(RectRegion defaultBounds) { 1230 this.defaultBounds = defaultBounds; 1231 } 1232 1233 /** 1234 * @return the rangeTopMin 1235 */ 1236 public Number getRangeTopMin() { 1237 return rangeTopMin; 1238 } 1239 1240 /** 1241 * @param rangeTopMin the rangeTopMin to set 1242 */ 1243 public synchronized void setRangeTopMin(Number rangeTopMin) { 1244 this.rangeTopMin = rangeTopMin; 1245 } 1246 1247 /** 1248 * @return the rangeTopMax 1249 */ 1250 public Number getRangeTopMax() { 1251 return rangeTopMax; 1252 } 1253 1254 /** 1255 * @param rangeTopMax the rangeTopMax to set 1256 */ 1257 public synchronized void setRangeTopMax(Number rangeTopMax) { 1258 this.rangeTopMax = rangeTopMax; 1259 } 1260 1261 /** 1262 * @return the rangeBottomMin 1263 */ 1264 public Number getRangeBottomMin() { 1265 return rangeBottomMin; 1266 } 1267 1268 /** 1269 * @param rangeBottomMin the rangeBottomMin to set 1270 */ 1271 public synchronized void setRangeBottomMin(Number rangeBottomMin) { 1272 this.rangeBottomMin = rangeBottomMin; 1273 } 1274 1275 /** 1276 * @return the rangeBottomMax 1277 */ 1278 public Number getRangeBottomMax() { 1279 return rangeBottomMax; 1280 } 1281 1282 /** 1283 * @param rangeBottomMax the rangeBottomMax to set 1284 */ 1285 public synchronized void setRangeBottomMax(Number rangeBottomMax) { 1286 this.rangeBottomMax = rangeBottomMax; 1287 } 1288 1289 /** 1290 * @return the domainLeftMin 1291 */ 1292 public Number getDomainLeftMin() { 1293 return domainLeftMin; 1294 } 1295 1296 /** 1297 * @param domainLeftMin the domainLeftMin to set 1298 */ 1299 public synchronized void setDomainLeftMin(Number domainLeftMin) { 1300 this.domainLeftMin = domainLeftMin; 1301 } 1302 1303 /** 1304 * @return the domainLeftMax 1305 */ 1306 public Number getDomainLeftMax() { 1307 return domainLeftMax; 1308 } 1309 1310 /** 1311 * @param domainLeftMax the domainLeftMax to set 1312 */ 1313 public synchronized void setDomainLeftMax(Number domainLeftMax) { 1314 this.domainLeftMax = domainLeftMax; 1315 } 1316 1317 /** 1318 * @return the domainRightMin 1319 */ 1320 public Number getDomainRightMin() { 1321 return domainRightMin; 1322 } 1323 1324 /** 1325 * @param domainRightMin the domainRightMin to set 1326 */ 1327 public synchronized void setDomainRightMin(Number domainRightMin) { 1328 this.domainRightMin = domainRightMin; 1329 } 1330 1331 /** 1332 * @return the domainRightMax 1333 */ 1334 public Number getDomainRightMax() { 1335 return domainRightMax; 1336 } 1337 1338 /** 1339 * @param domainRightMax the domainRightMax to set 1340 */ 1341 public synchronized void setDomainRightMax(Number domainRightMax) { 1342 this.domainRightMax = domainRightMax; 1343 } 1344 }