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.graphics.*; 20 21 import com.androidplot.exception.PlotRenderException; 22 import com.androidplot.ui.LayoutManager; 23 import com.androidplot.ui.SizeMetrics; 24 import com.androidplot.ui.widget.Widget; 25 import com.androidplot.util.FontUtils; 26 import com.androidplot.util.ValPixConverter; 27 import com.androidplot.util.ZHash; 28 import com.androidplot.util.ZIndexable; 29 30 import java.text.DecimalFormat; 31 import java.text.Format; 32 33 /** 34 * Displays graphical data annotated with domain and range tick markers. 35 */ 36 public class XYGraphWidget extends Widget { 37 38 public float getRangeLabelOrientation() { 39 return rangeLabelOrientation; 40 } 41 42 public void setRangeLabelOrientation(float rangeLabelOrientation) { 43 this.rangeLabelOrientation = rangeLabelOrientation; 44 } 45 46 public float getDomainLabelOrientation() { 47 return domainLabelOrientation; 48 } 49 50 public void setDomainLabelOrientation(float domainLabelOrientation) { 51 this.domainLabelOrientation = domainLabelOrientation; 52 } 53 54 /** 55 * Will be used in a future version. 56 */ 57 public enum XYPlotOrientation { 58 HORIZONTAL, VERTICAL 59 } 60 61 private static final int MARKER_LABEL_SPACING = 2; 62 private static final int CURSOR_LABEL_SPACING = 2; // space between cursor 63 private static final String TAG = "AndroidPlot"; 64 // lines and label in 65 // pixels 66 private float domainLabelWidth = 15; // how many pixels is the area 67 // allocated for domain labels 68 private float rangeLabelWidth = 41; // ... 69 private float domainLabelVerticalOffset = -5; 70 private float domainLabelHorizontalOffset = 0.0f; 71 private float rangeLabelHorizontalOffset = 1.0f; // allows tweaking of text position 72 private float rangeLabelVerticalOffset = 0.0f; // allows tweaking of text position 73 74 private int ticksPerRangeLabel = 1; 75 private int ticksPerDomainLabel = 1; 76 private float gridPaddingTop = 0; 77 private float gridPaddingBottom = 0; 78 private float gridPaddingLeft = 0; 79 private float gridPaddingRight = 0; 80 private int domainLabelTickExtension = 5; 81 private int rangeLabelTickExtension = 5; 82 private Paint gridBackgroundPaint; 83 private Paint rangeGridLinePaint; 84 private Paint rangeSubGridLinePaint; 85 private Paint domainGridLinePaint; 86 private Paint domainSubGridLinePaint; 87 private Paint domainLabelPaint; 88 private Paint rangeLabelPaint; 89 private Paint domainCursorPaint; 90 private Paint rangeCursorPaint; 91 private Paint cursorLabelPaint; 92 private Paint cursorLabelBackgroundPaint; 93 private XYPlot plot; 94 private Format rangeValueFormat; 95 private Format domainValueFormat; 96 private Paint domainOriginLinePaint; 97 private Paint rangeOriginLinePaint; 98 private Paint domainOriginLabelPaint; 99 private Paint rangeOriginLabelPaint; 100 private RectF gridRect; 101 private RectF paddedGridRect; 102 private float domainCursorPosition; 103 private float rangeCursorPosition; 104 @SuppressWarnings("FieldCanBeLocal") 105 private boolean drawCursorLabelEnabled = true; 106 private boolean drawMarkersEnabled = true; 107 108 private boolean rangeAxisLeft = true; 109 private boolean domainAxisBottom = true; 110 111 private float rangeLabelOrientation; 112 private float domainLabelOrientation; 113 114 // TODO: consider typing this manager with a special 115 // axisLabelRegionFormatter 116 // private ZHash<LineRegion, AxisValueLabelFormatter> domainLabelRegions; 117 // private ZHash<LineRegion, AxisValueLabelFormatter> rangeLabelRegions; 118 private ZHash<RectRegion, AxisValueLabelFormatter> axisValueLabelRegions; 119 120 { 121 gridBackgroundPaint = new Paint(); 122 gridBackgroundPaint.setColor(Color.rgb(140, 140, 140)); 123 gridBackgroundPaint.setStyle(Paint.Style.FILL); 124 rangeGridLinePaint = new Paint(); 125 rangeGridLinePaint.setColor(Color.rgb(180, 180, 180)); 126 rangeGridLinePaint.setAntiAlias(true); 127 rangeGridLinePaint.setStyle(Paint.Style.STROKE); 128 domainGridLinePaint = new Paint(rangeGridLinePaint); 129 domainSubGridLinePaint = new Paint(domainGridLinePaint); 130 rangeSubGridLinePaint = new Paint(rangeGridLinePaint); 131 domainOriginLinePaint = new Paint(); 132 domainOriginLinePaint.setColor(Color.WHITE); 133 domainOriginLinePaint.setAntiAlias(true); 134 rangeOriginLinePaint = new Paint(); 135 rangeOriginLinePaint.setColor(Color.WHITE); 136 rangeOriginLinePaint.setAntiAlias(true); 137 domainOriginLabelPaint = new Paint(); 138 domainOriginLabelPaint.setColor(Color.WHITE); 139 domainOriginLabelPaint.setAntiAlias(true); 140 domainOriginLabelPaint.setTextAlign(Paint.Align.CENTER); 141 rangeOriginLabelPaint = new Paint(); 142 rangeOriginLabelPaint.setColor(Color.WHITE); 143 rangeOriginLabelPaint.setAntiAlias(true); 144 rangeOriginLabelPaint.setTextAlign(Paint.Align.RIGHT); 145 domainLabelPaint = new Paint(); 146 domainLabelPaint.setColor(Color.LTGRAY); 147 domainLabelPaint.setAntiAlias(true); 148 domainLabelPaint.setTextAlign(Paint.Align.CENTER); 149 rangeLabelPaint = new Paint(); 150 rangeLabelPaint.setColor(Color.LTGRAY); 151 rangeLabelPaint.setAntiAlias(true); 152 rangeLabelPaint.setTextAlign(Paint.Align.RIGHT); 153 domainCursorPaint = new Paint(); 154 domainCursorPaint.setColor(Color.YELLOW); 155 rangeCursorPaint = new Paint(); 156 rangeCursorPaint.setColor(Color.YELLOW); 157 cursorLabelPaint = new Paint(); 158 cursorLabelPaint.setColor(Color.YELLOW); 159 cursorLabelBackgroundPaint = new Paint(); 160 cursorLabelBackgroundPaint.setColor(Color.argb(100, 50, 50, 50)); 161 setMarginTop(7); 162 setMarginRight(4); 163 setMarginBottom(4); 164 rangeValueFormat = new DecimalFormat("0.0"); 165 domainValueFormat = new DecimalFormat("0.0"); 166 // domainLabelRegions = new ZHash<LineRegion, 167 // AxisValueLabelFormatter>(); 168 // rangeLabelRegions = new ZHash<LineRegion, AxisValueLabelFormatter>(); 169 axisValueLabelRegions = new ZHash<RectRegion, AxisValueLabelFormatter>(); 170 } 171 172 public XYGraphWidget(LayoutManager layoutManager, XYPlot plot, SizeMetrics sizeMetrics) { 173 super(layoutManager, sizeMetrics); 174 this.plot = plot; 175 } 176 177 public ZIndexable<RectRegion> getAxisValueLabelRegions() { 178 return axisValueLabelRegions; 179 } 180 181 /** 182 * Add a new Region used for rendering axis valuelabels. Note that it is 183 * possible to add multiple Region instances which overlap, in which cast 184 * the last region to be added will be used. It is up to the developer to 185 * guard against this often undesireable situation. 186 * 187 * @param region 188 * @param formatter 189 */ 190 public void addAxisValueLabelRegion(RectRegion region, 191 AxisValueLabelFormatter formatter) { 192 axisValueLabelRegions.addToTop(region, formatter); 193 } 194 195 /** 196 * Convenience method - wraps addAxisValueLabelRegion, using 197 * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off range 198 * axis value labels. 199 * 200 * @param min 201 * @param max 202 * @param formatter 203 * 204 */ 205 public void addDomainAxisValueLabelRegion(double min, double max, 206 AxisValueLabelFormatter formatter) { 207 addAxisValueLabelRegion(new RectRegion(min, max, 208 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, null), 209 formatter); 210 } 211 212 /** 213 * Convenience method - wraps addAxisValueLabelRegion, using 214 * Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY to mask off domain 215 * axis value labels. 216 * 217 * @param min 218 * @param max 219 * @param formatter 220 */ 221 public void addRangeAxisValueLabelRegion(double min, double max, 222 AxisValueLabelFormatter formatter) { 223 addAxisValueLabelRegion(new RectRegion(Double.POSITIVE_INFINITY, 224 Double.NEGATIVE_INFINITY, min, max, null), formatter); 225 } 226 227 /* 228 * public void addRangeLabelRegion(LineRegion region, 229 * AxisValueLabelFormatter formatter) { rangeLabelRegions.addToTop(region, 230 * formatter); } 231 * 232 * public boolean removeRangeLabelRegion(LineRegion region) { return 233 * rangeLabelRegions.remove(region); } 234 */ 235 236 /** 237 * Returns the formatter associated with the first (bottom) Region 238 * containing x and y. 239 * 240 * @param x 241 * @param y 242 * @return the formatter associated with the first (bottom) region 243 * containing x and y. null otherwise. 244 */ 245 public AxisValueLabelFormatter getAxisValueLabelFormatterForVal(double x, 246 double y) { 247 for (RectRegion r : axisValueLabelRegions.elements()) { 248 if (r.containsValue(x, y)) { 249 return axisValueLabelRegions.get(r); 250 } 251 } 252 return null; 253 } 254 255 public AxisValueLabelFormatter getAxisValueLabelFormatterForDomainVal( 256 double val) { 257 for (RectRegion r : axisValueLabelRegions.elements()) { 258 if (r.containsDomainValue(val)) { 259 return axisValueLabelRegions.get(r); 260 } 261 } 262 return null; 263 } 264 265 public AxisValueLabelFormatter getAxisValueLabelFormatterForRangeVal( 266 double val) { 267 for (RectRegion r : axisValueLabelRegions.elements()) { 268 if (r.containsRangeValue(val)) { 269 return axisValueLabelRegions.get(r); 270 } 271 } 272 return null; 273 } 274 275 /** 276 * Returns the formatter associated with the first (bottom-most) Region 277 * containing value. 278 * 279 * @param value 280 * @return 281 */ 282 /* 283 * public AxisValueLabelFormatter getXYAxisFormatterForRangeVal(double 284 * value) { return getRegionContainingVal(rangeLabelRegions, value); } 285 *//** 286 * Returns the formatter associated with the first (bottom-most) Region 287 * containing value. 288 * 289 * @param value 290 * @return 291 */ 292 /* 293 * public AxisValueLabelFormatter getXYAxisFormatterForDomainVal(double 294 * value) { return getRegionContainingVal(domainLabelRegions, value); } 295 */ 296 297 /* 298 * private AxisValueLabelFormatter getRegionContainingVal(ZHash<LineRegion, 299 * AxisValueLabelFormatter> zhash, double val) { for (LineRegion r : 300 * zhash.elements()) { if (r.contains(val)) { return 301 * rangeLabelRegions.get(r); } } // nothing found return null; } 302 */ 303 304 /** 305 * Returns a RectF representing the grid area last drawn by this plot. 306 * 307 * @return 308 */ 309 public RectF getGridRect() { 310 return paddedGridRect; 311 } 312 private String getFormattedRangeValue(Number value) { 313 return rangeValueFormat.format(value); 314 } 315 316 private String getFormattedDomainValue(Number value) { 317 return domainValueFormat.format(value); 318 } 319 320 /** 321 * Convenience method. Wraps getYVal(float) 322 * 323 * @param point 324 * @return 325 */ 326 public Double getYVal(PointF point) { 327 return getYVal(point.y); 328 } 329 330 /** 331 * Converts a y pixel to a y value. 332 * 333 * @param yPix 334 * @return 335 */ 336 public Double getYVal(float yPix) { 337 if (plot.getCalculatedMinY() == null 338 || plot.getCalculatedMaxY() == null) { 339 return null; 340 } 341 return ValPixConverter.pixToVal(yPix - paddedGridRect.top, plot 342 .getCalculatedMinY().doubleValue(), plot.getCalculatedMaxY() 343 .doubleValue(), paddedGridRect.height(), true); 344 } 345 346 /** 347 * Convenience method. Wraps getXVal(float) 348 * 349 * @param point 350 * @return 351 */ 352 public Double getXVal(PointF point) { 353 return getXVal(point.x); 354 } 355 356 /** 357 * Converts an x pixel into an x value. 358 * 359 * @param xPix 360 * @return 361 */ 362 public Double getXVal(float xPix) { 363 if (plot.getCalculatedMinX() == null 364 || plot.getCalculatedMaxX() == null) { 365 return null; 366 } 367 return ValPixConverter.pixToVal(xPix - paddedGridRect.left, plot 368 .getCalculatedMinX().doubleValue(), plot.getCalculatedMaxX() 369 .doubleValue(), paddedGridRect.width(), false); 370 } 371 372 @Override 373 protected void doOnDraw(Canvas canvas, RectF widgetRect) 374 throws PlotRenderException { 375 gridRect = getGridRect(widgetRect); // used for drawing the background 376 // of the grid 377 paddedGridRect = getPaddedGridRect(gridRect); // used for drawing lines 378 // etc. 379 //Log.v(TAG, "gridRect :" + gridRect); 380 //Log.v(TAG, "paddedGridRect :" + paddedGridRect); 381 // if (!plot.isEmpty()) { 382 // don't draw if we have no space to draw into 383 if ((paddedGridRect.height() > 0.0f) && (paddedGridRect.width() > 0.0f)) { 384 if (plot.getCalculatedMinX() != null 385 && plot.getCalculatedMaxX() != null 386 && plot.getCalculatedMinY() != null 387 && plot.getCalculatedMaxY() != null) { 388 drawGrid(canvas); 389 drawData(canvas); 390 drawCursors(canvas); 391 if (isDrawMarkersEnabled()) { 392 drawMarkers(canvas); 393 } 394 } 395 } 396 // } 397 } 398 399 private RectF getGridRect(RectF widgetRect) { 400 return new RectF(widgetRect.left + ((rangeAxisLeft)?rangeLabelWidth:1), 401 widgetRect.top + ((domainAxisBottom)?1:domainLabelWidth), 402 widgetRect.right - ((rangeAxisLeft)?1:rangeLabelWidth), 403 widgetRect.bottom - ((domainAxisBottom)?domainLabelWidth:1)); 404 } 405 406 private RectF getPaddedGridRect(RectF gridRect) { 407 return new RectF(gridRect.left + gridPaddingLeft, gridRect.top 408 + gridPaddingTop, gridRect.right - gridPaddingRight, 409 gridRect.bottom - gridPaddingBottom); 410 } 411 412 private void drawTickText(Canvas canvas, XYAxisType axis, Number value, 413 float xPix, float yPix, Paint labelPaint) { 414 AxisValueLabelFormatter rf = null; 415 String txt = null; 416 double v = value.doubleValue(); 417 418 int canvasState = canvas.save(); 419 try { 420 switch (axis) { 421 case DOMAIN: 422 rf = getAxisValueLabelFormatterForDomainVal(v); 423 txt = getFormattedDomainValue(value); 424 canvas.rotate(getDomainLabelOrientation(), xPix, yPix); 425 break; 426 case RANGE: 427 rf = getAxisValueLabelFormatterForRangeVal(v); 428 txt = getFormattedRangeValue(value); 429 canvas.rotate(getRangeLabelOrientation(), xPix, yPix); 430 break; 431 } 432 433 // if a matching region formatter was found, create a clone 434 // of labelPaint and use the formatter's color. Otherwise 435 // just use labelPaint: 436 Paint p; 437 if (rf != null) { 438 // p = rf.getPaint(); 439 p = new Paint(labelPaint); 440 p.setColor(rf.getColor()); 441 // p.setColor(Color.RED); 442 } else { 443 p = labelPaint; 444 } 445 canvas.drawText(txt, xPix, yPix, p); 446 } finally { 447 canvas.restoreToCount(canvasState); 448 } 449 } 450 451 private void drawDomainTick(Canvas canvas, float xPix, Number xVal, 452 Paint labelPaint, Paint linePaint, boolean drawLineOnly) { 453 if (!drawLineOnly) { 454 if (linePaint != null) { 455 if (domainAxisBottom){ 456 canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom 457 + domainLabelTickExtension, linePaint); 458 } else { 459 canvas.drawLine(xPix, gridRect.top - domainLabelTickExtension, xPix, 460 gridRect.bottom , linePaint); 461 } 462 } 463 if (labelPaint != null) { 464 float fontHeight = FontUtils.getFontHeight(labelPaint); 465 float yPix; 466 if (domainAxisBottom){ 467 yPix = gridRect.bottom + domainLabelTickExtension 468 + domainLabelVerticalOffset + fontHeight; 469 } else { 470 yPix = gridRect.top - domainLabelTickExtension 471 - domainLabelVerticalOffset; 472 } 473 drawTickText(canvas, XYAxisType.DOMAIN, xVal, xPix + domainLabelHorizontalOffset, yPix, 474 labelPaint); 475 } 476 } else if (linePaint != null) { 477 478 canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom, 479 linePaint); 480 481 } 482 } 483 484 public void drawRangeTick(Canvas canvas, float yPix, Number yVal, 485 Paint labelPaint, Paint linePaint, boolean drawLineOnly) { 486 if (!drawLineOnly) { 487 if (linePaint != null) { 488 if (rangeAxisLeft){ 489 canvas.drawLine(gridRect.left - rangeLabelTickExtension, yPix, 490 gridRect.right, yPix, linePaint); 491 } else { 492 canvas.drawLine(gridRect.left, yPix, 493 gridRect.right + rangeLabelTickExtension, yPix, linePaint); 494 } 495 } 496 if (labelPaint != null) { 497 float xPix; 498 if (rangeAxisLeft){ 499 xPix = gridRect.left 500 - (rangeLabelTickExtension + rangeLabelHorizontalOffset); 501 } else { 502 xPix = gridRect.right 503 + (rangeLabelTickExtension + rangeLabelHorizontalOffset); 504 } 505 drawTickText(canvas, XYAxisType.RANGE, yVal, xPix, yPix - rangeLabelVerticalOffset, 506 labelPaint); 507 } 508 } else if (linePaint != null) { 509 canvas.drawLine(gridRect.left, yPix, gridRect.right, yPix, 510 linePaint); 511 } 512 } 513 514 /** 515 * Draws the drid and domain/range labels for the plot. 516 * 517 * @param canvas 518 */ 519 protected void drawGrid(Canvas canvas) { 520 521 if (gridBackgroundPaint != null) { 522 canvas.drawRect(gridRect, gridBackgroundPaint); 523 } 524 525 float domainOriginF; 526 if (plot.getDomainOrigin() != null) { 527 double domainOriginVal = plot.getDomainOrigin().doubleValue(); 528 domainOriginF = ValPixConverter.valToPix(domainOriginVal, plot 529 .getCalculatedMinX().doubleValue(), plot 530 .getCalculatedMaxX().doubleValue(), paddedGridRect.width(), 531 false); 532 domainOriginF += paddedGridRect.left; 533 // if no origin is set, use the leftmost value visible on the grid: 534 } else { 535 domainOriginF = paddedGridRect.left; 536 } 537 538 XYStep domainStep = XYStepCalculator.getStep(plot, XYAxisType.DOMAIN, 539 paddedGridRect, plot.getCalculatedMinX().doubleValue(), plot 540 .getCalculatedMaxX().doubleValue()); 541 542 // draw domain origin: 543 if (domainOriginF >= paddedGridRect.left 544 && domainOriginF <= paddedGridRect.right) { 545 if (domainOriginLinePaint != null){ 546 domainOriginLinePaint.setTextAlign(Paint.Align.CENTER); 547 } 548 drawDomainTick(canvas, domainOriginF, plot.getDomainOrigin() 549 .doubleValue(), domainOriginLabelPaint, 550 domainOriginLinePaint, false); 551 } 552 553 // draw ticks LEFT of origin: 554 { 555 int i = 1; 556 double xVal; 557 float xPix = domainOriginF - domainStep.getStepPix(); 558 for (; xPix >= paddedGridRect.left; xPix = domainOriginF 559 - (i * domainStep.getStepPix())) { 560 xVal = plot.getDomainOrigin().doubleValue() - i 561 * domainStep.getStepVal(); 562 if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) { 563 if (i % getTicksPerDomainLabel() == 0) { 564 drawDomainTick(canvas, xPix, xVal, domainLabelPaint, 565 domainGridLinePaint, false); 566 } else { 567 drawDomainTick(canvas, xPix, xVal, domainLabelPaint, 568 domainSubGridLinePaint, true); 569 } 570 } 571 i++; 572 } 573 } 574 575 // draw ticks RIGHT of origin: 576 { 577 int i = 1; 578 double xVal; 579 float xPix = domainOriginF + domainStep.getStepPix(); 580 for (; xPix <= paddedGridRect.right; xPix = domainOriginF 581 + (i * domainStep.getStepPix())) { 582 xVal = plot.getDomainOrigin().doubleValue() + i 583 * domainStep.getStepVal(); 584 if (xPix >= paddedGridRect.left && xPix <= paddedGridRect.right) { 585 586 if (i % getTicksPerDomainLabel() == 0) { 587 drawDomainTick(canvas, xPix, xVal, domainLabelPaint, 588 domainGridLinePaint, false); 589 } else { 590 drawDomainTick(canvas, xPix, xVal, domainLabelPaint, 591 domainSubGridLinePaint, true); 592 } 593 } 594 i++; 595 } 596 } 597 598 // draw range origin: 599 600 float rangeOriginF; 601 if (plot.getRangeOrigin() != null) { 602 // --------- NEW WAY ------ 603 double rangeOriginD = plot.getRangeOrigin().doubleValue(); 604 rangeOriginF = ValPixConverter.valToPix(rangeOriginD, plot 605 .getCalculatedMinY().doubleValue(), plot 606 .getCalculatedMaxY().doubleValue(), 607 paddedGridRect.height(), true); 608 rangeOriginF += paddedGridRect.top; 609 // if no origin is set, use the leftmost value visible on the grid 610 } else { 611 rangeOriginF = paddedGridRect.bottom; 612 } 613 614 XYStep rangeStep = XYStepCalculator.getStep(plot, XYAxisType.RANGE, 615 paddedGridRect, plot.getCalculatedMinY().doubleValue(), plot 616 .getCalculatedMaxY().doubleValue()); 617 618 // draw range origin: 619 if (rangeOriginF >= paddedGridRect.top 620 && rangeOriginF <= paddedGridRect.bottom) { 621 if (rangeOriginLinePaint != null){ 622 rangeOriginLinePaint.setTextAlign(Paint.Align.RIGHT); 623 } 624 drawRangeTick(canvas, rangeOriginF, plot.getRangeOrigin() 625 .doubleValue(), rangeOriginLabelPaint, 626 rangeOriginLinePaint, false); 627 } 628 // draw ticks ABOVE origin: 629 { 630 int i = 1; 631 double yVal; 632 float yPix = rangeOriginF - rangeStep.getStepPix(); 633 for (; yPix >= paddedGridRect.top; yPix = rangeOriginF 634 - (i * rangeStep.getStepPix())) { 635 yVal = plot.getRangeOrigin().doubleValue() + i 636 * rangeStep.getStepVal(); 637 if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) { 638 if (i % getTicksPerRangeLabel() == 0) { 639 drawRangeTick(canvas, yPix, yVal, rangeLabelPaint, 640 rangeGridLinePaint, false); 641 } else { 642 drawRangeTick(canvas, yPix, yVal, rangeLabelPaint, 643 rangeSubGridLinePaint, true); 644 } 645 } 646 i++; 647 } 648 } 649 650 // draw ticks BENEATH origin: 651 { 652 int i = 1; 653 double yVal; 654 float yPix = rangeOriginF + rangeStep.getStepPix(); 655 for (; yPix <= paddedGridRect.bottom; yPix = rangeOriginF 656 + (i * rangeStep.getStepPix())) { 657 yVal = plot.getRangeOrigin().doubleValue() - i 658 * rangeStep.getStepVal(); 659 if (yPix >= paddedGridRect.top && yPix <= paddedGridRect.bottom) { 660 if (i % getTicksPerRangeLabel() == 0) { 661 drawRangeTick(canvas, yPix, yVal, rangeLabelPaint, 662 rangeGridLinePaint, false); 663 } else { 664 drawRangeTick(canvas, yPix, yVal, rangeLabelPaint, 665 rangeSubGridLinePaint, true); 666 } 667 } 668 i++; 669 } 670 } 671 } 672 673 /** 674 * Renders the text associated with user defined markers 675 * 676 * @param canvas 677 * @param text 678 * @param marker 679 * @param x 680 * @param y 681 */ 682 private void drawMarkerText(Canvas canvas, String text, ValueMarker marker, 683 float x, float y) { 684 x += MARKER_LABEL_SPACING; 685 y -= MARKER_LABEL_SPACING; 686 RectF textRect = new RectF(FontUtils.getStringDimensions(text, 687 marker.getTextPaint())); 688 textRect.offsetTo(x, y - textRect.height()); 689 690 if (textRect.right > paddedGridRect.right) { 691 textRect.offset(-(textRect.right - paddedGridRect.right), 0); 692 } 693 694 if (textRect.top < paddedGridRect.top) { 695 textRect.offset(0, paddedGridRect.top - textRect.top); 696 } 697 698 canvas.drawText(text, textRect.left, textRect.bottom, 699 marker.getTextPaint()); 700 701 } 702 703 protected void drawMarkers(Canvas canvas) { 704 for (YValueMarker marker : plot.getYValueMarkers()) { 705 706 if (marker.getValue() != null) { 707 double yVal = marker.getValue().doubleValue(); 708 float yPix = ValPixConverter.valToPix(yVal, plot 709 .getCalculatedMinY().doubleValue(), plot 710 .getCalculatedMaxY().doubleValue(), paddedGridRect 711 .height(), true); 712 yPix += paddedGridRect.top; 713 canvas.drawLine(paddedGridRect.left, yPix, 714 paddedGridRect.right, yPix, marker.getLinePaint()); 715 716 // String text = getFormattedRangeValue(yVal); 717 float xPix = marker.getTextPosition().getPixelValue( 718 paddedGridRect.width()); 719 xPix += paddedGridRect.left; 720 721 if (marker.getText() != null) { 722 drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); 723 } else { 724 drawMarkerText(canvas, 725 getFormattedRangeValue(marker.getValue()), marker, 726 xPix, yPix); 727 } 728 } 729 } 730 731 for (XValueMarker marker : plot.getXValueMarkers()) { 732 if (marker.getValue() != null) { 733 double xVal = marker.getValue().doubleValue(); 734 float xPix = ValPixConverter.valToPix(xVal, plot 735 .getCalculatedMinX().doubleValue(), plot 736 .getCalculatedMaxX().doubleValue(), paddedGridRect 737 .width(), false); 738 xPix += paddedGridRect.left; 739 canvas.drawLine(xPix, paddedGridRect.top, xPix, 740 paddedGridRect.bottom, marker.getLinePaint()); 741 742 // String text = getFormattedDomainValue(xVal); 743 float yPix = marker.getTextPosition().getPixelValue( 744 paddedGridRect.height()); 745 yPix += paddedGridRect.top; 746 if (marker.getText() != null) { 747 drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); 748 } else { 749 drawMarkerText(canvas, 750 getFormattedDomainValue(marker.getValue()), marker, 751 xPix, yPix); 752 } 753 } 754 } 755 } 756 757 protected void drawCursors(Canvas canvas) { 758 boolean hasDomainCursor = false; 759 // draw the domain cursor: 760 if (domainCursorPaint != null 761 && domainCursorPosition <= paddedGridRect.right 762 && domainCursorPosition >= paddedGridRect.left) { 763 hasDomainCursor = true; 764 canvas.drawLine(domainCursorPosition, paddedGridRect.top, 765 domainCursorPosition, paddedGridRect.bottom, 766 domainCursorPaint); 767 } 768 769 boolean hasRangeCursor = false; 770 // draw the range cursor: 771 if (rangeCursorPaint != null 772 && rangeCursorPosition >= paddedGridRect.top 773 && rangeCursorPosition <= paddedGridRect.bottom) { 774 hasRangeCursor = true; 775 canvas.drawLine(paddedGridRect.left, rangeCursorPosition, 776 paddedGridRect.right, rangeCursorPosition, rangeCursorPaint); 777 } 778 779 if (drawCursorLabelEnabled && cursorLabelPaint != null 780 && hasRangeCursor && hasDomainCursor) { 781 782 String label = "X=" 783 + getDomainValueFormat().format(getDomainCursorVal()); 784 label += " Y=" + getRangeValueFormat().format(getRangeCursorVal()); 785 786 // convert the label dimensions rect into floating-point: 787 RectF cursorRect = new RectF(FontUtils.getPackedStringDimensions( 788 label, cursorLabelPaint)); 789 cursorRect.offsetTo(domainCursorPosition, rangeCursorPosition 790 - cursorRect.height()); 791 792 // if we are too close to the right edge of the plot, we will move 793 // the 794 // label to the left side of our cursor: 795 if (cursorRect.right >= paddedGridRect.right) { 796 cursorRect.offsetTo(domainCursorPosition - cursorRect.width(), 797 cursorRect.top); 798 } 799 800 // same thing for the top edge of the plot: 801 // dunno why but these rects can have negative values for top and 802 // bottom. 803 if (cursorRect.top <= paddedGridRect.top) { 804 cursorRect.offsetTo(cursorRect.left, rangeCursorPosition); 805 } 806 807 if (cursorLabelBackgroundPaint != null) { 808 canvas.drawRect(cursorRect, cursorLabelBackgroundPaint); 809 } 810 811 canvas.drawText(label, cursorRect.left, cursorRect.bottom, 812 cursorLabelPaint); 813 } 814 } 815 816 /** 817 * Draws lines and points for each element in the series. 818 * 819 * @param canvas 820 * @throws PlotRenderException 821 */ 822 protected void drawData(Canvas canvas) throws PlotRenderException { 823 // TODO: iterate through a XYSeriesRenderer list 824 825 // int canvasState = canvas.save(); 826 try { 827 canvas.save(Canvas.ALL_SAVE_FLAG); 828 canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT); 829 for (XYSeriesRenderer renderer : plot.getRendererList()) { 830 renderer.render(canvas, paddedGridRect); 831 } 832 // canvas.restoreToCount(canvasState); 833 } finally { 834 canvas.restore(); 835 } 836 } 837 838 protected void drawPoint(Canvas canvas, PointF point, Paint paint) { 839 canvas.drawPoint(point.x, point.y, paint); 840 } 841 842 public float getDomainLabelWidth() { 843 return domainLabelWidth; 844 } 845 846 public void setDomainLabelWidth(float domainLabelWidth) { 847 this.domainLabelWidth = domainLabelWidth; 848 } 849 850 public float getRangeLabelWidth() { 851 return rangeLabelWidth; 852 } 853 854 public void setRangeLabelWidth(float rangeLabelWidth) { 855 this.rangeLabelWidth = rangeLabelWidth; 856 } 857 858 public float getDomainLabelVerticalOffset() { 859 return domainLabelVerticalOffset; 860 } 861 862 public void setDomainLabelVerticalOffset(float domainLabelVerticalOffset) { 863 this.domainLabelVerticalOffset = domainLabelVerticalOffset; 864 } 865 866 public float getDomainLabelHorizontalOffset() { 867 return domainLabelHorizontalOffset; 868 } 869 870 public void setDomainLabelHorizontalOffset(float domainLabelHorizontalOffset) { 871 this.domainLabelHorizontalOffset = domainLabelHorizontalOffset; 872 } 873 874 public float getRangeLabelHorizontalOffset() { 875 return rangeLabelHorizontalOffset; 876 } 877 878 public void setRangeLabelHorizontalOffset(float rangeLabelHorizontalOffset) { 879 this.rangeLabelHorizontalOffset = rangeLabelHorizontalOffset; 880 } 881 882 public float getRangeLabelVerticalOffset() { 883 return rangeLabelVerticalOffset; 884 } 885 886 public void setRangeLabelVerticalOffset(float rangeLabelVerticalOffset) { 887 this.rangeLabelVerticalOffset = rangeLabelVerticalOffset; 888 } 889 890 public Paint getGridBackgroundPaint() { 891 return gridBackgroundPaint; 892 } 893 894 public void setGridBackgroundPaint(Paint gridBackgroundPaint) { 895 this.gridBackgroundPaint = gridBackgroundPaint; 896 } 897 898 public Paint getDomainLabelPaint() { 899 return domainLabelPaint; 900 } 901 902 public void setDomainLabelPaint(Paint domainLabelPaint) { 903 this.domainLabelPaint = domainLabelPaint; 904 } 905 906 public Paint getRangeLabelPaint() { 907 return rangeLabelPaint; 908 } 909 910 public void setRangeLabelPaint(Paint rangeLabelPaint) { 911 this.rangeLabelPaint = rangeLabelPaint; 912 } 913 914 /** 915 * Get the paint used to draw the domain grid line. 916 */ 917 public Paint getDomainGridLinePaint() { 918 return domainGridLinePaint; 919 } 920 921 /** 922 * Set the paint used to draw the domain grid line. 923 * @param gridLinePaint 924 */ 925 public void setDomainGridLinePaint(Paint gridLinePaint) { 926 this.domainGridLinePaint = gridLinePaint; 927 } 928 929 /** 930 * Get the paint used to draw the range grid line. 931 */ 932 public Paint getRangeGridLinePaint() { 933 return rangeGridLinePaint; 934 } 935 936 /** 937 * Get the paint used to draw the domain grid line. 938 */ 939 public Paint getDomainSubGridLinePaint() { 940 return domainSubGridLinePaint; 941 } 942 943 /** 944 * Set the paint used to draw the domain grid line. 945 * @param gridLinePaint 946 */ 947 public void setDomainSubGridLinePaint(Paint gridLinePaint) { 948 this.domainSubGridLinePaint = gridLinePaint; 949 } 950 951 /** 952 * Set the Paint used to draw the range grid line. 953 * @param gridLinePaint 954 */ 955 public void setRangeGridLinePaint(Paint gridLinePaint) { 956 this.rangeGridLinePaint = gridLinePaint; 957 } 958 959 /** 960 * Get the paint used to draw the range grid line. 961 */ 962 public Paint getRangeSubGridLinePaint() { 963 return rangeSubGridLinePaint; 964 } 965 966 /** 967 * Set the Paint used to draw the range grid line. 968 * @param gridLinePaint 969 */ 970 public void setRangeSubGridLinePaint(Paint gridLinePaint) { 971 this.rangeSubGridLinePaint = gridLinePaint; 972 } 973 974 // TODO: make a generic renderer queue. 975 976 public Format getRangeValueFormat() { 977 return rangeValueFormat; 978 } 979 980 public void setRangeValueFormat(Format rangeValueFormat) { 981 this.rangeValueFormat = rangeValueFormat; 982 } 983 984 public Format getDomainValueFormat() { 985 return domainValueFormat; 986 } 987 988 public void setDomainValueFormat(Format domainValueFormat) { 989 this.domainValueFormat = domainValueFormat; 990 } 991 992 public int getDomainLabelTickExtension() { 993 return domainLabelTickExtension; 994 } 995 996 public void setDomainLabelTickExtension(int domainLabelTickExtension) { 997 this.domainLabelTickExtension = domainLabelTickExtension; 998 } 999 1000 public int getRangeLabelTickExtension() { 1001 return rangeLabelTickExtension; 1002 } 1003 1004 public void setRangeLabelTickExtension(int rangeLabelTickExtension) { 1005 this.rangeLabelTickExtension = rangeLabelTickExtension; 1006 } 1007 1008 public int getTicksPerRangeLabel() { 1009 return ticksPerRangeLabel; 1010 } 1011 1012 public void setTicksPerRangeLabel(int ticksPerRangeLabel) { 1013 this.ticksPerRangeLabel = ticksPerRangeLabel; 1014 } 1015 1016 public int getTicksPerDomainLabel() { 1017 return ticksPerDomainLabel; 1018 } 1019 1020 public void setTicksPerDomainLabel(int ticksPerDomainLabel) { 1021 this.ticksPerDomainLabel = ticksPerDomainLabel; 1022 } 1023 1024 public void setGridPaddingTop(float gridPaddingTop) { 1025 this.gridPaddingTop = gridPaddingTop; 1026 } 1027 1028 public float getGridPaddingBottom() { 1029 return gridPaddingBottom; 1030 } 1031 1032 public void setGridPaddingBottom(float gridPaddingBottom) { 1033 this.gridPaddingBottom = gridPaddingBottom; 1034 } 1035 1036 public float getGridPaddingLeft() { 1037 return gridPaddingLeft; 1038 } 1039 1040 public void setGridPaddingLeft(float gridPaddingLeft) { 1041 this.gridPaddingLeft = gridPaddingLeft; 1042 } 1043 1044 public float getGridPaddingRight() { 1045 return gridPaddingRight; 1046 } 1047 1048 public void setGridPaddingRight(float gridPaddingRight) { 1049 this.gridPaddingRight = gridPaddingRight; 1050 } 1051 1052 public float getGridPaddingTop() { 1053 return gridPaddingTop; 1054 } 1055 1056 public void setGridPadding(float left, float top, float right, float bottom) { 1057 setGridPaddingLeft(left); 1058 setGridPaddingTop(top); 1059 setGridPaddingRight(right); 1060 setGridPaddingBottom(bottom); 1061 } 1062 1063 public Paint getDomainOriginLinePaint() { 1064 return domainOriginLinePaint; 1065 } 1066 1067 public void setDomainOriginLinePaint(Paint domainOriginLinePaint) { 1068 this.domainOriginLinePaint = domainOriginLinePaint; 1069 } 1070 1071 public Paint getRangeOriginLinePaint() { 1072 return rangeOriginLinePaint; 1073 } 1074 1075 public void setRangeOriginLinePaint(Paint rangeOriginLinePaint) { 1076 this.rangeOriginLinePaint = rangeOriginLinePaint; 1077 } 1078 1079 public Paint getDomainOriginLabelPaint() { 1080 return domainOriginLabelPaint; 1081 } 1082 1083 public void setDomainOriginLabelPaint(Paint domainOriginLabelPaint) { 1084 this.domainOriginLabelPaint = domainOriginLabelPaint; 1085 } 1086 1087 public Paint getRangeOriginLabelPaint() { 1088 return rangeOriginLabelPaint; 1089 } 1090 1091 public void setRangeOriginLabelPaint(Paint rangeOriginLabelPaint) { 1092 this.rangeOriginLabelPaint = rangeOriginLabelPaint; 1093 } 1094 1095 public void setCursorPosition(float x, float y) { 1096 setDomainCursorPosition(x); 1097 setRangeCursorPosition(y); 1098 } 1099 1100 public void setCursorPosition(PointF point) { 1101 setCursorPosition(point.x, point.y); 1102 } 1103 1104 public float getDomainCursorPosition() { 1105 return domainCursorPosition; 1106 } 1107 1108 public Double getDomainCursorVal() { 1109 return getXVal(getDomainCursorPosition()); 1110 } 1111 1112 public void setDomainCursorPosition(float domainCursorPosition) { 1113 this.domainCursorPosition = domainCursorPosition; 1114 } 1115 1116 public float getRangeCursorPosition() { 1117 return rangeCursorPosition; 1118 } 1119 1120 public Double getRangeCursorVal() { 1121 return getYVal(getRangeCursorPosition()); 1122 } 1123 1124 public void setRangeCursorPosition(float rangeCursorPosition) { 1125 this.rangeCursorPosition = rangeCursorPosition; 1126 } 1127 1128 public Paint getCursorLabelPaint() { 1129 return cursorLabelPaint; 1130 } 1131 1132 public void setCursorLabelPaint(Paint cursorLabelPaint) { 1133 this.cursorLabelPaint = cursorLabelPaint; 1134 } 1135 1136 public Paint getCursorLabelBackgroundPaint() { 1137 return cursorLabelBackgroundPaint; 1138 } 1139 1140 public void setCursorLabelBackgroundPaint(Paint cursorLabelBackgroundPaint) { 1141 this.cursorLabelBackgroundPaint = cursorLabelBackgroundPaint; 1142 } 1143 1144 public boolean isDrawMarkersEnabled() { 1145 return drawMarkersEnabled; 1146 } 1147 1148 public void setDrawMarkersEnabled(boolean drawMarkersEnabled) { 1149 this.drawMarkersEnabled = drawMarkersEnabled; 1150 } 1151 1152 public boolean isRangeAxisLeft() { 1153 return rangeAxisLeft; 1154 } 1155 1156 public void setRangeAxisLeft(boolean rangeAxisLeft) { 1157 this.rangeAxisLeft = rangeAxisLeft; 1158 } 1159 1160 public boolean isDomainAxisBottom() { 1161 return domainAxisBottom; 1162 } 1163 1164 public void setDomainAxisBottom(boolean domainAxisBottom) { 1165 this.domainAxisBottom = domainAxisBottom; 1166 } 1167 1168 /* 1169 * set the position of the range axis labels. Set the labelPaint textSizes before setting this. 1170 * This call sets the various vertical and horizontal offsets and widths to good defaults. 1171 * 1172 * @param rangeAxisLeft axis labels are on the left hand side not the right hand side. 1173 * @param rangeAxisOverlay axis labels are overlaid on the plot, not external to it. 1174 * @param tickSize the size of the tick extensions for none overlaid axis. 1175 * @param maxLableString Sample label representing the biggest size space needs to be allocated for. 1176 */ 1177 public void setRangeAxisPosition(boolean rangeAxisLeft, boolean rangeAxisOverlay, int tickSize, String maxLableString){ 1178 setRangeAxisLeft(rangeAxisLeft); 1179 1180 if (rangeAxisOverlay) { 1181 setRangeLabelWidth(1); // needs to be at least 1 to display grid line. 1182 setRangeLabelHorizontalOffset(-2.0f); 1183 setRangeLabelVerticalOffset(2.0f); // get above the line 1184 Paint p = getRangeLabelPaint(); 1185 if (p != null) { 1186 p.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); 1187 } 1188 Paint po = getRangeOriginLabelPaint(); 1189 if (po != null) { 1190 po.setTextAlign(((rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); 1191 } 1192 setRangeLabelTickExtension(0); 1193 } else { 1194 setRangeLabelWidth(1); // needs to be at least 1 to display grid line. 1195 // if we have a paint this gets bigger. 1196 setRangeLabelHorizontalOffset(1.0f); 1197 setRangeLabelTickExtension(tickSize); 1198 Paint p = getRangeLabelPaint(); 1199 if (p != null) { 1200 p.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); 1201 Rect r = FontUtils.getPackedStringDimensions(maxLableString,p); 1202 setRangeLabelVerticalOffset(r.top/2); 1203 setRangeLabelWidth(r.right + getRangeLabelTickExtension()); 1204 } 1205 Paint po = getRangeOriginLabelPaint(); 1206 if (po != null) { 1207 po.setTextAlign(((!rangeAxisLeft)?Paint.Align.LEFT:Paint.Align.RIGHT)); 1208 } 1209 } 1210 } 1211 1212 /* 1213 * set the position of the domain axis labels. Set the labelPaint textSizes before setting this. 1214 * This call sets the various vertical and horizontal offsets and widths to good defaults. 1215 * 1216 * @param domainAxisBottom axis labels are on the bottom not the top of the plot. 1217 * @param domainAxisOverlay axis labels are overlaid on the plot, not external to it. 1218 * @param tickSize the size of the tick extensions for non overlaid axis. 1219 * @param maxLableString Sample label representing the biggest size space needs to be allocated for. 1220 */ 1221 public void setDomainAxisPosition(boolean domainAxisBottom, boolean domainAxisOverlay, int tickSize, String maxLabelString){ 1222 setDomainAxisBottom(domainAxisBottom); 1223 if (domainAxisOverlay) { 1224 setDomainLabelWidth(1); // needs to be at least 1 to display grid line. 1225 setDomainLabelVerticalOffset(2.0f); // get above the line 1226 setDomainLabelTickExtension(0); 1227 Paint p = getDomainLabelPaint(); 1228 if (p != null) { 1229 Rect r = FontUtils.getPackedStringDimensions(maxLabelString,p); 1230 if (domainAxisBottom){ 1231 setDomainLabelVerticalOffset(2 * r.top); 1232 } else { 1233 setDomainLabelVerticalOffset(r.top - 1.0f); 1234 } 1235 } 1236 } else { 1237 setDomainLabelWidth(1); // needs to be at least 1 to display grid line. 1238 // if we have a paint this gets bigger. 1239 setDomainLabelTickExtension(tickSize); 1240 Paint p = getDomainLabelPaint(); 1241 if (p != null) { 1242 float fontHeight = FontUtils.getFontHeight(p); 1243 if (domainAxisBottom){ 1244 setDomainLabelVerticalOffset(-4.0f); 1245 } else { 1246 setDomainLabelVerticalOffset(+1.0f); 1247 } 1248 setDomainLabelWidth(fontHeight + getDomainLabelTickExtension()); 1249 } 1250 } 1251 } 1252 } 1253