Android Open Source - Pedometer Value Line Chart






From Project

Back to project page Pedometer.

License

The source code is released under:

Apache License

If you think the Android project Pedometer listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 */*  w  w  w.j a  v a 2 s  .  c  om*/
 *   Copyright (C) 2014 Paul Cech
 *
 *   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 org.eazegraph.lib.charts;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ValueAnimator;

import org.eazegraph.lib.R;
import org.eazegraph.lib.communication.IOnPointFocusedListener;
import org.eazegraph.lib.models.BaseModel;
import org.eazegraph.lib.models.LegendModel;
import org.eazegraph.lib.models.Point2D;
import org.eazegraph.lib.models.StandardValue;
import org.eazegraph.lib.models.ValueLinePoint;
import org.eazegraph.lib.models.ValueLineSeries;
import org.eazegraph.lib.utils.Utils;

import java.util.ArrayList;
import java.util.List;

/**
 * A LineChart which displays various line series with one value and the remaining information is
 * calculated dynamically. It is possible to draw normal and cubic lines.
 */
public class ValueLineChart extends BaseChart {

    /**
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can
     *                access the current theme, resources, etc.
     */
    public ValueLineChart(Context context) {
        super(context);

        mUseCubic                     = DEF_USE_CUBIC;
        mUseOverlapFill               = DEF_USE_OVERLAP_FILL;
        mLineStroke                   = Utils.dpToPx(DEF_LINE_STROKE);
        mFirstMultiplier              = DEF_FIRST_MULTIPLIER;
        mSecondMultiplier             = 1.0f - mFirstMultiplier;
        mShowIndicator                = DEF_SHOW_INDICATOR;
        mIndicatorWidth               = Utils.dpToPx(DEF_INDICATOR_WIDTH);
        mIndicatorLineColor           = DEF_INDICATOR_COLOR;
        mIndicatorTextColor           = DEF_INDICATOR_COLOR;
        mIndicatorTextSize            = Utils.dpToPx(DEF_INDICATOR_TEXT_SIZE);
        mIndicatorLeftPadding         = Utils.dpToPx(DEF_INDICATOR_LEFT_PADDING);
        mIndicatorTopPadding          = Utils.dpToPx(DEF_INDICATOR_TOP_PADDING);
        mShowStandardValues           = DEF_SHOW_STANDARD_VALUE;
        mXAxisStroke                  = Utils.dpToPx(DEF_X_AXIS_STROKE);
        mActivateIndicatorShadow      = DEF_ACTIVATE_INDICATOR_SHADOW;
        mIndicatorShadowStrength      = Utils.dpToPx(DEF_INDICATOR_SHADOW_STRENGTH);
        mIndicatorShadowColor         = DEF_INDICATOR_SHADOW_COLOR;
        mIndicatorTextUnit            = DEF_INDICATOR_TEXT_UNIT;
        mShowLegendBeneathIndicator   = DEF_SHOW_LEGEND_BENEATH_INDICATOR;
        mUseDynamicScaling            = DEF_USE_DYNAMIC_SCALING;
        mScalingFactor                = DEF_SCALING_FACTOR;

        initializeGraph();
    }

    /**
     * Constructor that is called when inflating a view from XML. This is called
     * when a view is being constructed from an XML file, supplying attributes
     * that were specified in the XML file. This version uses a default style of
     * 0, so the only attribute values applied are those in the Context's Theme
     * and the given AttributeSet.
     * <p/>
     * <p/>
     * The method onFinishInflate() will be called after all children have been
     * added.
     *
     * @param context The Context the view is running in, through which it can
     *                access the current theme, resources, etc.
     * @param attrs   The attributes of the XML tag that is inflating the view.
     * @see #View(android.content.Context, android.util.AttributeSet, int)
     */
    public ValueLineChart(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ValueLineChart,
                0, 0
        );

        try {

            mUseCubic                     = a.getBoolean(R.styleable.ValueLineChart_egUseCubic,                         DEF_USE_CUBIC);
            mUseOverlapFill               = a.getBoolean(R.styleable.ValueLineChart_egUseOverlapFill,                   DEF_USE_OVERLAP_FILL);
            mLineStroke                   = a.getDimension(R.styleable.ValueLineChart_egLineStroke,                     Utils.dpToPx(DEF_LINE_STROKE));
            mFirstMultiplier              = a.getFloat(R.styleable.ValueLineChart_egCurveSmoothness,                    DEF_FIRST_MULTIPLIER);
            mSecondMultiplier             = 1.0f - mFirstMultiplier;
            mShowIndicator                = a.getBoolean(R.styleable.ValueLineChart_egShowValueIndicator,               DEF_SHOW_INDICATOR);
            mIndicatorWidth               = a.getDimension(R.styleable.ValueLineChart_egIndicatorWidth,                 Utils.dpToPx(DEF_INDICATOR_WIDTH));
            mIndicatorLineColor           = a.getColor(R.styleable.ValueLineChart_egIndicatorLineColor,                 DEF_INDICATOR_COLOR);
            mIndicatorTextColor           = a.getColor(R.styleable.ValueLineChart_egIndicatorTextColor,                 DEF_INDICATOR_COLOR);
            mIndicatorTextSize            = a.getDimension(R.styleable.ValueLineChart_egIndicatorWidth,                 Utils.dpToPx(DEF_INDICATOR_TEXT_SIZE));
            mIndicatorLeftPadding         = a.getDimension(R.styleable.ValueLineChart_egIndicatorLeftPadding,           Utils.dpToPx(DEF_INDICATOR_LEFT_PADDING));
            mIndicatorTopPadding          = a.getDimension(R.styleable.ValueLineChart_egIndicatorTopPadding,            Utils.dpToPx(DEF_INDICATOR_TOP_PADDING));
            mShowStandardValues           = a.getBoolean(R.styleable.ValueLineChart_egShowStandardValue,                DEF_SHOW_STANDARD_VALUE);
            mXAxisStroke                  = a.getDimension(R.styleable.ValueLineChart_egXAxisStroke,                    Utils.dpToPx(DEF_X_AXIS_STROKE));
            mActivateIndicatorShadow      = a.getBoolean(R.styleable.ValueLineChart_egActivateIndicatorShadow,          DEF_ACTIVATE_INDICATOR_SHADOW);
            mIndicatorShadowStrength      = a.getDimension(R.styleable.ValueLineChart_egIndicatorShadowStrength,        Utils.dpToPx(DEF_INDICATOR_SHADOW_STRENGTH));
            mIndicatorShadowColor         = a.getColor(R.styleable.ValueLineChart_egIndicatorShadowColor,               DEF_INDICATOR_SHADOW_COLOR);
            mIndicatorTextUnit            = a.getString(R.styleable.ValueLineChart_egIndicatorTextUnit);
            mShowLegendBeneathIndicator   = a.getBoolean(R.styleable.ValueLineChart_egShowLegendBeneathIndicator,       DEF_SHOW_LEGEND_BENEATH_INDICATOR);
            mUseDynamicScaling            = a.getBoolean(R.styleable.ValueLineChart_egUseDynamicScaling,                DEF_USE_DYNAMIC_SCALING);
            mScalingFactor                = a.getFloat(R.styleable.ValueLineChart_egScalingFactor,                      DEF_SCALING_FACTOR);

        } finally {
            // release the TypedArray so that it can be reused.
            a.recycle();
        }

        if(mIndicatorTextUnit == null) {
            mIndicatorTextUnit = "";
        }

        initializeGraph();
    }

    /**
     * Adds a new series to the graph.
     * @param _Series The series which should be added.
     */
    public void addSeries(ValueLineSeries _Series) {
        mSeries.add(_Series);
        onDataChanged();
    }

    /**
     * Resets and clears the data object.
     */
    @Override
    public void clearChart() {
        mSeries.clear();
        mStandardValues.clear();
        mFocusedPoint = null;
        mLastPoint = null;
    }

    /**
     * Adds a custom legend which should be displayed instead of the dynamic legend.
     * @param _Legend A list of LegendModels which will be displayed.
     */
    public void addLegend(List<LegendModel> _Legend) {
        mLegendList.addAll(_Legend);
        mUseCustomLegend = true;
        onLegendDataChanged();
    }

    /**
     * Adds a standard value to the graph. The standard value is a horizontal line as an overlay
     * dependent on the loaded data set.
     * @param _standardValue The value which will be interpreted as a y-coordinate dependent
     *                       on the maximum value of the data set.
     */
    public void addStandardValue(StandardValue _standardValue) {
        mStandardValues.add(_standardValue);
        onDataChanged();
    }

    /**
     * Adds a standard value to the graph. The standard value is a horizontal line as an overlay
     * dependent on the loaded data set.
     * @param _standardValue The value which will be interpreted as a y-coordinate dependent
     *                       on the maximum value of the data set.
     */
    public void addStandardValue(float _standardValue) {
        mStandardValues.add(new StandardValue(_standardValue));
        onDataChanged();
    }

    /**
     * Adds a list of standard values to the graph.
     * @param _standardValues The list with standard values.
     */
    public void addStandardValues(List<StandardValue> _standardValues) {
        mStandardValues.addAll(_standardValues);
        onDataChanged();
    }

    /**
     * Clears the list which contains all standard values.
     */
    public void clearStandardValues() {
        mStandardValues.clear();
        onDataChanged();
    }

    /**
     * Sets the onPointFocusedListener.
     * @param _listener An instance of the IOnPointFocusedListener interface.
     */
    public void setOnPointFocusedListener(IOnPointFocusedListener _listener) {
        mListener = _listener;
    }

    /**
     * Checks if the graph is a cubic graph.
     * @return True if it's a cubic graph.
     */
    public boolean isUseCubic() {
        return mUseCubic;
    }

    /**
     * Sets the option if the graph should use a cubic spline interpolation or not.
     * @param _useCubic True if the graph should use cubic spline interpolation.
     */
    public void setUseCubic(boolean _useCubic) {
        mUseCubic = _useCubic;
        onDataChanged();
    }

    /**
     * Checks if the graph uses an overlap fill. An overlap fill occurs whether the user set it explicitly
     * through the attributes or if only one data set is present.
     * @return True if overlap fill is activated.
     */
    public boolean isUseOverlapFill() {
        return mUseOverlapFill;
    }

    /**
     * Sets the overlap fill attribute.
     * @param _useOverlapFill True if an overlap fill should be used.
     */
    public void setUseOverlapFill(boolean _useOverlapFill) {
        mUseOverlapFill = _useOverlapFill;
        onDataChanged();
    }

    /**
     * Returns the size of the line stroke for every series.
     * @return Line stroke in px.
     */
    public float getLineStroke() {
        return mLineStroke;
    }

    /**
     * Sets the line stroke for every series.
     * @param _lineStroke Line stroke as a dp value.
     */
    public void setLineStroke(float _lineStroke) {
        mLineStroke = Utils.dpToPx(_lineStroke);
        invalidateGlobal();
    }

    /**
     * Checks if the indicator should be shown or not.
     * @return True if the indicator is shown.
     */
    public boolean isShowIndicator() {
        return mShowIndicator;
    }

    /**
     * Sets if the indicator should be shown or not.
     * @param _showIndicator True if the indicator should be shown.
     */
    public void setShowIndicator(boolean _showIndicator) {
        mShowIndicator = _showIndicator;
        invalidateGlobal();
    }

    /**
     * Returns the indicator line width (stroke).
     * @return Indicator line width in px.
     */
    public float getIndicatorWidth() {
        return mIndicatorWidth;
    }

    /**
     * Sets the indicator line width (stroke)
     * @param _indicatorWidth Indicator width as a dp value.
     */
    public void setIndicatorWidth(float _indicatorWidth) {
        mIndicatorWidth = Utils.dpToPx(_indicatorWidth);
        invalidateGlobal();
    }

    /**
     * Returns the color of the indicator line.
     * @return Color value.
     */
    public int getIndicatorLineColor() {
        return mIndicatorLineColor;
    }

    /**
     * Sets the indicator line color.
     * @param _indicatorLineColor Indicator line color value
     */
    public void setIndicatorLineColor(int _indicatorLineColor) {
        mIndicatorLineColor = _indicatorLineColor;
        invalidateGraphOverlay();
    }

    /**
     * Returns the color of the indicator text.
     *
     * @return Color value
     */
    public int getIndicatorTextColor() {
        return mIndicatorTextColor;
    }

    /**
     * Sets the indicator text color.
     *
     * @param _indicatorTextColor Indicator text color value
     */
    public void setIndicatorTextColor(int _indicatorTextColor) {
        mIndicatorTextColor = _indicatorTextColor;
        invalidateGraphOverlay();
    }

    /**
     * Returns the indicators value text size.
     * @return Indicator text size.
     */
    public float getIndicatorTextSize() {
        return mIndicatorTextSize;
    }

    /**
     * Sets the indicators value text size.
     * @param _indicatorTextSize Indicator text size in sp.
     */
    public void setIndicatorTextSize(float _indicatorTextSize) {
        mIndicatorTextSize = Utils.dpToPx(_indicatorTextSize);
        invalidateGraphOverlay();
    }

    /**
     * Returns the left padding for the indicator text.
     * @return Indicator text left padding in px
     */
    public float getIndicatorLeftPadding() {
        return mIndicatorLeftPadding;
    }

    /**
     * Sets the left padding for the indicator text.
     * @param _indicatorLeftPadding Indicator text left padding in dp
     */
    public void setIndicatorLeftPadding(float _indicatorLeftPadding) {
        mIndicatorLeftPadding = Utils.dpToPx(_indicatorLeftPadding);
        invalidateGraphOverlay();
    }

    /**
     * Returns the top padding for the indicator text.
     * @return Indicator text top padding in px
     */
    public float getIndicatorTopPadding() {
        return mIndicatorTopPadding;
    }

    /**
     * Sets the top padding for the indicator text.
     * @param _indicatorTopPadding Indicator text top padding in dp
     */
    public void setIndicatorTopPadding(float _indicatorTopPadding) {
        mIndicatorTopPadding = Utils.dpToPx(_indicatorTopPadding);
        invalidateGraphOverlay();
    }

    /**
     * Checks if the standard value line should be shown or not.
     * @return True if the standard value line should be shown.
     */
    public boolean isShowStandardValues() {
        return mShowStandardValues;
    }

    /**
     * Sets if the standard value should be shown or not.
     * @param _showStandardValues True if the standard value line should be shown.
     */
    public void setShowStandardValues(boolean _showStandardValues) {
        mShowStandardValues = _showStandardValues;
        onDataChanged();
    }

    /**
     * Returns the stroke size of the X-axis.
     * @return Stroke size in px.
     */
    public float getXAxisStroke() {
        return mXAxisStroke;
    }

    /**
     * Sets the stroke size of the X-axis.
     * @param _XAxisStroke Stroke size in dp.
     */
    public void setXAxisStroke(float _XAxisStroke) {
        mXAxisStroke = Utils.dpToPx(_XAxisStroke);
        invalidateGraphOverlay();
    }

    /**
     *
     * @return Checks if the shadow layer for the indicator text is activated.
     */
    public boolean hasActivateIndicatorShadow() {
        return mActivateIndicatorShadow;
    }

    /**
     * Toggles the shadow layer for the indicator text.
     * @param _activateIndicatorShadow Indication if the shadow layer should be enabled or not.
     */
    public void setActivateIndicatorShadow(boolean _activateIndicatorShadow) {
        mActivateIndicatorShadow = _activateIndicatorShadow;
        invalidateGraphOverlay();
    }

    /**
     *
     * @return The shadow layers strength/radius.
     */
    public float getIndicatorShadowStrength() {
        return mIndicatorShadowStrength;
    }

    /**
     * Sets the shadow layers strength/radius.
     *
     * @param _indicatorShadowStrength The shadow strength/radius (in dp)
     */
    public void setIndicatorShadowStrength(float _indicatorShadowStrength) {
        mIndicatorShadowStrength = Utils.dpToPx(_indicatorShadowStrength);
        invalidateGraphOverlay();
    }

    /**
     *
     * @return The shadow color
     */
    public int getIndicatorShadowColor() {
        return mIndicatorShadowColor;
    }

    /**
     * Sets the color in which the shadow layer will be drawn.
     *
     * @param _indicatorShadowColor Color for the shadow.
     */
    public void setIndicatorShadowColor(int _indicatorShadowColor) {
        mIndicatorShadowColor = _indicatorShadowColor;
        invalidateGraphOverlay();
    }

    /**
     *
     * @return The currently set unit which is placed after the indicator text.
     */
    public String getIndicatorTextUnit() {
        return mIndicatorTextUnit;
    }

    /**
     * Sets the unit which is placed after the indicator text.
     * If it is an empty String, nothing will be displayed and disables the drawing of the unit.
     *
     * @param _indicatorTextUnit The unit which should be drawn.
     */
    public void setIndicatorTextUnit(String _indicatorTextUnit) {
        mIndicatorTextUnit = _indicatorTextUnit;
        invalidateGraphOverlay();
    }

    public boolean isUseDynamicScaling() {
        return mUseDynamicScaling;
    }

    public void setUseDynamicScaling(boolean _useDynamicScaling) {
        mUseDynamicScaling = _useDynamicScaling;
        onDataChanged();
    }

    public float getScalingFactor() {
        return mScalingFactor;
    }

    public void setScalingFactor(float _scalingFactor) {
        mScalingFactor = _scalingFactor;
        onDataChanged();
    }

    /**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }

    /**
     * This is called during layout when the size of this view has changed. If
     * you were just added to the view hierarchy, you're called with the old
     * values of 0.
     *
     * @param w    Current width of this view.
     * @param h    Current height of this view.
     * @param oldw Old width of this view.
     * @param oldh Old height of this view.
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        onDataChanged();
        if(mUseCustomLegend) {
            onLegendDataChanged();
        }
    }

    /**
     * This is the main entry point after the graph has been inflated. Used to initialize the graph
     * and its corresponding members.
     */
    @Override
    protected void initializeGraph() {
        super.initializeGraph();

        mGraphOverlay.decelerate();

        mSeries     = new ArrayList<ValueLineSeries>();
        mLegendList = new ArrayList<LegendModel>();

        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setStrokeWidth(mLineStroke);

        mLegendPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLegendPaint.setColor(mLegendColor);
        mLegendPaint.setTextSize(mLegendTextSize);
        mLegendPaint.setStrokeWidth(2);
        mLegendPaint.setStyle(Paint.Style.FILL);

        mMaxFontHeight = Utils.calculateMaxTextHeight(mLegendPaint);

        mIndicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mIndicatorPaint.setColor(mIndicatorLineColor);
        mIndicatorPaint.setTextSize(mIndicatorTextSize);
        mIndicatorPaint.setStrokeWidth(mIndicatorWidth);
        mIndicatorPaint.setStyle(Paint.Style.FILL);

        mRevealAnimator = ValueAnimator.ofFloat(0, 1);
        mRevealAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRevealValue = animation.getAnimatedFraction();

                mScale.reset();
                mScale.setScale(1, 1.f * mRevealValue, 0, mGraphHeight - mNegativeOffset);

                mGraph.invalidate();
            }
        });
        mRevealAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mStartedAnimation = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        if(this.isInEditMode()) {
            ValueLineSeries series1 = new ValueLineSeries();
            series1.setColor(0xFF63CBB0);

            series1.addPoint(new ValueLinePoint(1.4f));
            series1.addPoint(new ValueLinePoint(4.4f));
            series1.addPoint(new ValueLinePoint(2.4f));
            series1.addPoint(new ValueLinePoint(3.2f));
            series1.addPoint(new ValueLinePoint(2.6f));
            series1.addPoint(new ValueLinePoint(5.0f));
            series1.addPoint(new ValueLinePoint(3.5f));
            series1.addPoint(new ValueLinePoint(2.4f));
            series1.addPoint(new ValueLinePoint(0.4f));
            series1.addPoint(new ValueLinePoint(3.4f));
            series1.addPoint(new ValueLinePoint(2.5f));
            series1.addPoint(new ValueLinePoint(1.0f));
            series1.addPoint(new ValueLinePoint(4.2f));
            series1.addPoint(new ValueLinePoint(2.4f));
            series1.addPoint(new ValueLinePoint(3.6f));
            series1.addPoint(new ValueLinePoint(1.0f));
            series1.addPoint(new ValueLinePoint(2.5f));
            series1.addPoint(new ValueLinePoint(1.4f));
            addSeries(series1);
        }
    }

    /**
     * Should be called after new data is inserted. Will be automatically called, when the view dimensions
     * changed.
     *
     * Calculates various offsets and positions for different overlay features based on the graph settings.
     * After the calculation the Path is generated as a normal path or cubic path (Based on 'egUseCubic' attribute).
     */
    @Override
    protected void onDataChanged() {

        if(!mSeries.isEmpty()) {
            int   usableGraphHeight = (int) (mGraphHeight - Utils.dpToPx(1.f));
            int   seriesCount  = mSeries.size();
            float maxValue     = 0.f;
            float minValue     = Float.MAX_VALUE;
            mNegativeValue     = 0.f;
            mNegativeOffset    = 0.f;
            mHasNegativeValues = false;

            // calculate the maximum and minimum value present in data
            for (ValueLineSeries series : mSeries) {
                for (ValueLinePoint point : series.getSeries()) {

                    if (point.getValue() > maxValue)
                        maxValue = point.getValue();

                    if (point.getValue() < mNegativeValue)
                        mNegativeValue = point.getValue();

                    if (point.getValue() < minValue)
                        minValue = point.getValue();
                }
            }

            // check if the standardvalue is greater than all other values
            if(mShowStandardValues) {
                for (StandardValue value : mStandardValues) {

                    if(value.getValue() > maxValue)
                        maxValue = value.getValue();

                    if (value.getValue() < mNegativeValue)
                        mNegativeValue = value.getValue();

                    if (value.getValue() < minValue)
                        minValue = value.getValue();
                }
            }


            if(!mUseDynamicScaling) {
                minValue = 0;
            }
            else {
                minValue *= mScalingFactor;
            }

            // check if values below zero were found
            if(mNegativeValue < 0) {
                mHasNegativeValues = true;
                maxValue += (mNegativeValue * -1);
                minValue = 0;
            }


            float heightMultiplier  = usableGraphHeight / (maxValue - minValue);

            // calculate the offset
            if(mHasNegativeValues) {
                mNegativeOffset = (mNegativeValue * -1) * heightMultiplier;
            }

            // calculate the y position for standardValue
            if(mShowStandardValues) {
                for (StandardValue value : mStandardValues) {
                    value.setY((int) (usableGraphHeight - mNegativeOffset - ((value.getValue() - minValue) * heightMultiplier)));
                }
            }

            for (ValueLineSeries series : mSeries) {

                int   seriesPointCount  = series.getSeries().size();

                // check if more than one point is available
                if (seriesPointCount <= 1) {
                    Log.w(LOG_TAG, "More than one point should be available!");
                }
                else {

                    float widthOffset = (float) mGraphWidth / (float) seriesPointCount;
                    widthOffset += widthOffset / seriesPointCount;
                    float currentOffset = 0;
                    series.setWidthOffset(widthOffset);

                    // used to store first point and set it later as ending point, if a graph fill is selected
                    float firstX = currentOffset;
                    float firstY = usableGraphHeight - ((series.getSeries().get(0).getValue() - minValue) * heightMultiplier);

                    Path path = new Path();
                    path.moveTo(firstX, firstY);
                    series.getSeries().get(0).setCoordinates(new Point2D(firstX, firstY));

                    // If a cubic curve should be drawn then calculate cubic path
                    // If not then just draw basic lines
                    if (mUseCubic) {
                        Point2D P1 = new Point2D();
                        Point2D P2 = new Point2D();
                        Point2D P3 = new Point2D();

                        for (int i = 0; i < seriesPointCount - 1; i++) {

                            int i3 = (seriesPointCount - i) < 3 ? i + 1 : i + 2;
                            float offset2 = (seriesPointCount - i) < 3 ? mGraphWidth : currentOffset + widthOffset;
                            float offset3 = (seriesPointCount - i) < 3 ? mGraphWidth : currentOffset + (2*widthOffset);

                            P1.setX(currentOffset);
                            P1.setY(usableGraphHeight - ((series.getSeries().get(i).getValue() - minValue) * heightMultiplier));

                            P2.setX(offset2);
                            P2.setY(usableGraphHeight - ((series.getSeries().get(i + 1).getValue() - minValue) * heightMultiplier));
                            Utils.calculatePointDiff(P1, P2, P1, mSecondMultiplier);

                            P3.setX(offset3);
                            P3.setY(usableGraphHeight - ((series.getSeries().get(i3).getValue() - minValue) * heightMultiplier));
                            Utils.calculatePointDiff(P2, P3, P3, mFirstMultiplier);

                            currentOffset += widthOffset;

                            series.getSeries().get(i + 1).setCoordinates(new Point2D(P2.getX(), P2.getY()));
                            path.cubicTo(P1.getX(), P1.getY(), P2.getX(), P2.getY(), P3.getX(), P3.getY());
                        }
                    } else {
                        boolean first = true;
                        int count = 1;

                        for (ValueLinePoint point : series.getSeries()) {
                            if (first) {
                                first = false;
                                continue;
                            }
                            currentOffset += widthOffset;
                            if (count == seriesPointCount - 1) {
                                // if the last offset is smaller than the width, then the offset should be as long as the graph
                                // to prevent a graph drop
                                if (currentOffset < mGraphWidth) {
                                    currentOffset = mGraphWidth;
                                }
                            }
                            point.setCoordinates(new Point2D(currentOffset, usableGraphHeight - ((point.getValue() - minValue) * heightMultiplier)));
                            path.lineTo(point.getCoordinates().getX(), point.getCoordinates().getY());
                            count++;
                        }
                    }

                    if (mUseOverlapFill || seriesCount == 1) {
                        path.lineTo(mGraphWidth, usableGraphHeight);
                        path.lineTo(0, usableGraphHeight);
                        path.lineTo(firstX, firstY);
                    }

                    series.setPath(path);
                }
            }

            if(!mUseCustomLegend) {
                int index = 0;
                int size = mSeries.get(0).getSeries().size();

                // Only calculate if more than one point is available
                if (size > 1) {

                    for (ValueLinePoint valueLinePoint : mSeries.get(0).getSeries()) {
                        if (!(index == 0 || index == size - 1)) {
                            valueLinePoint.setLegendBounds(new RectF(
                                    valueLinePoint.getCoordinates().getX() - mSeries.get(0).getWidthOffset() / 2,
                                    0,
                                    valueLinePoint.getCoordinates().getX() + mSeries.get(0).getWidthOffset() / 2,
                                    mLegendHeight));
                        } else {
                            valueLinePoint.setIgnore(true);
                        }

                        index++;
                    }
                    Utils.calculateLegendInformation(mSeries.get(0).getSeries(), 0, mGraphWidth, mLegendPaint);
                }
            }

            // set the first point for the indicator
            if(mShowIndicator && mSeries.size() == 1) {
                int size = mSeries.get(0).getSeries().size();
                int index;

                // Only calculate if more than one point is available
                if (size > 1) {
                    // position the indicator in the middle at the nearest value
                    if (size == 3) {
                        index = size / 2;
                    } else {
                        index = (size / 2) - 1;
                    }

                    mFocusedPoint = mSeries.get(0).getSeries().get(index);
                    mTouchedArea.setX(mFocusedPoint.getCoordinates().getX());
                    mTouchedArea.setY(mFocusedPoint.getCoordinates().getY());

                    calculateValueTextHeight();
                }
            }
        }

        super.onDataChanged();
        invalidateGlobal();
    }

    /**
     * Calculates the legend bounds for a custom list of legends.
     */
    protected void onLegendDataChanged() {

        int   legendCount = mLegendList.size();
        float margin = (mLegendWidth / legendCount);
        float currentOffset = 0;

        for (LegendModel model : mLegendList) {
            model.setLegendBounds(new RectF(currentOffset, 0, currentOffset + margin, mLegendHeight));
            Rect textBounds = new Rect();
            mLegendPaint.getTextBounds(model.getLegendLabel(), 0, model.getLegendLabel().length(), textBounds);
            model.setTextBounds(textBounds);
            currentOffset += margin;
        }

        invalidateGlobal();
    }

    /**
     * Calculates the text height for the indicator value and sets its x-coordinate.
     */
    private void calculateValueTextHeight() {
        Rect valueRect = new Rect();
        Rect legendRect = new Rect();
        String str = Utils.getFloatString(mFocusedPoint.getValue(), mShowDecimal) + (!mIndicatorTextUnit.isEmpty() ? " " + mIndicatorTextUnit : "");

        // calculate the boundaries for both texts
        mIndicatorPaint.getTextBounds(str, 0, str.length(), valueRect);
        mLegendPaint.getTextBounds(mFocusedPoint.getLegendLabel(), 0, mFocusedPoint.getLegendLabel().length(), legendRect);

        // calculate string positions in overlay
        mValueTextHeight = valueRect.height();
        mValueLabelY  = (int) (mValueTextHeight + mIndicatorTopPadding);
        mLegendLabelY = (int) (mValueTextHeight + mIndicatorTopPadding + legendRect.height() + Utils.dpToPx(7.f));

        int chosenWidth = valueRect.width() > legendRect.width() ? valueRect.width() : legendRect.width();

        // check if text reaches over screen
        if(mFocusedPoint.getCoordinates().getX() + chosenWidth + mIndicatorLeftPadding > mGraphWidth) {
            mValueLabelX  = (int) (mFocusedPoint.getCoordinates().getX() - (valueRect.width() + mIndicatorLeftPadding));
            mLegendLabelX = (int) (mFocusedPoint.getCoordinates().getX() - (legendRect.width() + mIndicatorLeftPadding));
        }
        else {
            mValueLabelX = mLegendLabelX = (int) (mFocusedPoint.getCoordinates().getX() + mIndicatorLeftPadding);
        }
    }

    /**
     * Returns the first series.
     * @return The first series.
     */
    @Override
    public List<ValueLinePoint> getData() { return mSeries.get(0).getSeries(); }

    /**
     * Returns all series which are currently inserted.
     * @return Inserted series.
     */
    public List<ValueLineSeries> getDataSeries() { return mSeries; }

    // ---------------------------------------------------------------------------------------------
    //                          Override methods from view layers
    // ---------------------------------------------------------------------------------------------


    @Override
    protected void onGraphDraw(Canvas _Canvas) {
        super.onGraphDraw(_Canvas);
        if(mUseOverlapFill) {
            mLinePaint.setStyle(Paint.Style.FILL);
        }
        else {
            if(mSeries.size() == 1) {
                mLinePaint.setStyle(Paint.Style.FILL);
            }
            else {
                mLinePaint.setStrokeWidth(mLineStroke);
                mLinePaint.setStyle(Paint.Style.STROKE);
            }
        }

        _Canvas.concat(mScale);
        if(mHasNegativeValues) {
            _Canvas.translate(0, -mNegativeOffset);
        }
        // drawing of lines
        for (ValueLineSeries series : mSeries) {
            mLinePaint.setColor(series.getColor());
            _Canvas.drawPath(series.getPath(), mLinePaint);
        }

        _Canvas.restore();
    }

    @Override
    protected void onGraphOverlayDraw(Canvas _Canvas) {
        super.onGraphOverlayDraw(_Canvas);

        // draw x-axis
        mLegendPaint.setStrokeWidth(mXAxisStroke);
        _Canvas.drawLine(
                0,
                mGraphHeight - mNegativeOffset,
                mGraphWidth,
                mGraphHeight - mNegativeOffset,
                mLegendPaint
        );

        // draw standard value line
        if(mShowStandardValues) {
            for (StandardValue value : mStandardValues) {
                mIndicatorPaint.setColor(value.getColor());
                mIndicatorPaint.setStrokeWidth(value.getStroke());
                _Canvas.drawLine(
                        0,
                        value.getY(),
                        mGraphWidth,
                        value.getY(),
                        mIndicatorPaint
                );
            }
        }

        // draw touch indicator
        // TODO: if mShowIndicator is true, then check all series not only if one series is inserted
        if(mShowIndicator && mSeries.size() == 1) {
            mIndicatorPaint.setColor(mIndicatorLineColor);
            mIndicatorPaint.setStrokeWidth(mIndicatorWidth);

            _Canvas.drawLine(mTouchedArea.getX(), 0, mTouchedArea.getX(), mGraphHeight, mIndicatorPaint);

            if(mFocusedPoint != null) {

                // set shadow
                if(mActivateIndicatorShadow) {
                    mIndicatorPaint.setShadowLayer(mIndicatorShadowStrength, 0, 0, mIndicatorShadowColor);
                }

                mIndicatorPaint.setColor(mIndicatorTextColor);
                _Canvas.drawText(Utils.getFloatString(mFocusedPoint.getValue(), mShowDecimal) + (!mIndicatorTextUnit.isEmpty() ? " " + mIndicatorTextUnit : ""),
                        mValueLabelX,
                        mValueLabelY,
                        mIndicatorPaint);

                if(mShowLegendBeneathIndicator) {
                    mLegendPaint.setColor(mIndicatorTextColor);
                    _Canvas.drawText(mFocusedPoint.getLegendLabel(),
                            mLegendLabelX,
                            mLegendLabelY,
                            mLegendPaint);
                }

                // reset shadow
                if(mActivateIndicatorShadow) {
                    mIndicatorPaint.setShadowLayer(0, 0, 0, 0x00000000);
                }
            }
        }

    }

    @Override
    protected void onLegendDraw(Canvas _Canvas) {
        super.onLegendDraw(_Canvas);
        mLegendPaint.setColor(mLegendColor);
        mLegendPaint.setStrokeWidth(DEF_LEGEND_STROKE);

        if(!mSeries.isEmpty()) {
            if (mUseCustomLegend) {
                for (LegendModel model : mLegendList) {
                    Rect textBounds = model.getTextBounds();
                    RectF bounds = model.getLegendBounds();
                    _Canvas.drawText(model.getLegendLabel(), bounds.centerX() - (textBounds.width() / 2), bounds.centerY(), mLegendPaint);
                    _Canvas.drawLine(bounds.centerX(), bounds.centerY() - textBounds.height() - mLegendTopPadding, bounds.centerX(), mLegendTopPadding, mLegendPaint);
                }
            } else {
                List<? extends BaseModel> list = mSeries.get(0).getSeries();
                for (BaseModel model : list) {
                    if (model.canShowLabel()) {
                        RectF bounds = model.getLegendBounds();
                        _Canvas.drawText(model.getLegendLabel(), model.getLegendLabelPosition(), bounds.bottom - mMaxFontHeight, mLegendPaint);
                        _Canvas.drawLine(
                                bounds.centerX(),
                                bounds.bottom - mMaxFontHeight * 2 - mLegendTopPadding,
                                bounds.centerX(),
                                mLegendTopPadding, mLegendPaint
                        );
                    }
                }
            }
        }
    }

    @Override
    protected boolean onGraphOverlayTouchEvent(MotionEvent _Event) {

        super.onGraphOverlayTouchEvent(_Event);

        float newX = _Event.getX();
        float newY = _Event.getY();

        switch (_Event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                return true;
        }

        if(mShowIndicator && mSeries.size() == 1) {
            int size       = mSeries.get(0).getSeries().size();

            for (int i = 0; i < size; i++) {

                // check if touchedX equals one the points
                if (mSeries.get(0).getSeries().get(i).getCoordinates().getX() == newX) {
                    mFocusedPoint = mSeries.get(0).getSeries().get(i);
                    break;
                } else {
                    // check if we reached the last when --> (true) use last point
                    if (i == size - 1) {
                        mFocusedPoint = mSeries.get(0).getSeries().get(i);
                        break;
                    } else {
                        float x = mSeries.get(0).getSeries().get(i).getCoordinates().getX();
                        float nextX = mSeries.get(0).getSeries().get(i + 1).getCoordinates().getX();

                        // check if touchedX is between two points
                        if (newX > x && newX < nextX) {
                            // check which distance between touchedX and the two points is smaller
                            if (newX - x > nextX - newX) {
                                mFocusedPoint = mSeries.get(0).getSeries().get(i + 1);
                                break;
                            } else {
                                mFocusedPoint = mSeries.get(0).getSeries().get(i);
                                break;
                            }
                        }
                        //check if touchedX distance between the points is equal -> choose first Point
                        else if (newX > x && newX < nextX) {
                            mFocusedPoint = mSeries.get(0).getSeries().get(i);
                            break;
                        }
                    }
                }
            }

            if (mFocusedPoint != null) {
                mTouchedArea = mFocusedPoint.getCoordinates();

            } else {
                mTouchedArea.setX(newX);
                mTouchedArea.setY(newY);
            }

            if(mLastPoint != mFocusedPoint) {
                mLastPoint = mFocusedPoint;

                calculateValueTextHeight();

                if (mListener != null) {
                    mListener.onPointFocused(mSeries.get(0).getSeries().indexOf(mFocusedPoint));
                }
            }

            invalidateGlobal();
        }
        return true;
    }


    //##############################################################################################
    // Variables
    //##############################################################################################

    private static final String LOG_TAG = ValueLineChart.class.getSimpleName();

    public static final boolean DEF_USE_CUBIC                       = false;
    public static final boolean DEF_USE_OVERLAP_FILL                = false;
    public static final float   DEF_LINE_STROKE                     = 5f;
    public static final float   DEF_FIRST_MULTIPLIER                = 0.33f;
    public static final boolean DEF_SHOW_INDICATOR                  = true;
    public static final float   DEF_INDICATOR_WIDTH                 = 2f;
    public static final int     DEF_INDICATOR_COLOR                 = 0xFF0000FF;
    // will be interpreted as sp value
    public static final float   DEF_INDICATOR_TEXT_SIZE             = 15.f;
    public static final float   DEF_INDICATOR_LEFT_PADDING          = 4.f;
    public static final float   DEF_INDICATOR_TOP_PADDING           = 4.f;

    public static final boolean DEF_SHOW_STANDARD_VALUE             = true;
    public static final float   DEF_X_AXIS_STROKE                   = 2f;
    public static final float   DEF_LEGEND_STROKE                   = 2f;
    public static final boolean DEF_ACTIVATE_INDICATOR_SHADOW       = false;
    // dimension value
    public static final float   DEF_INDICATOR_SHADOW_STRENGTH       = 0.7f;
    public static final int     DEF_INDICATOR_SHADOW_COLOR          = 0xFF676767;
    public static final String  DEF_INDICATOR_TEXT_UNIT             = "";
    public static final boolean DEF_SHOW_LEGEND_BENEATH_INDICATOR   = false;
    public static final boolean DEF_USE_DYNAMIC_SCALING             = false;
    public static final float   DEF_SCALING_FACTOR                  = 0.96f;

    private Paint                   mLinePaint;
    private Paint                   mLegendPaint;
    private Paint                   mIndicatorPaint;

    private List<ValueLineSeries>   mSeries;
    private List<LegendModel>       mLegendList;

    private boolean                 mHasNegativeValues  = false;
    private float                   mNegativeValue      = 0.f;
    private float                   mNegativeOffset     = 0.f;

    private IOnPointFocusedListener mListener = null;

    private float                   mFirstMultiplier;
    private float                   mSecondMultiplier;

    private boolean                 mUseCustomLegend = false;
    private Point2D                 mTouchedArea     = new Point2D(0, 0);
    private ValueLinePoint          mFocusedPoint    = null;
    private float                   mValueTextHeight;

    // GraphOverlay vars
    private ValueLinePoint          mLastPoint = null;
    private int                     mValueLabelX  = 0;
    private int                     mValueLabelY  = 0;
    private int                     mLegendLabelX = 0;
    private int                     mLegendLabelY = 0;

    private List<StandardValue>     mStandardValues = new ArrayList<StandardValue>();

    /**
     * Indicates to fill the bottom area of a series with its given color.
     */
    private boolean                 mUseOverlapFill;
    private boolean                 mUseCubic;
    private float                   mLineStroke;
    private boolean                 mShowIndicator;
    private float                   mIndicatorWidth;
    private int                     mIndicatorLineColor;
    private int                     mIndicatorTextColor;
    private float                   mIndicatorTextSize;
    private float                   mIndicatorLeftPadding;
    private float                   mIndicatorTopPadding;
    private boolean                 mShowStandardValues;
    private float                   mXAxisStroke;
    private boolean                 mActivateIndicatorShadow;
    private float                   mIndicatorShadowStrength;
    private int                     mIndicatorShadowColor;
    private String                  mIndicatorTextUnit;
    private boolean                 mShowLegendBeneathIndicator;
    /**
     * Enabling this when only positive and big values are present and only have little fluctuations,
     * a y-axis scaling takes place to see a better difference between the values.
     */
    private boolean                 mUseDynamicScaling;
    /**
     * The factor for the dynamic scaling, which determines how many percent of the minimum value
     * should be subtracted to achieve the scaling.
     */
    private float                   mScalingFactor;

    protected Matrix                mScale = new Matrix();
}




Java Source Code List

com.google.example.games.basegameutils.ApplicationTest.java
com.google.example.games.basegameutils.BaseGameActivity.java
com.google.example.games.basegameutils.GameHelperUtils.java
com.google.example.games.basegameutils.GameHelper.java
com.nineoldandroids.ApplicationTest.java
com.nineoldandroids.animation.AnimatorInflater.java
com.nineoldandroids.animation.AnimatorListenerAdapter.java
com.nineoldandroids.animation.AnimatorSet.java
com.nineoldandroids.animation.Animator.java
com.nineoldandroids.animation.ArgbEvaluator.java
com.nineoldandroids.animation.FloatEvaluator.java
com.nineoldandroids.animation.FloatKeyframeSet.java
com.nineoldandroids.animation.IntEvaluator.java
com.nineoldandroids.animation.IntKeyframeSet.java
com.nineoldandroids.animation.KeyframeSet.java
com.nineoldandroids.animation.Keyframe.java
com.nineoldandroids.animation.ObjectAnimator.java
com.nineoldandroids.animation.PreHoneycombCompat.java
com.nineoldandroids.animation.PropertyValuesHolder.java
com.nineoldandroids.animation.TimeAnimator.java
com.nineoldandroids.animation.TypeEvaluator.java
com.nineoldandroids.animation.ValueAnimator.java
com.nineoldandroids.util.FloatProperty.java
com.nineoldandroids.util.IntProperty.java
com.nineoldandroids.util.NoSuchPropertyException.java
com.nineoldandroids.util.Property.java
com.nineoldandroids.util.ReflectiveProperty.java
com.nineoldandroids.view.ViewHelper.java
com.nineoldandroids.view.ViewPropertyAnimatorHC.java
com.nineoldandroids.view.ViewPropertyAnimatorICS.java
com.nineoldandroids.view.ViewPropertyAnimatorPreHC.java
com.nineoldandroids.view.ViewPropertyAnimator.java
com.nineoldandroids.view.animation.AnimatorProxy.java
de.j4velin.pedometer.Activity_Main.java
de.j4velin.pedometer.AppUpdatedReceiver.java
de.j4velin.pedometer.ApplicationTest.java
de.j4velin.pedometer.BootReceiver.java
de.j4velin.pedometer.Database.java
de.j4velin.pedometer.Dialog_Split.java
de.j4velin.pedometer.Dialog_Statistics.java
de.j4velin.pedometer.Fragment_Overview.java
de.j4velin.pedometer.Fragment_Settings.java
de.j4velin.pedometer.PlayServices.java
de.j4velin.pedometer.background.SensorListener.java
de.j4velin.pedometer.background.ShutdownRecevier.java
de.j4velin.pedometer.util.ColorPreview.java
de.j4velin.pedometer.util.Logger.java
de.j4velin.pedometer.util.TimeZoneListener.java
de.j4velin.pedometer.util.Util.java
de.j4velin.pedometer.widget.DashClock.java
de.j4velin.pedometer.widget.WidgetConfig.java
de.j4velin.pedometer.widget.WidgetUpdateService.java
de.j4velin.pedometer.widget.Widget.java
net.margaritov.preference.colorpicker.AlphaPatternDrawable.java
net.margaritov.preference.colorpicker.ApplicationTest.java
net.margaritov.preference.colorpicker.ColorPickerDialog.java
net.margaritov.preference.colorpicker.ColorPickerPanelView.java
net.margaritov.preference.colorpicker.ColorPickerPreference.java
net.margaritov.preference.colorpicker.ColorPickerView.java
net.margaritov.preference.colorpicker.Test.java
org.eazegraph.lib.ApplicationTest.java
org.eazegraph.lib.charts.BarChart.java
org.eazegraph.lib.charts.BaseBarChart.java
org.eazegraph.lib.charts.BaseChart.java
org.eazegraph.lib.charts.PieChart.java
org.eazegraph.lib.charts.StackedBarChart.java
org.eazegraph.lib.charts.ValueLineChart.java
org.eazegraph.lib.communication.IOnBarClickedListener.java
org.eazegraph.lib.communication.IOnItemFocusChangedListener.java
org.eazegraph.lib.communication.IOnPointFocusedListener.java
org.eazegraph.lib.models.BarModel.java
org.eazegraph.lib.models.BaseModel.java
org.eazegraph.lib.models.LegendModel.java
org.eazegraph.lib.models.PieModel.java
org.eazegraph.lib.models.Point2D.java
org.eazegraph.lib.models.StackedBarModel.java
org.eazegraph.lib.models.StandardValue.java
org.eazegraph.lib.models.ValueLinePoint.java
org.eazegraph.lib.models.ValueLineSeries.java
org.eazegraph.lib.utils.Utils.java