<commit_before>/*
 * Copyright (C) 2015 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hookedonplay.decoviewlib.charts;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import com.hookedonplay.decoviewlib.DecoView;
import com.hookedonplay.decoviewlib.events.DecoEvent;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;

/**
 * ChartSeries is the implementation of one series of data.
 * A {@link DecoView} can have one or more DataSeries
 */
abstract public class ChartSeries {
    /**
     * Minimum sweep angle. We need this to be greater than 0 as we want something drawn
     * even the data value is 0
     */
    static final private float MIN_SWEEP_ANGLE = 0.1f;
    static final private float MIN_SWEEP_ANGLE_FLAT = 0.1f;
    static final private float MIN_SWEEP_ANGLE_NONE = 0f;
    static final private float MIN_SWEEP_ANGLE_PIE = MIN_SWEEP_ANGLE_NONE;

    @SuppressWarnings("unused")
    protected final String TAG = getClass().getSimpleName();
    /**
     * ArcItem attributes to be drawn
     */
    protected final SeriesItem mSeriesItem;
    /**
     * Current Mode of drawing
     */
    protected DecoEvent.EventType mDrawMode;
    /**
     * Current Effect being executed (if any)
     */
    protected DecoDrawEffect mEffect;
    /**
     * Positions for current animation
     */
    protected float mPositionStart = 0;
    protected float mPositionEnd = 0;
    protected float mPositionCurrentEnd = 0;
    /**
     * 0..1.0f The percentage of the animation complete
     */
    protected float mPercentComplete = 1.0f;
    /**
     * Drawing bounds for arc
     */
    protected RectF mBounds;
    /**
     * Drawing bounds for arc after inset applied
     */
    protected RectF mBoundsInset;
    /**
     * Angle of drawing point origin.
     */
    protected int mAngleStart = 180;
    /**
     * Number of degrees to sweep. 360 for complete circle
     */
    protected int mAngleSweep = 360;
    /**
     * Paint used for drawing arc
     */
    protected Paint mPaint;
    /**
     * Arc visible or hidden
     */
    private boolean mVisible;
    /**
     * ValueAnimator to calculate arc drawing position during animation
     */
    private ValueAnimator mValueAnimator;

    private ColorAnimate mColorAnimate;

    /**
     * Construct an ArcSeries based on the ArcItem attributes and the angle and shape
     * of the arc
     *
     * @param seriesItem  Arc attributes to apply
     * @param totalAngle  Angle of the total arc area in degrees
     * @param rotateAngle number of degrees to rotate the start position
     *                    <p/>
     *                    No Access Modifier for the constructor is specified. This is deliberate so we use the
     *                    default package access. This class should not be constructed outside of the package scope.
     *                    Clients of this library need only to construct an {@link ChartSeries} and pass that to the
     *                    DynamicArcView to have a new ArcSeries created
     */
    ChartSeries(@NonNull SeriesItem seriesItem, int totalAngle, int rotateAngle) {
        mSeriesItem = seriesItem;
        mVisible = seriesItem.getInitialVisibility();
        setupView(totalAngle, rotateAngle);
        reset();
    }

    /**
     * Configure the view for the given angles. Set the total angle of the arc and also
     * pass an offset angle to change the initial drawing location
     *
     * @param totalAngle  The total angle of the Arc...360 is a circle
     * @param rotateAngle The angle to rotate the drawing start position
     */
    public void setupView(int totalAngle, int rotateAngle) {
        if (totalAngle < 0 || totalAngle > 360) {
            throw new IllegalArgumentException("Total angle of view must be in the range 0..360");
        }
        if (rotateAngle < 0 || rotateAngle > 360) {
            throw new IllegalArgumentException("Rotate angle of view must be in the range 0..360");
        }

        mAngleStart = rotateAngle;
        mAngleSweep = totalAngle;

        /**
         * If we are spinning anti-clockwise we need the start to be where the finish would
         * normally be
         */
        if (!mSeriesItem.getSpinClockwise()) {
            mAngleStart = (mAngleStart + mAngleSweep) % 360;
        }
        mBounds = null;
    }

    public SeriesItem getSeriesItem() {
        return mSeriesItem;
    }

    /**
     * Create the animation of filling the chart by using a valueAnimator to adjust values
     */
    public void startAnimateMove(@NonNull final DecoEvent event) {
        mDrawMode = event.getEventType();
        mVisible = true;

        cancelAnimation();

        final boolean changeColors = event.isColorSet();
        if (changeColors) {
            mColorAnimate = new ColorAnimate(mSeriesItem.getColor(), event.getColor());
            mSeriesItem.setColor(event.getColor());
        }
        float position = event.getEndPosition();

        if (Math.abs(mPositionEnd - position) < 0.01) {
            Log.e(TAG, "Aborting move request, no position change");
            return;
        }

        event.notifyStartListener();

        mPositionStart = mPositionCurrentEnd;
        mPositionEnd = position;

        mValueAnimator = ValueAnimator.ofFloat(mPositionStart, position);

        long animationDuration = event.getEffectDuration();
        if (animationDuration <= 0) {
            /**
             * If an animation duration is not set we calculate it using a formula of the proportion
             * of a revolution multiplied by the default time for a full revolution. This ensures
             * that the speed of the move is consistent for all ranges
             */
            animationDuration = (Math.abs((int) (mSeriesItem.getSpinDuration() *
                    ((mPositionStart - mPositionEnd) / mSeriesItem.getMaxValue()))));
        }
        mValueAnimator.setDuration(animationDuration);

        /**
         * Note: When setting the Interpolator the default is
         * {@link android.view.animation.AccelerateDecelerateInterpolator}
         *<commit_msg>Switch error log to verbose<commit_after>/*
 * Copyright (C) 2015 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hookedonplay.decoviewlib.charts;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import com.hookedonplay.decoviewlib.DecoView;
import com.hookedonplay.decoviewlib.events.DecoEvent;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;

/**
 * ChartSeries is the implementation of one series of data.
 * A {@link DecoView} can have one or more DataSeries
 */
abstract public class ChartSeries {
    /**
     * Minimum sweep angle. We need this to be greater than 0 as we want something drawn
     * even the data value is 0
     */
    static final private float MIN_SWEEP_ANGLE = 0.1f;
    static final private float MIN_SWEEP_ANGLE_FLAT = 0.1f;
    static final private float MIN_SWEEP_ANGLE_NONE = 0f;
    static final private float MIN_SWEEP_ANGLE_PIE = MIN_SWEEP_ANGLE_NONE;

    @SuppressWarnings("unused")
    protected final String TAG = getClass().getSimpleName();
    /**
     * ArcItem attributes to be drawn
     */
    protected final SeriesItem mSeriesItem;
    /**
     * Current Mode of drawing
     */
    protected DecoEvent.EventType mDrawMode;
    /**
     * Current Effect being executed (if any)
     */
    protected DecoDrawEffect mEffect;
    /**
     * Positions for current animation
     */
    protected float mPositionStart = 0;
    protected float mPositionEnd = 0;
    protected float mPositionCurrentEnd = 0;
    /**
     * 0..1.0f The percentage of the animation complete
     */
    protected float mPercentComplete = 1.0f;
    /**
     * Drawing bounds for arc
     */
    protected RectF mBounds;
    /**
     * Drawing bounds for arc after inset applied
     */
    protected RectF mBoundsInset;
    /**
     * Angle of drawing point origin.
     */
    protected int mAngleStart = 180;
    /**
     * Number of degrees to sweep. 360 for complete circle
     */
    protected int mAngleSweep = 360;
    /**
     * Paint used for drawing arc
     */
    protected Paint mPaint;
    /**
     * Arc visible or hidden
     */
    private boolean mVisible;
    /**
     * ValueAnimator to calculate arc drawing position during animation
     */
    private ValueAnimator mValueAnimator;

    private ColorAnimate mColorAnimate;

    /**
     * Construct an ArcSeries based on the ArcItem attributes and the angle and shape
     * of the arc
     *
     * @param seriesItem  Arc attributes to apply
     * @param totalAngle  Angle of the total arc area in degrees
     * @param rotateAngle number of degrees to rotate the start position
     *                    <p/>
     *                    No Access Modifier for the constructor is specified. This is deliberate so we use the
     *                    default package access. This class should not be constructed outside of the package scope.
     *                    Clients of this library need only to construct an {@link ChartSeries} and pass that to the
     *                    DynamicArcView to have a new ArcSeries created
     */
    ChartSeries(@NonNull SeriesItem seriesItem, int totalAngle, int rotateAngle) {
        mSeriesItem = seriesItem;
        mVisible = seriesItem.getInitialVisibility();
        setupView(totalAngle, rotateAngle);
        reset();
    }

    /**
     * Configure the view for the given angles. Set the total angle of the arc and also
     * pass an offset angle to change the initial drawing location
     *
     * @param totalAngle  The total angle of the Arc...360 is a circle
     * @param rotateAngle The angle to rotate the drawing start position
     */
    public void setupView(int totalAngle, int rotateAngle) {
        if (totalAngle < 0 || totalAngle > 360) {
            throw new IllegalArgumentException("Total angle of view must be in the range 0..360");
        }
        if (rotateAngle < 0 || rotateAngle > 360) {
            throw new IllegalArgumentException("Rotate angle of view must be in the range 0..360");
        }

        mAngleStart = rotateAngle;
        mAngleSweep = totalAngle;

        /**
         * If we are spinning anti-clockwise we need the start to be where the finish would
         * normally be
         */
        if (!mSeriesItem.getSpinClockwise()) {
            mAngleStart = (mAngleStart + mAngleSweep) % 360;
        }
        mBounds = null;
    }

    public SeriesItem getSeriesItem() {
        return mSeriesItem;
    }

    /**
     * Create the animation of filling the chart by using a valueAnimator to adjust values
     */
    public void startAnimateMove(@NonNull final DecoEvent event) {
        mDrawMode = event.getEventType();
        mVisible = true;

        cancelAnimation();

        final boolean changeColors = event.isColorSet();
        if (changeColors) {
            mColorAnimate = new ColorAnimate(mSeriesItem.getColor(), event.getColor());
            mSeriesItem.setColor(event.getColor());
        }
        float position = event.getEndPosition();

        if (Math.abs(mPositionEnd - position) < 0.01) {
            Log.v(TAG, "Aborting move request, no position change");
            return;
        }

        event.notifyStartListener();

        mPositionStart = mPositionCurrentEnd;
        mPositionEnd = position;

        mValueAnimator = ValueAnimator.ofFloat(mPositionStart, position);

        long animationDuration = event.getEffectDuration();
        if (animationDuration <= 0) {
            /**
             * If an animation duration is not set we calculate it using a formula of the proportion
             * of a revolution multiplied by the default time for a full revolution. This ensures
             * that the speed of the move is consistent for all ranges
             */
            animationDuration = (Math.abs((int) (mSeriesItem.getSpinDuration() *
                    ((mPositionStart - mPositionEnd) / mSeriesItem.getMaxValue()))));
        }
        mValueAnimator.setDuration(animationDuration);

        /**
         * Note: When setting the Interpolator the default is
         * {@link android.view.animation.AccelerateDecelerateInterpolator}
         *
{'fraction_non_alphanumeric': 0.07484258729250143, 'fraction_numerical': 0.008299942759015456, 'mean_word_length': 2.4864055874282864, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4743362', 'n_tokens_mistral': 3760, 'n_tokens_neox': 3578, 'n_words': 1320}