1 2 package com.github.mikephil.charting.charts; 3 4 import android.animation.ValueAnimator; 5 import android.animation.ValueAnimator.AnimatorUpdateListener; 6 import android.content.ContentValues; 7 import android.content.Context; 8 import android.graphics.Bitmap; 9 import android.graphics.Bitmap.CompressFormat; 10 import android.graphics.Canvas; 11 import android.graphics.Color; 12 import android.graphics.Paint; 13 import android.graphics.Paint.Align; 14 import android.graphics.RectF; 15 import android.graphics.Typeface; 16 import android.graphics.drawable.Drawable; 17 import android.os.Build; 18 import android.os.Environment; 19 import android.provider.MediaStore.Images; 20 import androidx.annotation.RequiresApi; 21 import android.text.TextUtils; 22 import android.util.AttributeSet; 23 import android.util.Log; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.view.ViewParent; 27 28 import com.github.mikephil.charting.animation.ChartAnimator; 29 import com.github.mikephil.charting.animation.Easing; 30 import com.github.mikephil.charting.animation.Easing.EasingFunction; 31 import com.github.mikephil.charting.components.Description; 32 import com.github.mikephil.charting.components.IMarker; 33 import com.github.mikephil.charting.components.Legend; 34 import com.github.mikephil.charting.components.XAxis; 35 import com.github.mikephil.charting.data.ChartData; 36 import com.github.mikephil.charting.data.Entry; 37 import com.github.mikephil.charting.formatter.DefaultValueFormatter; 38 import com.github.mikephil.charting.formatter.IValueFormatter; 39 import com.github.mikephil.charting.highlight.ChartHighlighter; 40 import com.github.mikephil.charting.highlight.Highlight; 41 import com.github.mikephil.charting.highlight.IHighlighter; 42 import com.github.mikephil.charting.interfaces.dataprovider.ChartInterface; 43 import com.github.mikephil.charting.interfaces.datasets.IDataSet; 44 import com.github.mikephil.charting.listener.ChartTouchListener; 45 import com.github.mikephil.charting.listener.OnChartGestureListener; 46 import com.github.mikephil.charting.listener.OnChartValueSelectedListener; 47 import com.github.mikephil.charting.renderer.DataRenderer; 48 import com.github.mikephil.charting.renderer.LegendRenderer; 49 import com.github.mikephil.charting.utils.MPPointF; 50 import com.github.mikephil.charting.utils.Utils; 51 import com.github.mikephil.charting.utils.ViewPortHandler; 52 53 import java.io.File; 54 import java.io.FileOutputStream; 55 import java.io.IOException; 56 import java.io.OutputStream; 57 import java.util.ArrayList; 58 59 /** 60 * Baseclass of all Chart-Views. 61 * 62 * @author Philipp Jahoda 63 */ 64 public abstract class Chart<T extends ChartData<? extends IDataSet<? extends Entry>>> extends 65 ViewGroup 66 implements ChartInterface { 67 68 public static final String LOG_TAG = "MPAndroidChart"; 69 70 /** 71 * flag that indicates if logging is enabled or not 72 */ 73 protected boolean mLogEnabled = false; 74 75 /** 76 * object that holds all data that was originally set for the chart, before 77 * it was modified or any filtering algorithms had been applied 78 */ 79 protected T mData = null; 80 81 /** 82 * Flag that indicates if highlighting per tap (touch) is enabled 83 */ 84 protected boolean mHighLightPerTapEnabled = true; 85 86 /** 87 * If set to true, chart continues to scroll after touch up 88 */ 89 private boolean mDragDecelerationEnabled = true; 90 91 /** 92 * Deceleration friction coefficient in [0 ; 1] interval, higher values 93 * indicate that speed will decrease slowly, for example if it set to 0, it 94 * will stop immediately. 1 is an invalid value, and will be converted to 95 * 0.999f automatically. 96 */ 97 private float mDragDecelerationFrictionCoef = 0.9f; 98 99 /** 100 * default value-formatter, number of digits depends on provided chart-data 101 */ 102 protected DefaultValueFormatter mDefaultValueFormatter = new DefaultValueFormatter(0); 103 104 /** 105 * paint object used for drawing the description text in the bottom right 106 * corner of the chart 107 */ 108 protected Paint mDescPaint; 109 110 /** 111 * paint object for drawing the information text when there are no values in 112 * the chart 113 */ 114 protected Paint mInfoPaint; 115 116 /** 117 * the object representing the labels on the x-axis 118 */ 119 protected XAxis mXAxis; 120 121 /** 122 * if true, touch gestures are enabled on the chart 123 */ 124 protected boolean mTouchEnabled = true; 125 126 /** 127 * the object responsible for representing the description text 128 */ 129 protected Description mDescription; 130 131 /** 132 * the legend object containing all data associated with the legend 133 */ 134 protected Legend mLegend; 135 136 /** 137 * listener that is called when a value on the chart is selected 138 */ 139 protected OnChartValueSelectedListener mSelectionListener; 140 141 protected ChartTouchListener mChartTouchListener; 142 143 /** 144 * text that is displayed when the chart is empty 145 */ 146 private String mNoDataText = "No chart data available."; 147 148 /** 149 * Gesture listener for custom callbacks when making gestures on the chart. 150 */ 151 private OnChartGestureListener mGestureListener; 152 153 protected LegendRenderer mLegendRenderer; 154 155 /** 156 * object responsible for rendering the data 157 */ 158 protected DataRenderer mRenderer; 159 160 protected IHighlighter mHighlighter; 161 162 /** 163 * object that manages the bounds and drawing constraints of the chart 164 */ 165 protected ViewPortHandler mViewPortHandler = new ViewPortHandler(); 166 167 /** 168 * object responsible for animations 169 */ 170 protected ChartAnimator mAnimator; 171 172 /** 173 * Extra offsets to be appended to the viewport 174 */ 175 private float mExtraTopOffset = 0.f, 176 mExtraRightOffset = 0.f, 177 mExtraBottomOffset = 0.f, 178 mExtraLeftOffset = 0.f; 179 180 /** 181 * default constructor for initialization in code 182 */ Chart(Context context)183 public Chart(Context context) { 184 super(context); 185 init(); 186 } 187 188 /** 189 * constructor for initialization in xml 190 */ Chart(Context context, AttributeSet attrs)191 public Chart(Context context, AttributeSet attrs) { 192 super(context, attrs); 193 init(); 194 } 195 196 /** 197 * even more awesome constructor 198 */ Chart(Context context, AttributeSet attrs, int defStyle)199 public Chart(Context context, AttributeSet attrs, int defStyle) { 200 super(context, attrs, defStyle); 201 init(); 202 } 203 204 /** 205 * initialize all paints and stuff 206 */ init()207 protected void init() { 208 209 setWillNotDraw(false); 210 // setLayerType(View.LAYER_TYPE_HARDWARE, null); 211 212 mAnimator = new ChartAnimator(new AnimatorUpdateListener() { 213 214 @Override 215 public void onAnimationUpdate(ValueAnimator animation) { 216 // ViewCompat.postInvalidateOnAnimation(Chart.this); 217 postInvalidate(); 218 } 219 }); 220 221 // initialize the utils 222 Utils.init(getContext()); 223 mMaxHighlightDistance = Utils.convertDpToPixel(500f); 224 225 mDescription = new Description(); 226 mLegend = new Legend(); 227 228 mLegendRenderer = new LegendRenderer(mViewPortHandler, mLegend); 229 230 mXAxis = new XAxis(); 231 232 mDescPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 233 234 mInfoPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 235 mInfoPaint.setColor(Color.rgb(247, 189, 51)); // orange 236 mInfoPaint.setTextAlign(Align.CENTER); 237 mInfoPaint.setTextSize(Utils.convertDpToPixel(12f)); 238 239 if (mLogEnabled) 240 Log.i("", "Chart.init()"); 241 } 242 243 // public void initWithDummyData() { 244 // ColorTemplate template = new ColorTemplate(); 245 // template.addColorsForDataSets(ColorTemplate.COLORFUL_COLORS, 246 // getContext()); 247 // 248 // setColorTemplate(template); 249 // setDrawYValues(false); 250 // 251 // ArrayList<String> xVals = new ArrayList<String>(); 252 // Calendar calendar = Calendar.getInstance(); 253 // for (int i = 0; i < 12; i++) { 254 // xVals.add(calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, 255 // Locale.getDefault())); 256 // } 257 // 258 // ArrayList<DataSet> dataSets = new ArrayList<DataSet>(); 259 // for (int i = 0; i < 3; i++) { 260 // 261 // ArrayList<Entry> yVals = new ArrayList<Entry>(); 262 // 263 // for (int j = 0; j < 12; j++) { 264 // float val = (float) (Math.random() * 100); 265 // yVals.add(new Entry(val, j)); 266 // } 267 // 268 // DataSet set = new DataSet(yVals, "DataSet " + i); 269 // dataSets.add(set); // add the datasets 270 // } 271 // // create a data object with the datasets 272 // ChartData data = new ChartData(xVals, dataSets); 273 // setData(data); 274 // invalidate(); 275 // } 276 277 /** 278 * Sets a new data object for the chart. The data object contains all values 279 * and information needed for displaying. 280 * 281 * @param data 282 */ setData(T data)283 public void setData(T data) { 284 285 mData = data; 286 mOffsetsCalculated = false; 287 288 if (data == null) { 289 return; 290 } 291 292 // calculate how many digits are needed 293 setupDefaultFormatter(data.getYMin(), data.getYMax()); 294 295 for (IDataSet set : mData.getDataSets()) { 296 if (set.needsFormatter() || set.getValueFormatter() == mDefaultValueFormatter) 297 set.setValueFormatter(mDefaultValueFormatter); 298 } 299 300 // let the chart know there is new data 301 notifyDataSetChanged(); 302 303 if (mLogEnabled) 304 Log.i(LOG_TAG, "Data is set."); 305 } 306 307 /** 308 * Clears the chart from all data (sets it to null) and refreshes it (by 309 * calling invalidate()). 310 */ clear()311 public void clear() { 312 mData = null; 313 mOffsetsCalculated = false; 314 mIndicesToHighlight = null; 315 mChartTouchListener.setLastHighlighted(null); 316 invalidate(); 317 } 318 319 /** 320 * Removes all DataSets (and thereby Entries) from the chart. Does not set the data object to null. Also refreshes the 321 * chart by calling invalidate(). 322 */ clearValues()323 public void clearValues() { 324 mData.clearValues(); 325 invalidate(); 326 } 327 328 /** 329 * Returns true if the chart is empty (meaning it's data object is either 330 * null or contains no entries). 331 * 332 * @return 333 */ isEmpty()334 public boolean isEmpty() { 335 336 if (mData == null) 337 return true; 338 else { 339 340 if (mData.getEntryCount() <= 0) 341 return true; 342 else 343 return false; 344 } 345 } 346 347 /** 348 * Lets the chart know its underlying data has changed and performs all 349 * necessary recalculations. It is crucial that this method is called 350 * everytime data is changed dynamically. Not calling this method can lead 351 * to crashes or unexpected behaviour. 352 */ notifyDataSetChanged()353 public abstract void notifyDataSetChanged(); 354 355 /** 356 * Calculates the offsets of the chart to the border depending on the 357 * position of an eventual legend or depending on the length of the y-axis 358 * and x-axis labels and their position 359 */ calculateOffsets()360 protected abstract void calculateOffsets(); 361 362 /** 363 * Calculates the y-min and y-max value and the y-delta and x-delta value 364 */ calcMinMax()365 protected abstract void calcMinMax(); 366 367 /** 368 * Calculates the required number of digits for the values that might be 369 * drawn in the chart (if enabled), and creates the default-value-formatter 370 */ setupDefaultFormatter(float min, float max)371 protected void setupDefaultFormatter(float min, float max) { 372 373 float reference = 0f; 374 375 if (mData == null || mData.getEntryCount() < 2) { 376 377 reference = Math.max(Math.abs(min), Math.abs(max)); 378 } else { 379 reference = Math.abs(max - min); 380 } 381 382 int digits = Utils.getDecimals(reference); 383 384 // setup the formatter with a new number of digits 385 mDefaultValueFormatter.setup(digits); 386 } 387 388 /** 389 * flag that indicates if offsets calculation has already been done or not 390 */ 391 private boolean mOffsetsCalculated = false; 392 393 @Override onDraw(Canvas canvas)394 protected void onDraw(Canvas canvas) { 395 // super.onDraw(canvas); 396 397 if (mData == null) { 398 399 boolean hasText = !TextUtils.isEmpty(mNoDataText); 400 401 if (hasText) { 402 MPPointF pt = getCenter(); 403 404 switch (mInfoPaint.getTextAlign()) { 405 case LEFT: 406 pt.x = 0; 407 canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint); 408 break; 409 410 case RIGHT: 411 pt.x *= 2.0; 412 canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint); 413 break; 414 415 default: 416 canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint); 417 break; 418 } 419 } 420 421 return; 422 } 423 424 if (!mOffsetsCalculated) { 425 426 calculateOffsets(); 427 mOffsetsCalculated = true; 428 } 429 } 430 431 /** 432 * Draws the description text in the bottom right corner of the chart (per default) 433 */ drawDescription(Canvas c)434 protected void drawDescription(Canvas c) { 435 436 // check if description should be drawn 437 if (mDescription != null && mDescription.isEnabled()) { 438 439 MPPointF position = mDescription.getPosition(); 440 441 mDescPaint.setTypeface(mDescription.getTypeface()); 442 mDescPaint.setTextSize(mDescription.getTextSize()); 443 mDescPaint.setColor(mDescription.getTextColor()); 444 mDescPaint.setTextAlign(mDescription.getTextAlign()); 445 446 float x, y; 447 448 // if no position specified, draw on default position 449 if (position == null) { 450 x = getWidth() - mViewPortHandler.offsetRight() - mDescription.getXOffset(); 451 y = getHeight() - mViewPortHandler.offsetBottom() - mDescription.getYOffset(); 452 } else { 453 x = position.x; 454 y = position.y; 455 } 456 457 c.drawText(mDescription.getText(), x, y, mDescPaint); 458 } 459 } 460 461 /** 462 * ################ ################ ################ ################ 463 */ 464 /** BELOW THIS CODE FOR HIGHLIGHTING */ 465 466 /** 467 * array of Highlight objects that reference the highlighted slices in the 468 * chart 469 */ 470 protected Highlight[] mIndicesToHighlight; 471 472 /** 473 * The maximum distance in dp away from an entry causing it to highlight. 474 */ 475 protected float mMaxHighlightDistance = 0f; 476 477 @Override getMaxHighlightDistance()478 public float getMaxHighlightDistance() { 479 return mMaxHighlightDistance; 480 } 481 482 /** 483 * Sets the maximum distance in screen dp a touch can be away from an entry to cause it to get highlighted. 484 * Default: 500dp 485 * 486 * @param distDp 487 */ setMaxHighlightDistance(float distDp)488 public void setMaxHighlightDistance(float distDp) { 489 mMaxHighlightDistance = Utils.convertDpToPixel(distDp); 490 } 491 492 /** 493 * Returns the array of currently highlighted values. This might a null or 494 * empty array if nothing is highlighted. 495 * 496 * @return 497 */ getHighlighted()498 public Highlight[] getHighlighted() { 499 return mIndicesToHighlight; 500 } 501 502 /** 503 * Returns true if values can be highlighted via tap gesture, false if not. 504 * 505 * @return 506 */ isHighlightPerTapEnabled()507 public boolean isHighlightPerTapEnabled() { 508 return mHighLightPerTapEnabled; 509 } 510 511 /** 512 * Set this to false to prevent values from being highlighted by tap gesture. 513 * Values can still be highlighted via drag or programmatically. Default: true 514 * 515 * @param enabled 516 */ setHighlightPerTapEnabled(boolean enabled)517 public void setHighlightPerTapEnabled(boolean enabled) { 518 mHighLightPerTapEnabled = enabled; 519 } 520 521 /** 522 * Returns true if there are values to highlight, false if there are no 523 * values to highlight. Checks if the highlight array is null, has a length 524 * of zero or if the first object is null. 525 * 526 * @return 527 */ valuesToHighlight()528 public boolean valuesToHighlight() { 529 return mIndicesToHighlight == null || mIndicesToHighlight.length <= 0 530 || mIndicesToHighlight[0] == null ? false 531 : true; 532 } 533 534 /** 535 * Sets the last highlighted value for the touchlistener. 536 * 537 * @param highs 538 */ setLastHighlighted(Highlight[] highs)539 protected void setLastHighlighted(Highlight[] highs) { 540 541 if (highs == null || highs.length <= 0 || highs[0] == null) { 542 mChartTouchListener.setLastHighlighted(null); 543 } else { 544 mChartTouchListener.setLastHighlighted(highs[0]); 545 } 546 } 547 548 /** 549 * Highlights the values at the given indices in the given DataSets. Provide 550 * null or an empty array to undo all highlighting. This should be used to 551 * programmatically highlight values. 552 * This method *will not* call the listener. 553 * 554 * @param highs 555 */ highlightValues(Highlight[] highs)556 public void highlightValues(Highlight[] highs) { 557 558 // set the indices to highlight 559 mIndicesToHighlight = highs; 560 561 setLastHighlighted(highs); 562 563 // redraw the chart 564 invalidate(); 565 } 566 567 /** 568 * Highlights any y-value at the given x-value in the given DataSet. 569 * Provide -1 as the dataSetIndex to undo all highlighting. 570 * This method will call the listener. 571 * @param x The x-value to highlight 572 * @param dataSetIndex The dataset index to search in 573 * @param dataIndex The data index to search in (only used in CombinedChartView currently) 574 */ highlightValue(float x, int dataSetIndex, int dataIndex)575 public void highlightValue(float x, int dataSetIndex, int dataIndex) { 576 highlightValue(x, dataSetIndex, dataIndex, true); 577 } 578 579 /** 580 * Highlights any y-value at the given x-value in the given DataSet. 581 * Provide -1 as the dataSetIndex to undo all highlighting. 582 * This method will call the listener. 583 * @param x The x-value to highlight 584 * @param dataSetIndex The dataset index to search in 585 */ highlightValue(float x, int dataSetIndex)586 public void highlightValue(float x, int dataSetIndex) { 587 highlightValue(x, dataSetIndex, -1, true); 588 } 589 590 /** 591 * Highlights the value at the given x-value and y-value in the given DataSet. 592 * Provide -1 as the dataSetIndex to undo all highlighting. 593 * This method will call the listener. 594 * @param x The x-value to highlight 595 * @param y The y-value to highlight. Supply `NaN` for "any" 596 * @param dataSetIndex The dataset index to search in 597 * @param dataIndex The data index to search in (only used in CombinedChartView currently) 598 */ highlightValue(float x, float y, int dataSetIndex, int dataIndex)599 public void highlightValue(float x, float y, int dataSetIndex, int dataIndex) { 600 highlightValue(x, y, dataSetIndex, dataIndex, true); 601 } 602 603 /** 604 * Highlights the value at the given x-value and y-value in the given DataSet. 605 * Provide -1 as the dataSetIndex to undo all highlighting. 606 * This method will call the listener. 607 * @param x The x-value to highlight 608 * @param y The y-value to highlight. Supply `NaN` for "any" 609 * @param dataSetIndex The dataset index to search in 610 */ highlightValue(float x, float y, int dataSetIndex)611 public void highlightValue(float x, float y, int dataSetIndex) { 612 highlightValue(x, y, dataSetIndex, -1, true); 613 } 614 615 /** 616 * Highlights any y-value at the given x-value in the given DataSet. 617 * Provide -1 as the dataSetIndex to undo all highlighting. 618 * @param x The x-value to highlight 619 * @param dataSetIndex The dataset index to search in 620 * @param dataIndex The data index to search in (only used in CombinedChartView currently) 621 * @param callListener Should the listener be called for this change 622 */ highlightValue(float x, int dataSetIndex, int dataIndex, boolean callListener)623 public void highlightValue(float x, int dataSetIndex, int dataIndex, boolean callListener) { 624 highlightValue(x, Float.NaN, dataSetIndex, dataIndex, callListener); 625 } 626 627 /** 628 * Highlights any y-value at the given x-value in the given DataSet. 629 * Provide -1 as the dataSetIndex to undo all highlighting. 630 * @param x The x-value to highlight 631 * @param dataSetIndex The dataset index to search in 632 * @param callListener Should the listener be called for this change 633 */ highlightValue(float x, int dataSetIndex, boolean callListener)634 public void highlightValue(float x, int dataSetIndex, boolean callListener) { 635 highlightValue(x, Float.NaN, dataSetIndex, -1, callListener); 636 } 637 638 /** 639 * Highlights any y-value at the given x-value in the given DataSet. 640 * Provide -1 as the dataSetIndex to undo all highlighting. 641 * @param x The x-value to highlight 642 * @param y The y-value to highlight. Supply `NaN` for "any" 643 * @param dataSetIndex The dataset index to search in 644 * @param dataIndex The data index to search in (only used in CombinedChartView currently) 645 * @param callListener Should the listener be called for this change 646 */ highlightValue(float x, float y, int dataSetIndex, int dataIndex, boolean callListener)647 public void highlightValue(float x, float y, int dataSetIndex, int dataIndex, boolean callListener) { 648 649 if (dataSetIndex < 0 || dataSetIndex >= mData.getDataSetCount()) { 650 highlightValue(null, callListener); 651 } else { 652 highlightValue(new Highlight(x, y, dataSetIndex, dataIndex), callListener); 653 } 654 } 655 656 /** 657 * Highlights any y-value at the given x-value in the given DataSet. 658 * Provide -1 as the dataSetIndex to undo all highlighting. 659 * @param x The x-value to highlight 660 * @param y The y-value to highlight. Supply `NaN` for "any" 661 * @param dataSetIndex The dataset index to search in 662 * @param callListener Should the listener be called for this change 663 */ highlightValue(float x, float y, int dataSetIndex, boolean callListener)664 public void highlightValue(float x, float y, int dataSetIndex, boolean callListener) { 665 highlightValue(x, y, dataSetIndex, -1, callListener); 666 } 667 668 /** 669 * Highlights the values represented by the provided Highlight object 670 * This method *will not* call the listener. 671 * 672 * @param highlight contains information about which entry should be highlighted 673 */ highlightValue(Highlight highlight)674 public void highlightValue(Highlight highlight) { 675 highlightValue(highlight, false); 676 } 677 678 /** 679 * Highlights the value selected by touch gesture. Unlike 680 * highlightValues(...), this generates a callback to the 681 * OnChartValueSelectedListener. 682 * 683 * @param high - the highlight object 684 * @param callListener - call the listener 685 */ highlightValue(Highlight high, boolean callListener)686 public void highlightValue(Highlight high, boolean callListener) { 687 688 Entry e = null; 689 690 if (high == null) 691 mIndicesToHighlight = null; 692 else { 693 694 if (mLogEnabled) 695 Log.i(LOG_TAG, "Highlighted: " + high.toString()); 696 697 e = mData.getEntryForHighlight(high); 698 if (e == null) { 699 mIndicesToHighlight = null; 700 high = null; 701 } else { 702 703 // set the indices to highlight 704 mIndicesToHighlight = new Highlight[]{ 705 high 706 }; 707 } 708 } 709 710 setLastHighlighted(mIndicesToHighlight); 711 712 if (callListener && mSelectionListener != null) { 713 714 if (!valuesToHighlight()) 715 mSelectionListener.onNothingSelected(); 716 else { 717 // notify the listener 718 mSelectionListener.onValueSelected(e, high); 719 } 720 } 721 722 // redraw the chart 723 invalidate(); 724 } 725 726 /** 727 * Returns the Highlight object (contains x-index and DataSet index) of the 728 * selected value at the given touch point inside the Line-, Scatter-, or 729 * CandleStick-Chart. 730 * 731 * @param x 732 * @param y 733 * @return 734 */ getHighlightByTouchPoint(float x, float y)735 public Highlight getHighlightByTouchPoint(float x, float y) { 736 737 if (mData == null) { 738 Log.e(LOG_TAG, "Can't select by touch. No data set."); 739 return null; 740 } else 741 return getHighlighter().getHighlight(x, y); 742 } 743 744 /** 745 * Set a new (e.g. custom) ChartTouchListener NOTE: make sure to 746 * setTouchEnabled(true); if you need touch gestures on the chart 747 * 748 * @param l 749 */ setOnTouchListener(ChartTouchListener l)750 public void setOnTouchListener(ChartTouchListener l) { 751 this.mChartTouchListener = l; 752 } 753 754 /** 755 * Returns an instance of the currently active touch listener. 756 * 757 * @return 758 */ getOnTouchListener()759 public ChartTouchListener getOnTouchListener() { 760 return mChartTouchListener; 761 } 762 763 /** 764 * ################ ################ ################ ################ 765 */ 766 /** BELOW CODE IS FOR THE MARKER VIEW */ 767 768 /** 769 * if set to true, the marker view is drawn when a value is clicked 770 */ 771 protected boolean mDrawMarkers = true; 772 773 /** 774 * the view that represents the marker 775 */ 776 protected IMarker mMarker; 777 778 /** 779 * draws all MarkerViews on the highlighted positions 780 */ drawMarkers(Canvas canvas)781 protected void drawMarkers(Canvas canvas) { 782 783 // if there is no marker view or drawing marker is disabled 784 if (mMarker == null || !isDrawMarkersEnabled() || !valuesToHighlight()) 785 return; 786 787 for (int i = 0; i < mIndicesToHighlight.length; i++) { 788 789 Highlight highlight = mIndicesToHighlight[i]; 790 791 IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex()); 792 793 Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]); 794 int entryIndex = set.getEntryIndex(e); 795 796 // make sure entry not null 797 if (e == null || entryIndex > set.getEntryCount() * mAnimator.getPhaseX()) 798 continue; 799 800 float[] pos = getMarkerPosition(highlight); 801 802 // check bounds 803 if (!mViewPortHandler.isInBounds(pos[0], pos[1])) 804 continue; 805 806 // callbacks to update the content 807 mMarker.refreshContent(e, highlight); 808 809 // draw the marker 810 mMarker.draw(canvas, pos[0], pos[1]); 811 } 812 } 813 814 /** 815 * Returns the actual position in pixels of the MarkerView for the given 816 * Highlight object. 817 * 818 * @param high 819 * @return 820 */ getMarkerPosition(Highlight high)821 protected float[] getMarkerPosition(Highlight high) { 822 return new float[]{high.getDrawX(), high.getDrawY()}; 823 } 824 825 /** 826 * ################ ################ ################ ################ 827 * ANIMATIONS ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 828 */ 829 /** CODE BELOW THIS RELATED TO ANIMATION */ 830 831 /** 832 * Returns the animator responsible for animating chart values. 833 * 834 * @return 835 */ getAnimator()836 public ChartAnimator getAnimator() { 837 return mAnimator; 838 } 839 840 /** 841 * If set to true, chart continues to scroll after touch up default: true 842 */ isDragDecelerationEnabled()843 public boolean isDragDecelerationEnabled() { 844 return mDragDecelerationEnabled; 845 } 846 847 /** 848 * If set to true, chart continues to scroll after touch up. Default: true. 849 * 850 * @param enabled 851 */ setDragDecelerationEnabled(boolean enabled)852 public void setDragDecelerationEnabled(boolean enabled) { 853 mDragDecelerationEnabled = enabled; 854 } 855 856 /** 857 * Returns drag deceleration friction coefficient 858 * 859 * @return 860 */ getDragDecelerationFrictionCoef()861 public float getDragDecelerationFrictionCoef() { 862 return mDragDecelerationFrictionCoef; 863 } 864 865 /** 866 * Deceleration friction coefficient in [0 ; 1] interval, higher values 867 * indicate that speed will decrease slowly, for example if it set to 0, it 868 * will stop immediately. 1 is an invalid value, and will be converted to 869 * 0.999f automatically. 870 * 871 * @param newValue 872 */ setDragDecelerationFrictionCoef(float newValue)873 public void setDragDecelerationFrictionCoef(float newValue) { 874 875 if (newValue < 0.f) 876 newValue = 0.f; 877 878 if (newValue >= 1f) 879 newValue = 0.999f; 880 881 mDragDecelerationFrictionCoef = newValue; 882 } 883 884 /** 885 * ################ ################ ################ ################ 886 * ANIMATIONS ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 887 */ 888 /** CODE BELOW FOR PROVIDING EASING FUNCTIONS */ 889 890 /** 891 * Animates the drawing / rendering of the chart on both x- and y-axis with 892 * the specified animation time. If animate(...) is called, no further 893 * calling of invalidate() is necessary to refresh the chart. ANIMATIONS 894 * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 895 * 896 * @param durationMillisX 897 * @param durationMillisY 898 * @param easingX a custom easing function to be used on the animation phase 899 * @param easingY a custom easing function to be used on the animation phase 900 */ 901 @RequiresApi(11) animateXY(int durationMillisX, int durationMillisY, EasingFunction easingX, EasingFunction easingY)902 public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easingX, 903 EasingFunction easingY) { 904 mAnimator.animateXY(durationMillisX, durationMillisY, easingX, easingY); 905 } 906 907 /** 908 * Animates the drawing / rendering of the chart on both x- and y-axis with 909 * the specified animation time. If animate(...) is called, no further 910 * calling of invalidate() is necessary to refresh the chart. ANIMATIONS 911 * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 912 * 913 * @param durationMillisX 914 * @param durationMillisY 915 * @param easing a custom easing function to be used on the animation phase 916 */ 917 @RequiresApi(11) animateXY(int durationMillisX, int durationMillisY, EasingFunction easing)918 public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easing) { 919 mAnimator.animateXY(durationMillisX, durationMillisY, easing); 920 } 921 922 /** 923 * Animates the rendering of the chart on the x-axis with the specified 924 * animation time. If animate(...) is called, no further calling of 925 * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR 926 * API LEVEL 11 (Android 3.0.x) AND HIGHER. 927 * 928 * @param durationMillis 929 * @param easing a custom easing function to be used on the animation phase 930 */ 931 @RequiresApi(11) animateX(int durationMillis, EasingFunction easing)932 public void animateX(int durationMillis, EasingFunction easing) { 933 mAnimator.animateX(durationMillis, easing); 934 } 935 936 /** 937 * Animates the rendering of the chart on the y-axis with the specified 938 * animation time. If animate(...) is called, no further calling of 939 * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR 940 * API LEVEL 11 (Android 3.0.x) AND HIGHER. 941 * 942 * @param durationMillis 943 * @param easing a custom easing function to be used on the animation phase 944 */ 945 @RequiresApi(11) animateY(int durationMillis, EasingFunction easing)946 public void animateY(int durationMillis, EasingFunction easing) { 947 mAnimator.animateY(durationMillis, easing); 948 } 949 950 /** 951 * ################ ################ ################ ################ 952 * ANIMATIONS ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 953 */ 954 /** CODE BELOW FOR PREDEFINED EASING OPTIONS */ 955 956 /** 957 * ################ ################ ################ ################ 958 * ANIMATIONS ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 959 */ 960 /** CODE BELOW FOR ANIMATIONS WITHOUT EASING */ 961 962 /** 963 * Animates the rendering of the chart on the x-axis with the specified 964 * animation time. If animate(...) is called, no further calling of 965 * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR 966 * API LEVEL 11 (Android 3.0.x) AND HIGHER. 967 * 968 * @param durationMillis 969 */ 970 @RequiresApi(11) animateX(int durationMillis)971 public void animateX(int durationMillis) { 972 mAnimator.animateX(durationMillis); 973 } 974 975 /** 976 * Animates the rendering of the chart on the y-axis with the specified 977 * animation time. If animate(...) is called, no further calling of 978 * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR 979 * API LEVEL 11 (Android 3.0.x) AND HIGHER. 980 * 981 * @param durationMillis 982 */ 983 @RequiresApi(11) animateY(int durationMillis)984 public void animateY(int durationMillis) { 985 mAnimator.animateY(durationMillis); 986 } 987 988 /** 989 * Animates the drawing / rendering of the chart on both x- and y-axis with 990 * the specified animation time. If animate(...) is called, no further 991 * calling of invalidate() is necessary to refresh the chart. ANIMATIONS 992 * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. 993 * 994 * @param durationMillisX 995 * @param durationMillisY 996 */ 997 @RequiresApi(11) animateXY(int durationMillisX, int durationMillisY)998 public void animateXY(int durationMillisX, int durationMillisY) { 999 mAnimator.animateXY(durationMillisX, durationMillisY); 1000 } 1001 1002 /** 1003 * ################ ################ ################ ################ 1004 */ 1005 /** BELOW THIS ONLY GETTERS AND SETTERS */ 1006 1007 1008 /** 1009 * Returns the object representing all x-labels, this method can be used to 1010 * acquire the XAxis object and modify it (e.g. change the position of the 1011 * labels, styling, etc.) 1012 * 1013 * @return 1014 */ getXAxis()1015 public XAxis getXAxis() { 1016 return mXAxis; 1017 } 1018 1019 /** 1020 * Returns the default IValueFormatter that has been determined by the chart 1021 * considering the provided minimum and maximum values. 1022 * 1023 * @return 1024 */ getDefaultValueFormatter()1025 public IValueFormatter getDefaultValueFormatter() { 1026 return mDefaultValueFormatter; 1027 } 1028 1029 /** 1030 * set a selection listener for the chart 1031 * 1032 * @param l 1033 */ setOnChartValueSelectedListener(OnChartValueSelectedListener l)1034 public void setOnChartValueSelectedListener(OnChartValueSelectedListener l) { 1035 this.mSelectionListener = l; 1036 } 1037 1038 /** 1039 * Sets a gesture-listener for the chart for custom callbacks when executing 1040 * gestures on the chart surface. 1041 * 1042 * @param l 1043 */ setOnChartGestureListener(OnChartGestureListener l)1044 public void setOnChartGestureListener(OnChartGestureListener l) { 1045 this.mGestureListener = l; 1046 } 1047 1048 /** 1049 * Returns the custom gesture listener. 1050 * 1051 * @return 1052 */ getOnChartGestureListener()1053 public OnChartGestureListener getOnChartGestureListener() { 1054 return mGestureListener; 1055 } 1056 1057 /** 1058 * returns the current y-max value across all DataSets 1059 * 1060 * @return 1061 */ getYMax()1062 public float getYMax() { 1063 return mData.getYMax(); 1064 } 1065 1066 /** 1067 * returns the current y-min value across all DataSets 1068 * 1069 * @return 1070 */ getYMin()1071 public float getYMin() { 1072 return mData.getYMin(); 1073 } 1074 1075 @Override getXChartMax()1076 public float getXChartMax() { 1077 return mXAxis.mAxisMaximum; 1078 } 1079 1080 @Override getXChartMin()1081 public float getXChartMin() { 1082 return mXAxis.mAxisMinimum; 1083 } 1084 1085 @Override getXRange()1086 public float getXRange() { 1087 return mXAxis.mAxisRange; 1088 } 1089 1090 /** 1091 * Returns a recyclable MPPointF instance. 1092 * Returns the center point of the chart (the whole View) in pixels. 1093 * 1094 * @return 1095 */ getCenter()1096 public MPPointF getCenter() { 1097 return MPPointF.getInstance(getWidth() / 2f, getHeight() / 2f); 1098 } 1099 1100 /** 1101 * Returns a recyclable MPPointF instance. 1102 * Returns the center of the chart taking offsets under consideration. 1103 * (returns the center of the content rectangle) 1104 * 1105 * @return 1106 */ 1107 @Override getCenterOffsets()1108 public MPPointF getCenterOffsets() { 1109 return mViewPortHandler.getContentCenter(); 1110 } 1111 1112 /** 1113 * Sets extra offsets (around the chart view) to be appended to the 1114 * auto-calculated offsets. 1115 * 1116 * @param left 1117 * @param top 1118 * @param right 1119 * @param bottom 1120 */ setExtraOffsets(float left, float top, float right, float bottom)1121 public void setExtraOffsets(float left, float top, float right, float bottom) { 1122 setExtraLeftOffset(left); 1123 setExtraTopOffset(top); 1124 setExtraRightOffset(right); 1125 setExtraBottomOffset(bottom); 1126 } 1127 1128 /** 1129 * Set an extra offset to be appended to the viewport's top 1130 */ setExtraTopOffset(float offset)1131 public void setExtraTopOffset(float offset) { 1132 mExtraTopOffset = Utils.convertDpToPixel(offset); 1133 } 1134 1135 /** 1136 * @return the extra offset to be appended to the viewport's top 1137 */ getExtraTopOffset()1138 public float getExtraTopOffset() { 1139 return mExtraTopOffset; 1140 } 1141 1142 /** 1143 * Set an extra offset to be appended to the viewport's right 1144 */ setExtraRightOffset(float offset)1145 public void setExtraRightOffset(float offset) { 1146 mExtraRightOffset = Utils.convertDpToPixel(offset); 1147 } 1148 1149 /** 1150 * @return the extra offset to be appended to the viewport's right 1151 */ getExtraRightOffset()1152 public float getExtraRightOffset() { 1153 return mExtraRightOffset; 1154 } 1155 1156 /** 1157 * Set an extra offset to be appended to the viewport's bottom 1158 */ setExtraBottomOffset(float offset)1159 public void setExtraBottomOffset(float offset) { 1160 mExtraBottomOffset = Utils.convertDpToPixel(offset); 1161 } 1162 1163 /** 1164 * @return the extra offset to be appended to the viewport's bottom 1165 */ getExtraBottomOffset()1166 public float getExtraBottomOffset() { 1167 return mExtraBottomOffset; 1168 } 1169 1170 /** 1171 * Set an extra offset to be appended to the viewport's left 1172 */ setExtraLeftOffset(float offset)1173 public void setExtraLeftOffset(float offset) { 1174 mExtraLeftOffset = Utils.convertDpToPixel(offset); 1175 } 1176 1177 /** 1178 * @return the extra offset to be appended to the viewport's left 1179 */ getExtraLeftOffset()1180 public float getExtraLeftOffset() { 1181 return mExtraLeftOffset; 1182 } 1183 1184 /** 1185 * Set this to true to enable logcat outputs for the chart. Beware that 1186 * logcat output decreases rendering performance. Default: disabled. 1187 * 1188 * @param enabled 1189 */ setLogEnabled(boolean enabled)1190 public void setLogEnabled(boolean enabled) { 1191 mLogEnabled = enabled; 1192 } 1193 1194 /** 1195 * Returns true if log-output is enabled for the chart, fals if not. 1196 * 1197 * @return 1198 */ isLogEnabled()1199 public boolean isLogEnabled() { 1200 return mLogEnabled; 1201 } 1202 1203 /** 1204 * Sets the text that informs the user that there is no data available with 1205 * which to draw the chart. 1206 * 1207 * @param text 1208 */ setNoDataText(String text)1209 public void setNoDataText(String text) { 1210 mNoDataText = text; 1211 } 1212 1213 /** 1214 * Sets the color of the no data text. 1215 * 1216 * @param color 1217 */ setNoDataTextColor(int color)1218 public void setNoDataTextColor(int color) { 1219 mInfoPaint.setColor(color); 1220 } 1221 1222 /** 1223 * Sets the typeface to be used for the no data text. 1224 * 1225 * @param tf 1226 */ setNoDataTextTypeface(Typeface tf)1227 public void setNoDataTextTypeface(Typeface tf) { 1228 mInfoPaint.setTypeface(tf); 1229 } 1230 1231 /** 1232 * alignment of the no data text 1233 * 1234 * @param align 1235 */ setNoDataTextAlignment(Align align)1236 public void setNoDataTextAlignment(Align align) { 1237 mInfoPaint.setTextAlign(align); 1238 } 1239 1240 /** 1241 * Set this to false to disable all gestures and touches on the chart, 1242 * default: true 1243 * 1244 * @param enabled 1245 */ setTouchEnabled(boolean enabled)1246 public void setTouchEnabled(boolean enabled) { 1247 this.mTouchEnabled = enabled; 1248 } 1249 1250 /** 1251 * sets the marker that is displayed when a value is clicked on the chart 1252 * 1253 * @param marker 1254 */ setMarker(IMarker marker)1255 public void setMarker(IMarker marker) { 1256 mMarker = marker; 1257 } 1258 1259 /** 1260 * returns the marker that is set as a marker view for the chart 1261 * 1262 * @return 1263 */ getMarker()1264 public IMarker getMarker() { 1265 return mMarker; 1266 } 1267 1268 @Deprecated setMarkerView(IMarker v)1269 public void setMarkerView(IMarker v) { 1270 setMarker(v); 1271 } 1272 1273 @Deprecated getMarkerView()1274 public IMarker getMarkerView() { 1275 return getMarker(); 1276 } 1277 1278 /** 1279 * Sets a new Description object for the chart. 1280 * 1281 * @param desc 1282 */ setDescription(Description desc)1283 public void setDescription(Description desc) { 1284 this.mDescription = desc; 1285 } 1286 1287 /** 1288 * Returns the Description object of the chart that is responsible for holding all information related 1289 * to the description text that is displayed in the bottom right corner of the chart (by default). 1290 * 1291 * @return 1292 */ getDescription()1293 public Description getDescription() { 1294 return mDescription; 1295 } 1296 1297 /** 1298 * Returns the Legend object of the chart. This method can be used to get an 1299 * instance of the legend in order to customize the automatically generated 1300 * Legend. 1301 * 1302 * @return 1303 */ getLegend()1304 public Legend getLegend() { 1305 return mLegend; 1306 } 1307 1308 /** 1309 * Returns the renderer object responsible for rendering / drawing the 1310 * Legend. 1311 * 1312 * @return 1313 */ getLegendRenderer()1314 public LegendRenderer getLegendRenderer() { 1315 return mLegendRenderer; 1316 } 1317 1318 /** 1319 * Returns the rectangle that defines the borders of the chart-value surface 1320 * (into which the actual values are drawn). 1321 * 1322 * @return 1323 */ 1324 @Override getContentRect()1325 public RectF getContentRect() { 1326 return mViewPortHandler.getContentRect(); 1327 } 1328 1329 /** 1330 * disables intercept touchevents 1331 */ disableScroll()1332 public void disableScroll() { 1333 ViewParent parent = getParent(); 1334 if (parent != null) 1335 parent.requestDisallowInterceptTouchEvent(true); 1336 } 1337 1338 /** 1339 * enables intercept touchevents 1340 */ enableScroll()1341 public void enableScroll() { 1342 ViewParent parent = getParent(); 1343 if (parent != null) 1344 parent.requestDisallowInterceptTouchEvent(false); 1345 } 1346 1347 /** 1348 * paint for the grid background (only line and barchart) 1349 */ 1350 public static final int PAINT_GRID_BACKGROUND = 4; 1351 1352 /** 1353 * paint for the info text that is displayed when there are no values in the 1354 * chart 1355 */ 1356 public static final int PAINT_INFO = 7; 1357 1358 /** 1359 * paint for the description text in the bottom right corner 1360 */ 1361 public static final int PAINT_DESCRIPTION = 11; 1362 1363 /** 1364 * paint for the hole in the middle of the pie chart 1365 */ 1366 public static final int PAINT_HOLE = 13; 1367 1368 /** 1369 * paint for the text in the middle of the pie chart 1370 */ 1371 public static final int PAINT_CENTER_TEXT = 14; 1372 1373 /** 1374 * paint used for the legend 1375 */ 1376 public static final int PAINT_LEGEND_LABEL = 18; 1377 1378 /** 1379 * set a new paint object for the specified parameter in the chart e.g. 1380 * Chart.PAINT_VALUES 1381 * 1382 * @param p the new paint object 1383 * @param which Chart.PAINT_VALUES, Chart.PAINT_GRID, Chart.PAINT_VALUES, 1384 * ... 1385 */ setPaint(Paint p, int which)1386 public void setPaint(Paint p, int which) { 1387 1388 switch (which) { 1389 case PAINT_INFO: 1390 mInfoPaint = p; 1391 break; 1392 case PAINT_DESCRIPTION: 1393 mDescPaint = p; 1394 break; 1395 } 1396 } 1397 1398 /** 1399 * Returns the paint object associated with the provided constant. 1400 * 1401 * @param which e.g. Chart.PAINT_LEGEND_LABEL 1402 * @return 1403 */ getPaint(int which)1404 public Paint getPaint(int which) { 1405 switch (which) { 1406 case PAINT_INFO: 1407 return mInfoPaint; 1408 case PAINT_DESCRIPTION: 1409 return mDescPaint; 1410 } 1411 1412 return null; 1413 } 1414 1415 @Deprecated isDrawMarkerViewsEnabled()1416 public boolean isDrawMarkerViewsEnabled() { 1417 return isDrawMarkersEnabled(); 1418 } 1419 1420 @Deprecated setDrawMarkerViews(boolean enabled)1421 public void setDrawMarkerViews(boolean enabled) { 1422 setDrawMarkers(enabled); 1423 } 1424 1425 /** 1426 * returns true if drawing the marker is enabled when tapping on values 1427 * (use the setMarker(IMarker marker) method to specify a marker) 1428 * 1429 * @return 1430 */ isDrawMarkersEnabled()1431 public boolean isDrawMarkersEnabled() { 1432 return mDrawMarkers; 1433 } 1434 1435 /** 1436 * Set this to true to draw a user specified marker when tapping on 1437 * chart values (use the setMarker(IMarker marker) method to specify a 1438 * marker). Default: true 1439 * 1440 * @param enabled 1441 */ setDrawMarkers(boolean enabled)1442 public void setDrawMarkers(boolean enabled) { 1443 mDrawMarkers = enabled; 1444 } 1445 1446 /** 1447 * Returns the ChartData object that has been set for the chart. 1448 * 1449 * @return 1450 */ getData()1451 public T getData() { 1452 return mData; 1453 } 1454 1455 /** 1456 * Returns the ViewPortHandler of the chart that is responsible for the 1457 * content area of the chart and its offsets and dimensions. 1458 * 1459 * @return 1460 */ getViewPortHandler()1461 public ViewPortHandler getViewPortHandler() { 1462 return mViewPortHandler; 1463 } 1464 1465 /** 1466 * Returns the Renderer object the chart uses for drawing data. 1467 * 1468 * @return 1469 */ getRenderer()1470 public DataRenderer getRenderer() { 1471 return mRenderer; 1472 } 1473 1474 /** 1475 * Sets a new DataRenderer object for the chart. 1476 * 1477 * @param renderer 1478 */ setRenderer(DataRenderer renderer)1479 public void setRenderer(DataRenderer renderer) { 1480 1481 if (renderer != null) 1482 mRenderer = renderer; 1483 } 1484 getHighlighter()1485 public IHighlighter getHighlighter() { 1486 return mHighlighter; 1487 } 1488 1489 /** 1490 * Sets a custom highligher object for the chart that handles / processes 1491 * all highlight touch events performed on the chart-view. 1492 * 1493 * @param highlighter 1494 */ setHighlighter(ChartHighlighter highlighter)1495 public void setHighlighter(ChartHighlighter highlighter) { 1496 mHighlighter = highlighter; 1497 } 1498 1499 /** 1500 * Returns a recyclable MPPointF instance. 1501 * 1502 * @return 1503 */ 1504 @Override getCenterOfView()1505 public MPPointF getCenterOfView() { 1506 return getCenter(); 1507 } 1508 1509 /** 1510 * Returns the bitmap that represents the chart. 1511 * 1512 * @return 1513 */ getChartBitmap()1514 public Bitmap getChartBitmap() { 1515 // Define a bitmap with the same size as the view 1516 Bitmap returnedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565); 1517 // Bind a canvas to it 1518 Canvas canvas = new Canvas(returnedBitmap); 1519 // Get the view's background 1520 Drawable bgDrawable = getBackground(); 1521 if (bgDrawable != null) 1522 // has background drawable, then draw it on the canvas 1523 bgDrawable.draw(canvas); 1524 else 1525 // does not have background drawable, then draw white background on 1526 // the canvas 1527 canvas.drawColor(Color.WHITE); 1528 // draw the view on the canvas 1529 draw(canvas); 1530 // return the bitmap 1531 return returnedBitmap; 1532 } 1533 1534 /** 1535 * Saves the current chart state with the given name to the given path on 1536 * the sdcard leaving the path empty "" will put the saved file directly on 1537 * the SD card chart is saved as a PNG image, example: 1538 * saveToPath("myfilename", "foldername1/foldername2"); 1539 * 1540 * @param title 1541 * @param pathOnSD e.g. "folder1/folder2/folder3" 1542 * @return returns true on success, false on error 1543 */ saveToPath(String title, String pathOnSD)1544 public boolean saveToPath(String title, String pathOnSD) { 1545 1546 1547 1548 Bitmap b = getChartBitmap(); 1549 1550 OutputStream stream = null; 1551 try { 1552 stream = new FileOutputStream(Environment.getExternalStorageDirectory().getPath() 1553 + pathOnSD + "/" + title 1554 + ".png"); 1555 1556 /* 1557 * Write bitmap to file using JPEG or PNG and 40% quality hint for 1558 * JPEG. 1559 */ 1560 b.compress(CompressFormat.PNG, 40, stream); 1561 1562 stream.close(); 1563 } catch (Exception e) { 1564 e.printStackTrace(); 1565 return false; 1566 } 1567 1568 return true; 1569 } 1570 1571 /** 1572 * Saves the current state of the chart to the gallery as an image type. The 1573 * compression must be set for JPEG only. 0 == maximum compression, 100 = low 1574 * compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE 1575 * 1576 * @param fileName e.g. "my_image" 1577 * @param subFolderPath e.g. "ChartPics" 1578 * @param fileDescription e.g. "Chart details" 1579 * @param format e.g. Bitmap.CompressFormat.PNG 1580 * @param quality e.g. 50, min = 0, max = 100 1581 * @return returns true if saving was successful, false if not 1582 */ saveToGallery(String fileName, String subFolderPath, String fileDescription, Bitmap.CompressFormat format, int quality)1583 public boolean saveToGallery(String fileName, String subFolderPath, String fileDescription, Bitmap.CompressFormat 1584 format, int quality) { 1585 // restrain quality 1586 if (quality < 0 || quality > 100) 1587 quality = 50; 1588 1589 long currentTime = System.currentTimeMillis(); 1590 1591 File extBaseDir = Environment.getExternalStorageDirectory(); 1592 File file = new File(extBaseDir.getAbsolutePath() + "/DCIM/" + subFolderPath); 1593 if (!file.exists()) { 1594 if (!file.mkdirs()) { 1595 return false; 1596 } 1597 } 1598 1599 String mimeType = ""; 1600 switch (format) { 1601 case PNG: 1602 mimeType = "image/png"; 1603 if (!fileName.endsWith(".png")) 1604 fileName += ".png"; 1605 break; 1606 case WEBP: 1607 mimeType = "image/webp"; 1608 if (!fileName.endsWith(".webp")) 1609 fileName += ".webp"; 1610 break; 1611 case JPEG: 1612 default: 1613 mimeType = "image/jpeg"; 1614 if (!(fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) 1615 fileName += ".jpg"; 1616 break; 1617 } 1618 1619 String filePath = file.getAbsolutePath() + "/" + fileName; 1620 FileOutputStream out = null; 1621 try { 1622 out = new FileOutputStream(filePath); 1623 1624 Bitmap b = getChartBitmap(); 1625 b.compress(format, quality, out); 1626 1627 out.flush(); 1628 out.close(); 1629 1630 } catch (IOException e) { 1631 e.printStackTrace(); 1632 1633 return false; 1634 } 1635 1636 long size = new File(filePath).length(); 1637 1638 ContentValues values = new ContentValues(8); 1639 1640 // store the details 1641 values.put(Images.Media.TITLE, fileName); 1642 values.put(Images.Media.DISPLAY_NAME, fileName); 1643 values.put(Images.Media.DATE_ADDED, currentTime); 1644 values.put(Images.Media.MIME_TYPE, mimeType); 1645 values.put(Images.Media.DESCRIPTION, fileDescription); 1646 values.put(Images.Media.ORIENTATION, 0); 1647 values.put(Images.Media.DATA, filePath); 1648 values.put(Images.Media.SIZE, size); 1649 1650 return getContext().getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values) != null; 1651 } 1652 1653 /** 1654 * Saves the current state of the chart to the gallery as a JPEG image. The 1655 * filename and compression can be set. 0 == maximum compression, 100 = low 1656 * compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE 1657 * 1658 * @param fileName e.g. "my_image" 1659 * @param quality e.g. 50, min = 0, max = 100 1660 * @return returns true if saving was successful, false if not 1661 */ saveToGallery(String fileName, int quality)1662 public boolean saveToGallery(String fileName, int quality) { 1663 return saveToGallery(fileName, "", "MPAndroidChart-Library Save", Bitmap.CompressFormat.PNG, quality); 1664 } 1665 1666 /** 1667 * Saves the current state of the chart to the gallery as a PNG image. 1668 * NOTE: Needs permission WRITE_EXTERNAL_STORAGE 1669 * 1670 * @param fileName e.g. "my_image" 1671 * @return returns true if saving was successful, false if not 1672 */ saveToGallery(String fileName)1673 public boolean saveToGallery(String fileName) { 1674 return saveToGallery(fileName, "", "MPAndroidChart-Library Save", Bitmap.CompressFormat.PNG, 40); 1675 } 1676 1677 /** 1678 * tasks to be done after the view is setup 1679 */ 1680 protected ArrayList<Runnable> mJobs = new ArrayList<Runnable>(); 1681 removeViewportJob(Runnable job)1682 public void removeViewportJob(Runnable job) { 1683 mJobs.remove(job); 1684 } 1685 clearAllViewportJobs()1686 public void clearAllViewportJobs() { 1687 mJobs.clear(); 1688 } 1689 1690 /** 1691 * Either posts a job immediately if the chart has already setup it's 1692 * dimensions or adds the job to the execution queue. 1693 * 1694 * @param job 1695 */ addViewportJob(Runnable job)1696 public void addViewportJob(Runnable job) { 1697 1698 if (mViewPortHandler.hasChartDimens()) { 1699 post(job); 1700 } else { 1701 mJobs.add(job); 1702 } 1703 } 1704 1705 /** 1706 * Returns all jobs that are scheduled to be executed after 1707 * onSizeChanged(...). 1708 * 1709 * @return 1710 */ getJobs()1711 public ArrayList<Runnable> getJobs() { 1712 return mJobs; 1713 } 1714 1715 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1716 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1717 1718 for (int i = 0; i < getChildCount(); i++) { 1719 getChildAt(i).layout(left, top, right, bottom); 1720 } 1721 } 1722 1723 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1724 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1725 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1726 int size = (int) Utils.convertDpToPixel(50f); 1727 setMeasuredDimension( 1728 Math.max(getSuggestedMinimumWidth(), 1729 resolveSize(size, 1730 widthMeasureSpec)), 1731 Math.max(getSuggestedMinimumHeight(), 1732 resolveSize(size, 1733 heightMeasureSpec))); 1734 } 1735 1736 @Override onSizeChanged(int w, int h, int oldw, int oldh)1737 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1738 if (mLogEnabled) 1739 Log.i(LOG_TAG, "OnSizeChanged()"); 1740 1741 if (w > 0 && h > 0 && w < 10000 && h < 10000) { 1742 if (mLogEnabled) 1743 Log.i(LOG_TAG, "Setting chart dimens, width: " + w + ", height: " + h); 1744 mViewPortHandler.setChartDimens(w, h); 1745 } else { 1746 if (mLogEnabled) 1747 Log.w(LOG_TAG, "*Avoiding* setting chart dimens! width: " + w + ", height: " + h); 1748 } 1749 1750 // This may cause the chart view to mutate properties affecting the view port -- 1751 // lets do this before we try to run any pending jobs on the view port itself 1752 notifyDataSetChanged(); 1753 1754 for (Runnable r : mJobs) { 1755 post(r); 1756 } 1757 1758 mJobs.clear(); 1759 1760 super.onSizeChanged(w, h, oldw, oldh); 1761 } 1762 1763 /** 1764 * Setting this to true will set the layer-type HARDWARE for the view, false 1765 * will set layer-type SOFTWARE. 1766 * 1767 * @param enabled 1768 */ setHardwareAccelerationEnabled(boolean enabled)1769 public void setHardwareAccelerationEnabled(boolean enabled) { 1770 1771 if (enabled) 1772 setLayerType(View.LAYER_TYPE_HARDWARE, null); 1773 else 1774 setLayerType(View.LAYER_TYPE_SOFTWARE, null); 1775 } 1776 1777 @Override onDetachedFromWindow()1778 protected void onDetachedFromWindow() { 1779 super.onDetachedFromWindow(); 1780 1781 //Log.i(LOG_TAG, "Detaching..."); 1782 1783 if (mUnbind) 1784 unbindDrawables(this); 1785 } 1786 1787 /** 1788 * unbind flag 1789 */ 1790 private boolean mUnbind = false; 1791 1792 /** 1793 * Unbind all drawables to avoid memory leaks. 1794 * Link: http://stackoverflow.com/a/6779164/1590502 1795 * 1796 * @param view 1797 */ unbindDrawables(View view)1798 private void unbindDrawables(View view) { 1799 1800 if (view.getBackground() != null) { 1801 view.getBackground().setCallback(null); 1802 } 1803 if (view instanceof ViewGroup) { 1804 for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { 1805 unbindDrawables(((ViewGroup) view).getChildAt(i)); 1806 } 1807 ((ViewGroup) view).removeAllViews(); 1808 } 1809 } 1810 1811 /** 1812 * Set this to true to enable "unbinding" of drawables. When a View is detached 1813 * from a window. This helps avoid memory leaks. 1814 * Default: false 1815 * Link: http://stackoverflow.com/a/6779164/1590502 1816 * 1817 * @param enabled 1818 */ setUnbindEnabled(boolean enabled)1819 public void setUnbindEnabled(boolean enabled) { 1820 this.mUnbind = enabled; 1821 } 1822 } 1823