• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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