• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 package com.github.mikephil.charting.charts;
3 
4 import android.animation.ObjectAnimator;
5 import android.animation.ValueAnimator;
6 import android.animation.ValueAnimator.AnimatorUpdateListener;
7 import android.annotation.SuppressLint;
8 import android.content.Context;
9 import android.graphics.RectF;
10 import android.util.AttributeSet;
11 import android.util.Log;
12 import android.view.MotionEvent;
13 
14 import com.github.mikephil.charting.animation.Easing;
15 import com.github.mikephil.charting.animation.Easing.EasingFunction;
16 import com.github.mikephil.charting.components.Legend;
17 import com.github.mikephil.charting.components.XAxis;
18 import com.github.mikephil.charting.data.ChartData;
19 import com.github.mikephil.charting.data.Entry;
20 import com.github.mikephil.charting.interfaces.datasets.IDataSet;
21 import com.github.mikephil.charting.listener.PieRadarChartTouchListener;
22 import com.github.mikephil.charting.utils.MPPointF;
23 import com.github.mikephil.charting.utils.Utils;
24 
25 /**
26  * Baseclass of PieChart and RadarChart.
27  *
28  * @author Philipp Jahoda
29  */
30 public abstract class PieRadarChartBase<T extends ChartData<? extends IDataSet<? extends Entry>>>
31         extends Chart<T> {
32 
33     /**
34      * holds the normalized version of the current rotation angle of the chart
35      */
36     private float mRotationAngle = 270f;
37 
38     /**
39      * holds the raw version of the current rotation angle of the chart
40      */
41     private float mRawRotationAngle = 270f;
42 
43     /**
44      * flag that indicates if rotation is enabled or not
45      */
46     protected boolean mRotateEnabled = true;
47 
48     /**
49      * Sets the minimum offset (padding) around the chart, defaults to 0.f
50      */
51     protected float mMinOffset = 0.f;
52 
PieRadarChartBase(Context context)53     public PieRadarChartBase(Context context) {
54         super(context);
55     }
56 
PieRadarChartBase(Context context, AttributeSet attrs)57     public PieRadarChartBase(Context context, AttributeSet attrs) {
58         super(context, attrs);
59     }
60 
PieRadarChartBase(Context context, AttributeSet attrs, int defStyle)61     public PieRadarChartBase(Context context, AttributeSet attrs, int defStyle) {
62         super(context, attrs, defStyle);
63     }
64 
65     @Override
init()66     protected void init() {
67         super.init();
68 
69         mChartTouchListener = new PieRadarChartTouchListener(this);
70     }
71 
72     @Override
calcMinMax()73     protected void calcMinMax() {
74         //mXAxis.mAxisRange = mData.getXVals().size() - 1;
75     }
76 
77     @Override
getMaxVisibleCount()78     public int getMaxVisibleCount() {
79         return mData.getEntryCount();
80     }
81 
82     @Override
onTouchEvent(MotionEvent event)83     public boolean onTouchEvent(MotionEvent event) {
84         // use the pie- and radarchart listener own listener
85         if (mTouchEnabled && mChartTouchListener != null)
86             return mChartTouchListener.onTouch(this, event);
87         else
88             return super.onTouchEvent(event);
89     }
90 
91     @Override
computeScroll()92     public void computeScroll() {
93 
94         if (mChartTouchListener instanceof PieRadarChartTouchListener)
95             ((PieRadarChartTouchListener) mChartTouchListener).computeScroll();
96     }
97 
98     @Override
notifyDataSetChanged()99     public void notifyDataSetChanged() {
100         if (mData == null)
101             return;
102 
103         calcMinMax();
104 
105         if (mLegend != null)
106             mLegendRenderer.computeLegend(mData);
107 
108         calculateOffsets();
109     }
110 
111     @Override
calculateOffsets()112     public void calculateOffsets() {
113 
114         float legendLeft = 0f, legendRight = 0f, legendBottom = 0f, legendTop = 0f;
115 
116         if (mLegend != null && mLegend.isEnabled() && !mLegend.isDrawInsideEnabled()) {
117 
118             float fullLegendWidth = Math.min(mLegend.mNeededWidth,
119                     mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent());
120 
121             switch (mLegend.getOrientation()) {
122                 case VERTICAL: {
123                     float xLegendOffset = 0.f;
124 
125                     if (mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.LEFT
126                             || mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.RIGHT) {
127                         if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.CENTER) {
128                             // this is the space between the legend and the chart
129                             final float spacing = Utils.convertDpToPixel(13f);
130 
131                             xLegendOffset = fullLegendWidth + spacing;
132 
133                         } else {
134                             // this is the space between the legend and the chart
135                             float spacing = Utils.convertDpToPixel(8f);
136 
137                             float legendWidth = fullLegendWidth + spacing;
138                             float legendHeight = mLegend.mNeededHeight + mLegend.mTextHeightMax;
139 
140                             MPPointF center = getCenter();
141 
142                             float bottomX = mLegend.getHorizontalAlignment() ==
143                                     Legend.LegendHorizontalAlignment.RIGHT
144                                     ? getWidth() - legendWidth + 15.f
145                                     : legendWidth - 15.f;
146                             float bottomY = legendHeight + 15.f;
147                             float distLegend = distanceToCenter(bottomX, bottomY);
148 
149                             MPPointF reference = getPosition(center, getRadius(),
150                                     getAngleForPoint(bottomX, bottomY));
151 
152                             float distReference = distanceToCenter(reference.x, reference.y);
153                             float minOffset = Utils.convertDpToPixel(5f);
154 
155                             if (bottomY >= center.y && getHeight() - legendWidth > getWidth()) {
156                                 xLegendOffset = legendWidth;
157                             } else if (distLegend < distReference) {
158 
159                                 float diff = distReference - distLegend;
160                                 xLegendOffset = minOffset + diff;
161                             }
162 
163                             MPPointF.recycleInstance(center);
164                             MPPointF.recycleInstance(reference);
165                         }
166                     }
167 
168                     switch (mLegend.getHorizontalAlignment()) {
169                         case LEFT:
170                             legendLeft = xLegendOffset;
171                             break;
172 
173                         case RIGHT:
174                             legendRight = xLegendOffset;
175                             break;
176 
177                         case CENTER:
178                             switch (mLegend.getVerticalAlignment()) {
179                                 case TOP:
180                                     legendTop = Math.min(mLegend.mNeededHeight,
181                                             mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent());
182                                     break;
183                                 case BOTTOM:
184                                     legendBottom = Math.min(mLegend.mNeededHeight,
185                                             mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent());
186                                     break;
187                             }
188                             break;
189                     }
190                 }
191                 break;
192 
193                 case HORIZONTAL:
194                     float yLegendOffset = 0.f;
195 
196                     if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.TOP ||
197                             mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.BOTTOM) {
198 
199                         // It's possible that we do not need this offset anymore as it
200                         //   is available through the extraOffsets, but changing it can mean
201                         //   changing default visibility for existing apps.
202                         float yOffset = getRequiredLegendOffset();
203 
204                         yLegendOffset = Math.min(mLegend.mNeededHeight + yOffset,
205                                 mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent());
206 
207                         switch (mLegend.getVerticalAlignment()) {
208                             case TOP:
209                                 legendTop = yLegendOffset;
210                                 break;
211                             case BOTTOM:
212                                 legendBottom = yLegendOffset;
213                                 break;
214                         }
215                     }
216                     break;
217             }
218 
219             legendLeft += getRequiredBaseOffset();
220             legendRight += getRequiredBaseOffset();
221             legendTop += getRequiredBaseOffset();
222             legendBottom += getRequiredBaseOffset();
223         }
224 
225         float minOffset = Utils.convertDpToPixel(mMinOffset);
226 
227         if (this instanceof RadarChart) {
228             XAxis x = this.getXAxis();
229 
230             if (x.isEnabled() && x.isDrawLabelsEnabled()) {
231                 minOffset = Math.max(minOffset, x.mLabelRotatedWidth);
232             }
233         }
234 
235         legendTop += getExtraTopOffset();
236         legendRight += getExtraRightOffset();
237         legendBottom += getExtraBottomOffset();
238         legendLeft += getExtraLeftOffset();
239 
240         float offsetLeft = Math.max(minOffset, legendLeft);
241         float offsetTop = Math.max(minOffset, legendTop);
242         float offsetRight = Math.max(minOffset, legendRight);
243         float offsetBottom = Math.max(minOffset, Math.max(getRequiredBaseOffset(), legendBottom));
244 
245         mViewPortHandler.restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom);
246 
247         if (mLogEnabled)
248             Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop
249                     + ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom);
250     }
251 
252     /**
253      * returns the angle relative to the chart center for the given point on the
254      * chart in degrees. The angle is always between 0 and 360°, 0° is NORTH,
255      * 90° is EAST, ...
256      *
257      * @param x
258      * @param y
259      * @return
260      */
getAngleForPoint(float x, float y)261     public float getAngleForPoint(float x, float y) {
262 
263         MPPointF c = getCenterOffsets();
264 
265         double tx = x - c.x, ty = y - c.y;
266         double length = Math.sqrt(tx * tx + ty * ty);
267         double r = Math.acos(ty / length);
268 
269         float angle = (float) Math.toDegrees(r);
270 
271         if (x > c.x)
272             angle = 360f - angle;
273 
274         // add 90° because chart starts EAST
275         angle = angle + 90f;
276 
277         // neutralize overflow
278         if (angle > 360f)
279             angle = angle - 360f;
280 
281         MPPointF.recycleInstance(c);
282 
283         return angle;
284     }
285 
286     /**
287      * Returns a recyclable MPPointF instance.
288      * Calculates the position around a center point, depending on the distance
289      * from the center, and the angle of the position around the center.
290      *
291      * @param center
292      * @param dist
293      * @param angle  in degrees, converted to radians internally
294      * @return
295      */
getPosition(MPPointF center, float dist, float angle)296     public MPPointF getPosition(MPPointF center, float dist, float angle) {
297 
298         MPPointF p = MPPointF.getInstance(0, 0);
299         getPosition(center, dist, angle, p);
300         return p;
301     }
302 
getPosition(MPPointF center, float dist, float angle, MPPointF outputPoint)303     public void getPosition(MPPointF center, float dist, float angle, MPPointF outputPoint) {
304         outputPoint.x = (float) (center.x + dist * Math.cos(Math.toRadians(angle)));
305         outputPoint.y = (float) (center.y + dist * Math.sin(Math.toRadians(angle)));
306     }
307 
308     /**
309      * Returns the distance of a certain point on the chart to the center of the
310      * chart.
311      *
312      * @param x
313      * @param y
314      * @return
315      */
distanceToCenter(float x, float y)316     public float distanceToCenter(float x, float y) {
317 
318         MPPointF c = getCenterOffsets();
319 
320         float dist = 0f;
321 
322         float xDist = 0f;
323         float yDist = 0f;
324 
325         if (x > c.x) {
326             xDist = x - c.x;
327         } else {
328             xDist = c.x - x;
329         }
330 
331         if (y > c.y) {
332             yDist = y - c.y;
333         } else {
334             yDist = c.y - y;
335         }
336 
337         // pythagoras
338         dist = (float) Math.sqrt(Math.pow(xDist, 2.0) + Math.pow(yDist, 2.0));
339 
340         MPPointF.recycleInstance(c);
341 
342         return dist;
343     }
344 
345     /**
346      * Returns the xIndex for the given angle around the center of the chart.
347      * Returns -1 if not found / outofbounds.
348      *
349      * @param angle
350      * @return
351      */
getIndexForAngle(float angle)352     public abstract int getIndexForAngle(float angle);
353 
354     /**
355      * Set an offset for the rotation of the RadarChart in degrees. Default 270f
356      * --> top (NORTH)
357      *
358      * @param angle
359      */
setRotationAngle(float angle)360     public void setRotationAngle(float angle) {
361         mRawRotationAngle = angle;
362         mRotationAngle = Utils.getNormalizedAngle(mRawRotationAngle);
363     }
364 
365     /**
366      * gets the raw version of the current rotation angle of the pie chart the
367      * returned value could be any value, negative or positive, outside of the
368      * 360 degrees. this is used when working with rotation direction, mainly by
369      * gestures and animations.
370      *
371      * @return
372      */
getRawRotationAngle()373     public float getRawRotationAngle() {
374         return mRawRotationAngle;
375     }
376 
377     /**
378      * gets a normalized version of the current rotation angle of the pie chart,
379      * which will always be between 0.0 < 360.0
380      *
381      * @return
382      */
getRotationAngle()383     public float getRotationAngle() {
384         return mRotationAngle;
385     }
386 
387     /**
388      * Set this to true to enable the rotation / spinning of the chart by touch.
389      * Set it to false to disable it. Default: true
390      *
391      * @param enabled
392      */
setRotationEnabled(boolean enabled)393     public void setRotationEnabled(boolean enabled) {
394         mRotateEnabled = enabled;
395     }
396 
397     /**
398      * Returns true if rotation of the chart by touch is enabled, false if not.
399      *
400      * @return
401      */
isRotationEnabled()402     public boolean isRotationEnabled() {
403         return mRotateEnabled;
404     }
405 
406     /**
407      * Gets the minimum offset (padding) around the chart, defaults to 0.f
408      */
getMinOffset()409     public float getMinOffset() {
410         return mMinOffset;
411     }
412 
413     /**
414      * Sets the minimum offset (padding) around the chart, defaults to 0.f
415      */
setMinOffset(float minOffset)416     public void setMinOffset(float minOffset) {
417         mMinOffset = minOffset;
418     }
419 
420     /**
421      * returns the diameter of the pie- or radar-chart
422      *
423      * @return
424      */
getDiameter()425     public float getDiameter() {
426         RectF content = mViewPortHandler.getContentRect();
427         content.left += getExtraLeftOffset();
428         content.top += getExtraTopOffset();
429         content.right -= getExtraRightOffset();
430         content.bottom -= getExtraBottomOffset();
431         return Math.min(content.width(), content.height());
432     }
433 
434     /**
435      * Returns the radius of the chart in pixels.
436      *
437      * @return
438      */
getRadius()439     public abstract float getRadius();
440 
441     /**
442      * Returns the required offset for the chart legend.
443      *
444      * @return
445      */
getRequiredLegendOffset()446     protected abstract float getRequiredLegendOffset();
447 
448     /**
449      * Returns the base offset needed for the chart without calculating the
450      * legend size.
451      *
452      * @return
453      */
getRequiredBaseOffset()454     protected abstract float getRequiredBaseOffset();
455 
456     @Override
getYChartMax()457     public float getYChartMax() {
458         // TODO Auto-generated method stub
459         return 0;
460     }
461 
462     @Override
getYChartMin()463     public float getYChartMin() {
464         // TODO Auto-generated method stub
465         return 0;
466     }
467 
468     /**
469      * ################ ################ ################ ################
470      */
471     /** CODE BELOW THIS RELATED TO ANIMATION */
472 
473     /**
474      * Applys a spin animation to the Chart.
475      *
476      * @param durationmillis
477      * @param fromangle
478      * @param toangle
479      */
480     @SuppressLint("NewApi")
spin(int durationmillis, float fromangle, float toangle, EasingFunction easing)481     public void spin(int durationmillis, float fromangle, float toangle, EasingFunction easing) {
482 
483         setRotationAngle(fromangle);
484 
485         ObjectAnimator spinAnimator = ObjectAnimator.ofFloat(this, "rotationAngle", fromangle,
486                 toangle);
487         spinAnimator.setDuration(durationmillis);
488         spinAnimator.setInterpolator(easing);
489 
490         spinAnimator.addUpdateListener(new AnimatorUpdateListener() {
491 
492             @Override
493             public void onAnimationUpdate(ValueAnimator animation) {
494                 postInvalidate();
495             }
496         });
497         spinAnimator.start();
498     }
499 }
500