content: 
  package com.github.mikephil.charting.charts;
  
  import android.content.Context;
  import android.graphics.Matrix;
  import android.graphics.Paint.Align;
  import android.graphics.PointF;
  import android.util.AttributeSet;
  import android.view.MotionEvent;
  
  import com.github.mikephil.charting.data.ChartData;
  import com.github.mikephil.charting.data.DataSet;
  import com.github.mikephil.charting.data.Entry;
  import com.github.mikephil.charting.listener.PieRadarChartTouchListener;
  import com.github.mikephil.charting.utils.Legend.LegendPosition;
  import com.github.mikephil.charting.utils.Utils;
  import com.nineoldandroids.animation.ObjectAnimator;
  
  /**
   * Baseclass of PieChart and RadarChart.
   * 
   * @author Philipp Jahoda
   */
  public abstract class PieRadarChartBase<T extends ChartData<? extends DataSet<? extends Entry>>>
          extends Chart<T> {
  
      /** holds the current rotation angle of the chart */
      protected float mRotationAngle = 270f;
  
      /** flag that indicates if rotation is enabled or not */
      private boolean mRotateEnabled = true;
  
      /** the pie- and radarchart touchlistener */
      private OnTouchListener mListener;
  
      public PieRadarChartBase(Context context) {
          super(context);
      }
  
      public PieRadarChartBase(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public PieRadarChartBase(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
      }
  
      @Override
      protected void init() {
          super.init();
  
          mListener = new PieRadarChartTouchListener(this);
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          // use the pie- and radarchart listener own listener
          if (mTouchEnabled && mListener != null)
              return mListener.onTouch(this, event);
          else
              return super.onTouchEvent(event);
      }
  
      @Override
      public void prepare() {
  
          if (mDataNotSet)
              return;
  
          calcMinMax(false);
  
          prepareLegend();
  
          calculateOffsets();
      }
  
      @Override
      public void notifyDataSetChanged() {
          prepare();
      }
  
      @Override
      protected void calculateOffsets() {
  
          float legendRight = 0f, legendBottom = 0f, legendTop = 0f;
  
          if (mDrawLegend && mLegend != null && mLegend.getPosition() != LegendPosition.NONE) {
  
              if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART_CENTER) {
  
                  // this is the space between the legend and the chart
                  float spacing = Utils.convertDpToPixel(13f);
  
                  legendRight = getFullLegendWidth() + spacing;
  
                  mLegendLabelPaint.setTextAlign(Align.LEFT);
                  // legendTop = mLegend.getFullHeight(mLegendLabelPaint);
  
              } else if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART) {
  
                  // this is the space between the legend and the chart
                  float spacing = Utils.convertDpToPixel(13f);
  
                  float legendWidth = getFullLegendWidth() + spacing;
  
                  float legendHeight = mLegend.getFullHeight(mLegendLabelPaint) + mOffsetTop;
  
                  PointF c = getCenter();
  
                  PointF bottomRight = new PointF(getWidth() - legendWidth, legendHeight);
                  PointF reference = getPosition(c, getRadius(), 320);
  
                  float distLegend = distanceToCenter(bottomRight.x, bottomRight.y);
                  float distReference = distanceToCenter(reference.x, reference.y);
                  float min = Utils.convertDpToPixel(5f);
  
                  if (distLegend < distReference) {
  
                      float diff = distReference - distLegend;
  
                      legendRight = min + diff;
                      legendTop = min + diff;
                  }
  
                  if (bottomRight.y >= c.y) {
                      legendRight = legendWidth;
                  }
  
                  mLegendLabelPaint.setTextAlign(Align.LEFT);
  
              } else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT
                      || mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT
                      || mLegend.getPosition() == LegendPosition.BELOW_CHART_CENTER) {
  
                  legendBottom = getRequiredBottomOffset();
              }
  
              legendRight += getRequiredBaseOffset();
              legendTop += getRequiredBaseOffset();
  
              mLegend.setOffsetBottom(mLegendLabelPaint.getTextSize() * 4f);
              mLegend.setOffsetRight(legendRight);
          }
  
          float min = Utils.convertDpToPixel(11f);
  
          if (mLegend != null) {
              mLegend.setOffsetLeft(min);
          }
  
          mOffsetLeft = Math.max(min, getRequiredBaseOffset());
          mOffsetTop = Math.max(min, legendTop);
          mOffsetRight = Math.max(min, legendRight);
          mOffsetBottom = Math.max(min, Math.max(getRequiredBaseOffset(), legendBottom));
  
          applyCalculatedOffsets();
      }
  
      @Override
      protected void drawAdditional() {
          // TODO Auto-generated method stub
      }
  
      /**
       * Applys the newly calculated offsets to the matrices.
       */
      protected void applyCalculatedOffsets() {
  
          prepareContentRect();
  
          float scaleX = (float) ((getWidth() - mOffsetLeft - mOffsetRight) / mDeltaX);
          float scaleY = (float) ((getHeight() - mOffsetBottom - mOffsetTop) / mDeltaY);
  
          Matrix val = new Matrix();
          val.postTranslate(0, -mYChartMin);
          val.postScale(scaleX, -scaleY);
  
          mTrans.getValueMatrix().set(val);
  
          Matrix offset = new Matrix();
          offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom);
  
          mTrans.getOffsetMatrix().set(offset);
      }
  
      /** the angle where the dragging started */
      private float mStartAngle = 0f;
  
      /**
       * sets the starting angle of the rotation, this is only used by the touch
       * listener, x and y is the touch position
       * 
       * @param x
       * @param y
       */
      public void setStartAngle(float x, float y) {
  
          mStartAngle = getAngleForPoint(x, y);
  
          // take the current angle into consideration when starting a new drag
          mStartAngle -= mRotationAngle;
      }
  
      /**
       * updates the view rotation depending on the given touch position, also
       * takes the starting angle into consideration
       * 
       * @param x
       * @param y
       */
      public void updateRotation(float x, float y) {
  
          mRotationAngle = getAngleForPoint(x, y);
  
          // take the offset into consideration
          mRotationAngle -= mStartAngle;
  
          // keep the angle >= 0 and <= 360
          mRotationAngle = (mRotationAngle + 360f) % 360f;
      }
  
      /**
       * returns the angle relative to the chart center for the given point on the
       * chart in degrees. The angle is always between 0 and 360°, 0° is NORTH,
       * 90° is EAST, ...
       * 
       * @param x
       * @param y
       * @return
       */
      public float getAngleForPoint(float x, float y) {
  
          PointF c = getCenterOffsets();
  
          double tx = x - c.x, ty = y - c.y;
          double length = Math.sqrt(tx * tx + ty * ty);
          double r = Math.acos(ty / length);
  
          float angle = (float) Math.toDegrees(r);
  
          if (x > c.x)
              angle = 360f - angle;
  
          // add 90° because chart starts EAST
          angle = angle + 90f;
  
          // neutralize overflow
          if (angle > 360f)
              angle = angle - 360f;
  
          return angle;
      }
  
      /**
       * Calculates the position around a center point, depending on the distance
       * from the center, and the angle of the position around the center.
       * 
       * @param center
       * @param dist
       * @param angle in degrees, converted to radians internally
       * @return
       */
      protected PointF getPosition(PointF center, float dist, float angle) {
  
          PointF p = new PointF((float) (center.x + dist * Math.cos(Math.toRadians(angle))),
                  (float) (center.y + dist * Math.sin(Math.toRadians(angle))));
          return p;
      }
  
      /**
       * Returns the distance of a certain point on the chart to the center of the
       * chart.
       * 
       * @param c the center
       * @param x
       * @param y
       * @return
       */
      public float distanceToCenter(float x, float y) {
  
          PointF c = getCenterOffsets();
  
          float dist = 0f;
  
          float xDist = 0f;
          float yDist = 0f;
  
          if (x > c.x) {
              xDist = x - c.x;
          } else {
              xDist = c.x - x;
          }
  
          if (y > c.y) {
              yDist = y - c.y;
          } else {
              yDist = c.y - y;
          }
  
          // pythagoras
          dist = (float) Math.sqrt(Math.pow(xDist, 2.0) + Math.pow(yDist, 2.0));
  
          return dist;
      }
  
      /**
       * Returns the xIndex for the given angle around the center of the chart.
       * Returns -1 if not found / outofbounds.
       * 
       * @param angle
       * @return
       */
      public abstract int getIndexForAngle(float angle);
  
      /**
       * Set an offset for the rotation of the RadarChart in degrees. Default 270f
       * --> top (NORTH)
       * 
       * @param angle
       */
      public void setRotationAngle(float angle) {
  
          angle = (int) Math.abs(angle % 360);
          mRotationAngle = angle;
      }
  
      /**
       * gets the current rotation angle of the pie chart
       * 
       * @return
       */
      public float getRotationAngle() {
          return mRotationAngle;
      }
  
      /**
       * Set this to true to enable the rotation / spinning of the chart by touch.
       * Set it to false to disable it. Default: true
       * 
       * @param enabled
       */
      public void setRotationEnabled(boolean enabled) {
          mRotateEnabled = enabled;
      }
  
      /**
       * Returns true if rotation of the chart by touch is enabled, false if not.
       * 
       * @return
       */
      public boolean isRotationEnabled() {
          return mRotateEnabled;
      }
  
      /**
       * returns the diameter of the pie- or radar-chart
       * 
       * @return
       */
      public float getDiameter() {
          if (mContentRect == null)
              return 0;
          else
              return Math.min(mContentRect.width(), mContentRect.height());
      }
  
      /**
       * Returns the radius of the chart in pixels.
       * 
       * @return
       */
      public abstract float getRadius();
  
      /**
       * Returns the required bottom offset for the chart.
       * 
       * @return
       */
      protected abstract float getRequiredBottomOffset();
  
      /**
       * Returns the base offset needed for the chart without calculating the
       * legend size.
       * 
       * @return
       */
      protected abstract float getRequiredBaseOffset();
  
      /**
       * Returns the required right offset for the chart.
       * 
       * @return
       */
      private float getFullLegendWidth() {
          return mLegend.getMaximumEntryLength(mLegendLabelPaint)
                  + mLegend.getFormSize() + mLegend.getFormToTextSpace();
      }
  
      /**
       * set a new (e.g. custom) charttouchlistener NOTE: make sure to
       * setTouchEnabled(true); if you need touch gestures on the chart
       * 
       * @param l
       */
      public void setOnTouchListener(OnTouchListener l) {
          this.mListener = l;
      }
  
      /**
       * ################ ################ ################ ################
       */
      /** CODE BELOW THIS RELATED TO ANIMATION */
  
      /** objectanimator used for animating values on y-axis */
      private ObjectAnimator mSpinAnimator;
  
      /**
       * Applys a spin animation to the Chart.
       * 
       * @param durationmillis
       * @param fromangle
       * @param toangle
       */
      public void spin(int durationmillis, float fromangle, float toangle) {
  
          mRotationAngle = fromangle;
  
          mSpinAnimator = ObjectAnimator.ofFloat(this, "rotationAngle", fromangle, toangle);
          mSpinAnimator.setDuration(durationmillis);
          mSpinAnimator.addUpdateListener(this);
          mSpinAnimator.start();
      }
  }
  