1 2 package com.github.mikephil.charting.utils; 3 4 import android.graphics.Matrix; 5 import android.graphics.RectF; 6 import android.view.View; 7 8 /** 9 * Class that contains information about the charts current viewport settings, including offsets, scale & translation 10 * levels, ... 11 * 12 * @author Philipp Jahoda 13 */ 14 public class ViewPortHandler { 15 16 /** 17 * matrix used for touch events 18 */ 19 protected final Matrix mMatrixTouch = new Matrix(); 20 21 /** 22 * this rectangle defines the area in which graph values can be drawn 23 */ 24 protected RectF mContentRect = new RectF(); 25 26 protected float mChartWidth = 0f; 27 protected float mChartHeight = 0f; 28 29 /** 30 * minimum scale value on the y-axis 31 */ 32 private float mMinScaleY = 1f; 33 34 /** 35 * maximum scale value on the y-axis 36 */ 37 private float mMaxScaleY = Float.MAX_VALUE; 38 39 /** 40 * minimum scale value on the x-axis 41 */ 42 private float mMinScaleX = 1f; 43 44 /** 45 * maximum scale value on the x-axis 46 */ 47 private float mMaxScaleX = Float.MAX_VALUE; 48 49 /** 50 * contains the current scale factor of the x-axis 51 */ 52 private float mScaleX = 1f; 53 54 /** 55 * contains the current scale factor of the y-axis 56 */ 57 private float mScaleY = 1f; 58 59 /** 60 * current translation (drag distance) on the x-axis 61 */ 62 private float mTransX = 0f; 63 64 /** 65 * current translation (drag distance) on the y-axis 66 */ 67 private float mTransY = 0f; 68 69 /** 70 * offset that allows the chart to be dragged over its bounds on the x-axis 71 */ 72 private float mTransOffsetX = 0f; 73 74 /** 75 * offset that allows the chart to be dragged over its bounds on the x-axis 76 */ 77 private float mTransOffsetY = 0f; 78 79 /** 80 * Constructor - don't forget calling setChartDimens(...) 81 */ ViewPortHandler()82 public ViewPortHandler() { 83 84 } 85 86 /** 87 * Sets the width and height of the chart. 88 * 89 * @param width 90 * @param height 91 */ 92 setChartDimens(float width, float height)93 public void setChartDimens(float width, float height) { 94 95 float offsetLeft = this.offsetLeft(); 96 float offsetTop = this.offsetTop(); 97 float offsetRight = this.offsetRight(); 98 float offsetBottom = this.offsetBottom(); 99 100 mChartHeight = height; 101 mChartWidth = width; 102 103 restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom); 104 } 105 hasChartDimens()106 public boolean hasChartDimens() { 107 if (mChartHeight > 0 && mChartWidth > 0) 108 return true; 109 else 110 return false; 111 } 112 restrainViewPort(float offsetLeft, float offsetTop, float offsetRight, float offsetBottom)113 public void restrainViewPort(float offsetLeft, float offsetTop, float offsetRight, 114 float offsetBottom) { 115 mContentRect.set(offsetLeft, offsetTop, mChartWidth - offsetRight, mChartHeight 116 - offsetBottom); 117 } 118 offsetLeft()119 public float offsetLeft() { 120 return mContentRect.left; 121 } 122 offsetRight()123 public float offsetRight() { 124 return mChartWidth - mContentRect.right; 125 } 126 offsetTop()127 public float offsetTop() { 128 return mContentRect.top; 129 } 130 offsetBottom()131 public float offsetBottom() { 132 return mChartHeight - mContentRect.bottom; 133 } 134 contentTop()135 public float contentTop() { 136 return mContentRect.top; 137 } 138 contentLeft()139 public float contentLeft() { 140 return mContentRect.left; 141 } 142 contentRight()143 public float contentRight() { 144 return mContentRect.right; 145 } 146 contentBottom()147 public float contentBottom() { 148 return mContentRect.bottom; 149 } 150 contentWidth()151 public float contentWidth() { 152 return mContentRect.width(); 153 } 154 contentHeight()155 public float contentHeight() { 156 return mContentRect.height(); 157 } 158 getContentRect()159 public RectF getContentRect() { 160 return mContentRect; 161 } 162 getContentCenter()163 public MPPointF getContentCenter() { 164 return MPPointF.getInstance(mContentRect.centerX(), mContentRect.centerY()); 165 } 166 getChartHeight()167 public float getChartHeight() { 168 return mChartHeight; 169 } 170 getChartWidth()171 public float getChartWidth() { 172 return mChartWidth; 173 } 174 175 /** 176 * Returns the smallest extension of the content rect (width or height). 177 * 178 * @return 179 */ getSmallestContentExtension()180 public float getSmallestContentExtension() { 181 return Math.min(mContentRect.width(), mContentRect.height()); 182 } 183 184 /** 185 * ################ ################ ################ ################ 186 */ 187 /** CODE BELOW THIS RELATED TO SCALING AND GESTURES */ 188 189 /** 190 * Zooms in by 1.4f, x and y are the coordinates (in pixels) of the zoom 191 * center. 192 * 193 * @param x 194 * @param y 195 */ zoomIn(float x, float y)196 public Matrix zoomIn(float x, float y) { 197 198 Matrix save = new Matrix(); 199 zoomIn(x, y, save); 200 return save; 201 } 202 zoomIn(float x, float y, Matrix outputMatrix)203 public void zoomIn(float x, float y, Matrix outputMatrix) { 204 outputMatrix.reset(); 205 outputMatrix.set(mMatrixTouch); 206 outputMatrix.postScale(1.4f, 1.4f, x, y); 207 } 208 209 /** 210 * Zooms out by 0.7f, x and y are the coordinates (in pixels) of the zoom 211 * center. 212 */ zoomOut(float x, float y)213 public Matrix zoomOut(float x, float y) { 214 215 Matrix save = new Matrix(); 216 zoomOut(x, y, save); 217 return save; 218 } 219 zoomOut(float x, float y, Matrix outputMatrix)220 public void zoomOut(float x, float y, Matrix outputMatrix) { 221 outputMatrix.reset(); 222 outputMatrix.set(mMatrixTouch); 223 outputMatrix.postScale(0.7f, 0.7f, x, y); 224 } 225 226 /** 227 * Zooms out to original size. 228 * @param outputMatrix 229 */ resetZoom(Matrix outputMatrix)230 public void resetZoom(Matrix outputMatrix) { 231 outputMatrix.reset(); 232 outputMatrix.set(mMatrixTouch); 233 outputMatrix.postScale(1.0f, 1.0f, 0.0f, 0.0f); 234 } 235 236 /** 237 * Post-scales by the specified scale factors. 238 * 239 * @param scaleX 240 * @param scaleY 241 * @return 242 */ zoom(float scaleX, float scaleY)243 public Matrix zoom(float scaleX, float scaleY) { 244 245 Matrix save = new Matrix(); 246 zoom(scaleX, scaleY, save); 247 return save; 248 } 249 zoom(float scaleX, float scaleY, Matrix outputMatrix)250 public void zoom(float scaleX, float scaleY, Matrix outputMatrix) { 251 outputMatrix.reset(); 252 outputMatrix.set(mMatrixTouch); 253 outputMatrix.postScale(scaleX, scaleY); 254 } 255 256 /** 257 * Post-scales by the specified scale factors. x and y is pivot. 258 * 259 * @param scaleX 260 * @param scaleY 261 * @param x 262 * @param y 263 * @return 264 */ zoom(float scaleX, float scaleY, float x, float y)265 public Matrix zoom(float scaleX, float scaleY, float x, float y) { 266 267 Matrix save = new Matrix(); 268 zoom(scaleX, scaleY, x, y, save); 269 return save; 270 } 271 zoom(float scaleX, float scaleY, float x, float y, Matrix outputMatrix)272 public void zoom(float scaleX, float scaleY, float x, float y, Matrix outputMatrix) { 273 outputMatrix.reset(); 274 outputMatrix.set(mMatrixTouch); 275 outputMatrix.postScale(scaleX, scaleY, x, y); 276 } 277 278 /** 279 * Sets the scale factor to the specified values. 280 * 281 * @param scaleX 282 * @param scaleY 283 * @return 284 */ setZoom(float scaleX, float scaleY)285 public Matrix setZoom(float scaleX, float scaleY) { 286 287 Matrix save = new Matrix(); 288 setZoom(scaleX, scaleY, save); 289 return save; 290 } 291 setZoom(float scaleX, float scaleY, Matrix outputMatrix)292 public void setZoom(float scaleX, float scaleY, Matrix outputMatrix) { 293 outputMatrix.reset(); 294 outputMatrix.set(mMatrixTouch); 295 outputMatrix.setScale(scaleX, scaleY); 296 } 297 298 /** 299 * Sets the scale factor to the specified values. x and y is pivot. 300 * 301 * @param scaleX 302 * @param scaleY 303 * @param x 304 * @param y 305 * @return 306 */ setZoom(float scaleX, float scaleY, float x, float y)307 public Matrix setZoom(float scaleX, float scaleY, float x, float y) { 308 309 Matrix save = new Matrix(); 310 save.set(mMatrixTouch); 311 312 save.setScale(scaleX, scaleY, x, y); 313 314 return save; 315 } 316 317 protected float[] valsBufferForFitScreen = new float[9]; 318 319 /** 320 * Resets all zooming and dragging and makes the chart fit exactly it's 321 * bounds. 322 */ fitScreen()323 public Matrix fitScreen() { 324 325 Matrix save = new Matrix(); 326 fitScreen(save); 327 return save; 328 } 329 330 /** 331 * Resets all zooming and dragging and makes the chart fit exactly it's 332 * bounds. Output Matrix is available for those who wish to cache the object. 333 */ fitScreen(Matrix outputMatrix)334 public void fitScreen(Matrix outputMatrix) { 335 mMinScaleX = 1f; 336 mMinScaleY = 1f; 337 338 outputMatrix.set(mMatrixTouch); 339 340 float[] vals = valsBufferForFitScreen; 341 for (int i = 0; i < 9; i++) { 342 vals[i] = 0; 343 } 344 345 outputMatrix.getValues(vals); 346 347 // reset all translations and scaling 348 vals[Matrix.MTRANS_X] = 0f; 349 vals[Matrix.MTRANS_Y] = 0f; 350 vals[Matrix.MSCALE_X] = 1f; 351 vals[Matrix.MSCALE_Y] = 1f; 352 353 outputMatrix.setValues(vals); 354 } 355 356 /** 357 * Post-translates to the specified points. Less Performant. 358 * 359 * @param transformedPts 360 * @return 361 */ translate(final float[] transformedPts)362 public Matrix translate(final float[] transformedPts) { 363 364 Matrix save = new Matrix(); 365 translate(transformedPts, save); 366 return save; 367 } 368 369 /** 370 * Post-translates to the specified points. Output matrix allows for caching objects. 371 * 372 * @param transformedPts 373 * @return 374 */ translate(final float[] transformedPts, Matrix outputMatrix)375 public void translate(final float[] transformedPts, Matrix outputMatrix) { 376 outputMatrix.reset(); 377 outputMatrix.set(mMatrixTouch); 378 final float x = transformedPts[0] - offsetLeft(); 379 final float y = transformedPts[1] - offsetTop(); 380 outputMatrix.postTranslate(-x, -y); 381 } 382 383 protected Matrix mCenterViewPortMatrixBuffer = new Matrix(); 384 385 /** 386 * Centers the viewport around the specified position (x-index and y-value) 387 * in the chart. Centering the viewport outside the bounds of the chart is 388 * not possible. Makes most sense in combination with the 389 * setScaleMinima(...) method. 390 * 391 * @param transformedPts the position to center view viewport to 392 * @param view 393 * @return save 394 */ centerViewPort(final float[] transformedPts, final View view)395 public void centerViewPort(final float[] transformedPts, final View view) { 396 397 Matrix save = mCenterViewPortMatrixBuffer; 398 save.reset(); 399 save.set(mMatrixTouch); 400 401 final float x = transformedPts[0] - offsetLeft(); 402 final float y = transformedPts[1] - offsetTop(); 403 404 save.postTranslate(-x, -y); 405 406 refresh(save, view, true); 407 } 408 409 /** 410 * buffer for storing the 9 matrix values of a 3x3 matrix 411 */ 412 protected final float[] matrixBuffer = new float[9]; 413 414 /** 415 * call this method to refresh the graph with a given matrix 416 * 417 * @param newMatrix 418 * @return 419 */ refresh(Matrix newMatrix, View chart, boolean invalidate)420 public Matrix refresh(Matrix newMatrix, View chart, boolean invalidate) { 421 422 mMatrixTouch.set(newMatrix); 423 424 // make sure scale and translation are within their bounds 425 limitTransAndScale(mMatrixTouch, mContentRect); 426 427 if (invalidate) 428 chart.invalidate(); 429 430 newMatrix.set(mMatrixTouch); 431 return newMatrix; 432 } 433 434 /** 435 * limits the maximum scale and X translation of the given matrix 436 * 437 * @param matrix 438 */ limitTransAndScale(Matrix matrix, RectF content)439 public void limitTransAndScale(Matrix matrix, RectF content) { 440 441 matrix.getValues(matrixBuffer); 442 443 float curTransX = matrixBuffer[Matrix.MTRANS_X]; 444 float curScaleX = matrixBuffer[Matrix.MSCALE_X]; 445 446 float curTransY = matrixBuffer[Matrix.MTRANS_Y]; 447 float curScaleY = matrixBuffer[Matrix.MSCALE_Y]; 448 449 // min scale-x is 1f 450 mScaleX = Math.min(Math.max(mMinScaleX, curScaleX), mMaxScaleX); 451 452 // min scale-y is 1f 453 mScaleY = Math.min(Math.max(mMinScaleY, curScaleY), mMaxScaleY); 454 455 float width = 0f; 456 float height = 0f; 457 458 if (content != null) { 459 width = content.width(); 460 height = content.height(); 461 } 462 463 float maxTransX = -width * (mScaleX - 1f); 464 mTransX = Math.min(Math.max(curTransX, maxTransX - mTransOffsetX), mTransOffsetX); 465 466 float maxTransY = height * (mScaleY - 1f); 467 mTransY = Math.max(Math.min(curTransY, maxTransY + mTransOffsetY), -mTransOffsetY); 468 469 matrixBuffer[Matrix.MTRANS_X] = mTransX; 470 matrixBuffer[Matrix.MSCALE_X] = mScaleX; 471 472 matrixBuffer[Matrix.MTRANS_Y] = mTransY; 473 matrixBuffer[Matrix.MSCALE_Y] = mScaleY; 474 475 matrix.setValues(matrixBuffer); 476 } 477 478 /** 479 * Sets the minimum scale factor for the x-axis 480 * 481 * @param xScale 482 */ setMinimumScaleX(float xScale)483 public void setMinimumScaleX(float xScale) { 484 485 if (xScale < 1f) 486 xScale = 1f; 487 488 mMinScaleX = xScale; 489 490 limitTransAndScale(mMatrixTouch, mContentRect); 491 } 492 493 /** 494 * Sets the maximum scale factor for the x-axis 495 * 496 * @param xScale 497 */ setMaximumScaleX(float xScale)498 public void setMaximumScaleX(float xScale) { 499 500 if (xScale == 0.f) 501 xScale = Float.MAX_VALUE; 502 503 mMaxScaleX = xScale; 504 505 limitTransAndScale(mMatrixTouch, mContentRect); 506 } 507 508 /** 509 * Sets the minimum and maximum scale factors for the x-axis 510 * 511 * @param minScaleX 512 * @param maxScaleX 513 */ setMinMaxScaleX(float minScaleX, float maxScaleX)514 public void setMinMaxScaleX(float minScaleX, float maxScaleX) { 515 516 if (minScaleX < 1f) 517 minScaleX = 1f; 518 519 if (maxScaleX == 0.f) 520 maxScaleX = Float.MAX_VALUE; 521 522 mMinScaleX = minScaleX; 523 mMaxScaleX = maxScaleX; 524 525 limitTransAndScale(mMatrixTouch, mContentRect); 526 } 527 528 /** 529 * Sets the minimum scale factor for the y-axis 530 * 531 * @param yScale 532 */ setMinimumScaleY(float yScale)533 public void setMinimumScaleY(float yScale) { 534 535 if (yScale < 1f) 536 yScale = 1f; 537 538 mMinScaleY = yScale; 539 540 limitTransAndScale(mMatrixTouch, mContentRect); 541 } 542 543 /** 544 * Sets the maximum scale factor for the y-axis 545 * 546 * @param yScale 547 */ setMaximumScaleY(float yScale)548 public void setMaximumScaleY(float yScale) { 549 550 if (yScale == 0.f) 551 yScale = Float.MAX_VALUE; 552 553 mMaxScaleY = yScale; 554 555 limitTransAndScale(mMatrixTouch, mContentRect); 556 } 557 setMinMaxScaleY(float minScaleY, float maxScaleY)558 public void setMinMaxScaleY(float minScaleY, float maxScaleY) { 559 560 if (minScaleY < 1f) 561 minScaleY = 1f; 562 563 if (maxScaleY == 0.f) 564 maxScaleY = Float.MAX_VALUE; 565 566 mMinScaleY = minScaleY; 567 mMaxScaleY = maxScaleY; 568 569 limitTransAndScale(mMatrixTouch, mContentRect); 570 } 571 572 /** 573 * Returns the charts-touch matrix used for translation and scale on touch. 574 * 575 * @return 576 */ getMatrixTouch()577 public Matrix getMatrixTouch() { 578 return mMatrixTouch; 579 } 580 581 /** 582 * ################ ################ ################ ################ 583 */ 584 /** 585 * BELOW METHODS FOR BOUNDS CHECK 586 */ 587 isInBoundsX(float x)588 public boolean isInBoundsX(float x) { 589 return isInBoundsLeft(x) && isInBoundsRight(x); 590 } 591 isInBoundsY(float y)592 public boolean isInBoundsY(float y) { 593 return isInBoundsTop(y) && isInBoundsBottom(y); 594 } 595 isInBounds(float x, float y)596 public boolean isInBounds(float x, float y) { 597 return isInBoundsX(x) && isInBoundsY(y); 598 } 599 isInBoundsLeft(float x)600 public boolean isInBoundsLeft(float x) { 601 return mContentRect.left <= x + 1; 602 } 603 isInBoundsRight(float x)604 public boolean isInBoundsRight(float x) { 605 x = (float) ((int) (x * 100.f)) / 100.f; 606 return mContentRect.right >= x - 1; 607 } 608 isInBoundsTop(float y)609 public boolean isInBoundsTop(float y) { 610 return mContentRect.top <= y; 611 } 612 isInBoundsBottom(float y)613 public boolean isInBoundsBottom(float y) { 614 y = (float) ((int) (y * 100.f)) / 100.f; 615 return mContentRect.bottom >= y; 616 } 617 618 /** 619 * returns the current x-scale factor 620 */ getScaleX()621 public float getScaleX() { 622 return mScaleX; 623 } 624 625 /** 626 * returns the current y-scale factor 627 */ getScaleY()628 public float getScaleY() { 629 return mScaleY; 630 } 631 getMinScaleX()632 public float getMinScaleX() { 633 return mMinScaleX; 634 } 635 getMaxScaleX()636 public float getMaxScaleX() { 637 return mMaxScaleX; 638 } 639 getMinScaleY()640 public float getMinScaleY() { 641 return mMinScaleY; 642 } 643 getMaxScaleY()644 public float getMaxScaleY() { 645 return mMaxScaleY; 646 } 647 648 /** 649 * Returns the translation (drag / pan) distance on the x-axis 650 * 651 * @return 652 */ getTransX()653 public float getTransX() { 654 return mTransX; 655 } 656 657 /** 658 * Returns the translation (drag / pan) distance on the y-axis 659 * 660 * @return 661 */ getTransY()662 public float getTransY() { 663 return mTransY; 664 } 665 666 /** 667 * if the chart is fully zoomed out, return true 668 * 669 * @return 670 */ isFullyZoomedOut()671 public boolean isFullyZoomedOut() { 672 673 return isFullyZoomedOutX() && isFullyZoomedOutY(); 674 } 675 676 /** 677 * Returns true if the chart is fully zoomed out on it's y-axis (vertical). 678 * 679 * @return 680 */ isFullyZoomedOutY()681 public boolean isFullyZoomedOutY() { 682 return !(mScaleY > mMinScaleY || mMinScaleY > 1f); 683 } 684 685 /** 686 * Returns true if the chart is fully zoomed out on it's x-axis 687 * (horizontal). 688 * 689 * @return 690 */ isFullyZoomedOutX()691 public boolean isFullyZoomedOutX() { 692 return !(mScaleX > mMinScaleX || mMinScaleX > 1f); 693 } 694 695 /** 696 * Set an offset in dp that allows the user to drag the chart over it's 697 * bounds on the x-axis. 698 * 699 * @param offset 700 */ setDragOffsetX(float offset)701 public void setDragOffsetX(float offset) { 702 mTransOffsetX = Utils.convertDpToPixel(offset); 703 } 704 705 /** 706 * Set an offset in dp that allows the user to drag the chart over it's 707 * bounds on the y-axis. 708 * 709 * @param offset 710 */ setDragOffsetY(float offset)711 public void setDragOffsetY(float offset) { 712 mTransOffsetY = Utils.convertDpToPixel(offset); 713 } 714 715 /** 716 * Returns true if both drag offsets (x and y) are zero or smaller. 717 * 718 * @return 719 */ hasNoDragOffset()720 public boolean hasNoDragOffset() { 721 return mTransOffsetX <= 0 && mTransOffsetY <= 0; 722 } 723 724 /** 725 * Returns true if the chart is not yet fully zoomed out on the x-axis 726 * 727 * @return 728 */ canZoomOutMoreX()729 public boolean canZoomOutMoreX() { 730 return mScaleX > mMinScaleX; 731 } 732 733 /** 734 * Returns true if the chart is not yet fully zoomed in on the x-axis 735 * 736 * @return 737 */ canZoomInMoreX()738 public boolean canZoomInMoreX() { 739 return mScaleX < mMaxScaleX; 740 } 741 742 /** 743 * Returns true if the chart is not yet fully zoomed out on the y-axis 744 * 745 * @return 746 */ canZoomOutMoreY()747 public boolean canZoomOutMoreY() { 748 return mScaleY > mMinScaleY; 749 } 750 751 /** 752 * Returns true if the chart is not yet fully zoomed in on the y-axis 753 * 754 * @return 755 */ canZoomInMoreY()756 public boolean canZoomInMoreY() { 757 return mScaleY < mMaxScaleY; 758 } 759 } 760